Repository navigation
Conversation
There was a problem hiding this comment.
All reported issues were addressed across 1 file
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
| * queues backlog + 1 connections (sk_acceptq_is_full) where macOS queues | ||
| * backlog, so 2 is also the queue length the guest asked for. | ||
| */ | ||
| int host_backlog = backlog == 1 ? 2 : backlog; |
There was a problem hiding this comment.
The second reason in the comment (Linux queues backlog + 1 where macOS queues backlog) holds for every positive backlog, yet only 1 is remapped. So listen(fd, 4) still queues one connection fewer than Linux, and a reader will take this ternary for the general rule. The remap also applies to AF_UNIX listeners, which the urgent-mark bug never touches. Either say that this is the TCP urgent-mark workaround and that matching Linux at 1 is a side effect, or, as a follow-up, map every positive backlog to backlog + 1 below the host somaxconn and pin the queue depth with a test that leaves connections unaccepted.
There was a problem hiding this comment.
Done in aa01142: sys_listen passes backlog + 1 for every backlog from 0 up and leaves a negative one to the host, which reads it as somaxconn the way Linux does. Host backlog 1 is avoided on AF_INET/AF_INET6 only, so AF_UNIX gets the Linux depth exactly and a TCP listen(fd, 0) queues 2 instead of 1. tests/test-listen-backlog.c leaves connections unaccepted and checks the depth; the TCP cases are a lower bound, because macOS TCP sometimes completes one or two connections past its queue even with plain host sockets.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
On macOS 27.0.1 (xnu-13432.1.9) a TCP socket accepted from a listener with backlog 1 loses the urgent mark of the first urgent byte on about half of the connections. The connection receives the byte, but SIOCATMARK stays 0, recv(MSG_OOB) fails with EINVAL, and EVFILT_EXCEPT with NOTE_OOB does not fire, so the watcher in asyncio.c has no event to turn into SIGURG. A program with plain blocking sockets and no elfuse loses the mark on 94 to 111 of 200 connections at backlog 1, and on none at backlog 0, 2 or 128. sys_listen passes 2 to the host when the guest asks for 1. Linux lets backlog + 1 connections wait (sk_acceptq_is_full) and macOS lets exactly backlog connections wait, measured for backlog 1 to 4, so host backlog 2 holds the 2 connections Linux holds for listen(fd, 1). Verified: the SIGURG case of tests/test-sigio.c failed 19 of 40 runs without the change and 0 of 200 with it. Fixes sysprog21#402
00dcc57 to
aa01142
Compare
Linux lets backlog + 1 connections wait on a listener (sk_acceptq_is_full, unix_recvq_full) and macOS lets exactly backlog wait, so a guest listen(fd, 4) queued one connection fewer than on Linux, and listen(fd, 0) queued 128 instead of 1, since macOS reads 0 as somaxconn. Measured on the host, for TCP and AF_UNIX alike: backlog 1 to 128 queues exactly that many, and 0, negative, or above 128 queues 128. sys_listen now passes backlog + 1 for every backlog from 0 up, and leaves a negative one to the host, which reads it as somaxconn the way Linux does. Host backlog 1 stays avoided on AF_INET and AF_INET6 only, for the urgent-mark loss in the previous commit, so a TCP listen(fd, 0) queues 2 where Linux queues 1. AF_UNIX gets the exact depth. tests/test-listen-backlog.c connects without accepting and counts what completes. The AF_UNIX cases check the exact depth. The TCP cases check a lower bound, because macOS TCP sometimes completes one or two connections past its queue: 1 run in 30 with plain host sockets.
aa01142 to
1f569c6
Compare
Fixes #402.
The last case of
tests/test-sigio.c("SIGURG delivered on TCP out-of-band byte without O_ASYNC") failed in about half of the runs. The watcher insrc/syscall/asyncio.cis not at fault. In a failing run the knote is armed and noEVFILT_EXCEPTevent arrives, because the accepted host socket has no urgent mark to report:TCP_CONNECTION_INFOshows the byte received (tcpi_rxbytesis 1), butSIOCATMARKreports 0 andrecv(MSG_OOB | MSG_PEEK)fails withEINVAL. When the mark is set, the event,signal_queueand the guest handler all follow.The trigger is the host backlog. The test calls
listen(lst, 1)andsys_listenhanded that value to the host. On macOS 27.0.1 a TCP socket accepted from a listener with backlog 1 loses the urgent mark of the first urgent byte on about half of the connections. This program shows it with plain blocking sockets and no elfuse:On this host, 200 connections per run:
./oob-backlog 200 1: 94 to 111 connections lose the byte (six runs)../oob-backlog 200 0,200 2,200 128: none../oob-backlog 200 1 1(one in-band byte received first): none.The loss is per connection: a second urgent byte on the same connection had the same result as the first on 200 of 200 connections. Why the kernel does this is not established; reading the sequence numbers on
lo0needs root.The second commit gives every listener Linux's accept-queue depth. Linux lets backlog + 1 connections wait (
sk_acceptq_is_full,unix_recvq_full), and macOS lets exactly backlog wait. Measured on the host for TCP andAF_UNIXalike, backlog 1 to 128 queues exactly that many, and 0, a negative value or anything above 128 queues 128 (kern.ipc.somaxconn). Solisten(fd, 4)queued 4 instead of 5, andlisten(fd, 0)queued 128 instead of 1.sys_listenpasses backlog + 1 for every backlog from 0 up and passes a negative one through; both kernels read a negative backlog assomaxconn. OnAF_INETandAF_INET6host backlog 1 is never used, for the urgent-mark loss above, so a TCPlisten(fd, 0)queues 2 where Linux queues 1.AF_UNIXgets the Linux depth exactly.tests/test-listen-backlog.cconnects without accepting and counts the connections that complete. TheAF_UNIXcases check the exact depth. The TCP cases check that backlog + 1 fit and set no upper bound, because macOS TCP sometimes completes one or two connections past its queue: 1 run in 30 with plain host sockets, and 10 of 80 TCP cases over 40 runs under elfuse.Rebased on
mainat 8612033../build/elfuse build/test-sigio, 40 runs onmain: 19 failed, each on the SIGURG case../build/elfuse build/test-listen-backlog, 50 runs: 0 failed. Before the second commit, 3 of its 5 cases failed.test-listen-backlogon the QEMU Linux reference VM, 3 runs: 0 failed.make check: all sections pass.make test-matrix-elfuse-aarch64: 300 passed, 0 failed, 10 skipped (six need a sysroot lane, four lack a fixture).test-sigioandtest-listen-backlogpass in it.Summary by cubic
Fixes #402 by aligning listener queue depth with Linux and avoiding a macOS 27.0.1 bug that loses the urgent mark on connections accepted from a backlog-1 listener.
sys_listenpasses backlog + 1 to the host for every backlog from 0 up, solisten(fd, 0)queues 1 andlisten(fd, 4)queues 5 onAF_UNIX, matching Linux; negative backlogs go to the host, which reads them assomaxconn.AF_INETandAF_INET6because macOS 27.0.1 loses the urgent mark of the first urgent byte on about half of connections accepted from a backlog-1 listener (no SIGURG andrecv(MSG_OOB)fails), so a TCPlisten(fd, 0)queues 2 instead of 1.tests/test-sigio.cfailed 19 of 40 runs before and 0 of 200 with the fix.test-listen-backlog, which uses a per-runmkdtempsocket directory to avoid concurrent-run conflicts; itsAF_UNIXcases check the exact depth basket while its TCP cases check only that backlog + 1 fit because macOS TCP sometimes over-queues.Written for commit 1f569c6. Summary will update on new commits.