fix(cmux-next): a refused write ends the daemon connection (follow-up of #28654)
Summary
This is a follow-up of #28654 (merged). Since #28654, the heartbeat no longer closes a live daemon's connection. That opened a gap:
- When the writer refused a write (its backlog limit, because the daemon stopped reading its socket), the transport only failed the pending requests.
- The socket stayed open, the reader stayed in
read, and noonClosecame.DaemonConnectionkept the dead transport as "ready", so every later request failed at once. - No real signal ended the connection: the writer's refusal was the signal, and nothing acted on it.
Now:
- A refused write goes through
end(), asclose()does: the pending requests fail, the socket shuts down and a bridge child ends. The reader then reads EOF and postsonClose, and the owner reconnects. - A closed transport is never a live peer (
peerProcessIsAlivechecksisClosed). This only matters for a transport that closes while a heartbeat ask is out: the heartbeat already stops at a closed transport (tickreturns at once), so the real fix is theend()above.
feat's heartbeat asks every interval, not after a silence. An answered ask already marks the daemon answering again, so the perf port's "a frame after a miss" rule does not apply here.
Found by the independent review of the perf port of #28654 (finding 2).
Takes back
Not applicable: this is a fix-forward follow-up of #28654. A revert of #28654 would bring back the "closed by client" P0 ("I can't even make new workspaces").
Testing
Focused command (fleet, CmuxNext package test lane), python3 /work/wt/chrome-parity-design/proof-step.py:
- Red on the base:
red --branch cp/fix-p0-refused-write-feat --ref 9fef9a6729f944264468cae63325eb6b480bcb95 --suite RefusedWriteEndsConnectionTests -- --red. Job 2f1a63e631a530988520a803: failed on the test's own assertions (2 issues, no crash):RefusedWriteEndsConnectionTests.swift:31:9: Expectation failed: (ended.count → 0) == 1("a refused write left the connection open: no onClose ([])")RefusedWriteEndsConnectionTests.swift:32:9: Expectation failed: !((transport → CmuxNextDaemon.LineTransport).peerProcessIsAlive → true → true)("a closed transport must not report its peer alive")
- Green on the head:
green --branch cp/fix-p0-refused-write-feat --ref dc5125f97747d790c64a32084c2be8718841c92a --suite RefusedWriteEndsConnectionTests,HeartbeatLiveDaemonTests,BusyRefusalReconnectTests. Job 1e4c7b7e07deca9de9a170df: passed: RefusedWriteEndsConnectionTests (13 s), HeartbeatLiveDaemonTests (3 s), BusyRefusalReconnectTests (2 s); "All 3 suites passed", no FLAKY notice.
Observed / Not run
- Observed:
prepush-gate.pypassed (no new violations). - Observed: compile-check 8eae4ba85b106e5ac13e65ff of the head passed (CmuxNext, app and cmux-tui receipts ok:true). Independent review: SHIP at dc5125f9774 (https://github.com/manaflow-ai/cmux/pull/28849#issuecomment-6538561086).
- Observed: red 2f1a63e6 failed and green 1e4c7b7e passed, as above.
- Not run: a live daemon that stops reading its socket under real load (only the fake daemon in the test).
Changelog
Fixed: cmux reconnects to cmux-tui when cmux-tui stops reading its socket, instead of keeping a dead connection.
No capture: no UI change.