acpmux: close WS admission even while a spawned child holds the listener (1012 test flake)

Merged
TE
8d ago
+20-1

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_admission calls shutdown(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)

runtreeresult
10x full cargo test -p acpmux --libbase 400cb08this test failed 1/10 (run)
25x full suite, diagnosticsbase 400cb08failed 2/25, diagnostics above (run)
5x this test, held duplicate, production unchangedbase + test changefailed 5/5 at the same assertion (run)
5x this testthis headpassed 5/5 (run)
40x full cargo test -p acpmux --libthis headthis test passed 40/40; the other reds were the flakes listed below (run)
cargo clippy -p acpmux --all-targets -- -D warningsthis headclean (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