acpmux: close WS admission even while a spawned child holds the listener (1012 test flake)
Flake fix for a red seen on base: server::ws_shutdown::tests::restart_drains_peer_web_and_redeeming_ticket_clients_with_1012 (from #27214) failed at tests.rs:83 with "admission closed before 1012" in about 1 of 12 full cargo test -p acpmux --lib runs. Fixing a base red; never blocked.
The race
serve_ws_until drops its listener and only then sends Close 1012, so the test's post-1012 connect should be refused. But a process spawned at that moment by any other task (another test in the same binary; in the daemon, an agent, git or login-shell spawn) holds a copy of the listening socket from its clone until its exec. Linux keeps the socket listening, and completes handshakes into its backlog, until the last copy closes. So the connect succeeded, and the connection was reset once the child exec'd.
Diagnostics from a failing run at base (instrumented copy, not committed): no process held the listener's socket inode any more when the test looked, /proc/net/tcp had no LISTEN entry for the port, and the test's own connection had vanished with no FIN_WAIT/TIME_WAIT trace, i.e. it was reset. That rules out another test reusing the freed port (it would have accepted and closed with a FIN) and TCP self-connect, and matches a listener that a short-lived child's copy kept open.
It is a real, if small, production gap too: the comment above that drop promises an immediate retry cannot reconnect to the dying listener, and on Linux a concurrent spawn breaks that promise.
The fix
ws_shutdown::close_admissioncallsshutdown(SHUT_RD)on the listener before dropping it. On Linux that stops the listening socket itself (tcp_disconnect->inet_csk_listen_stop), whatever copies remain, so admission closes at once. Other systems refuse shutdown on a listening socket and keep today's drop.- The test, on Linux, now holds a duplicate of the listener for its whole run, the same thing an in-flight child does. The race is now taken on every run, and the assertions are unchanged: web, peer and redeeming-ticket clients each get 1012, and a connect after each is refused.
Evidence (Linux, cmux-ci test, 32 vCPU)
| run | tree | result |
|---|---|---|
10x full cargo test -p acpmux --lib | base 400cb08 | this test failed 1/10 (run) |
| 25x full suite, diagnostics | base 400cb08 | failed 2/25, diagnostics above (run) |
| 5x this test, held duplicate, production unchanged | base + test change | failed 5/5 at the same assertion (run) |
| 5x this test | this head | passed 5/5 (run) |
40x full cargo test -p acpmux --lib | this head | this test passed 40/40; the other reds were the flakes listed below (run) |
cargo clippy -p acpmux --all-targets -- -D warnings | this head | clean (same run) |
Other flakes seen in those runs, at base and head alike, and not touched here: wireguard::tests::loopback_throughput_and_shutdown (9 of the 40 head runs), session_scope::tests::a_session_start_never_waits_for_another_to_set_the_slice, cli::harness_login::tests::codex_bridge_status_*, cli::send_relay::tests::the_relay_gets_the_session_the_text_and_the_sender_and_its_status_is_ours.
Risk: low. One syscall on the restart path, before the drop that already happened there; Linux only.
Agent session: agent session
š¤ Generated with Claude Code