cmux-host: bound the Cloud agent's event queue, identify read and activity line

Merged
TE
5d ago
+176-17

What

Three bounds on the Cloud agent in cmux-host, from the crash audit (manaflow-ai/cmuxterm-hq#5351, items 2 and 3):

  • Event queue: EventQueue holds at most 256 events (EVENT_QUEUE_MAX; each is already capped at 4 KB, so about 1 MB). Past that, emit refuses new events and the session logs "socket line refused: event queue full". The events it already holds keep their order, and the in-flight head is never dropped. Before, the queue grew without bound while it backed off (up to 600 s) and nothing drained it.
  • query_identify: one deadline now covers the whole exchange, where before each read had its own timeout and a byte every 1.5 s beat the 2 s timeout forever. A reply line over 64 KB (MAX_LINE) is refused, even when its newline arrives in the read that crosses the cap. The write gets the time left.
  • Activity stream: an unfinished line over 64 KB counts as a dropped stream (it reconnects with backoff). Before, the buffer kept growing and was rescanned on every read. The check lives in read_lines, which the agent socket's clients now share; they already closed on the same condition. On a read error, the complete lines already buffered are still handled, as the activity path did before.

Audit text: "role.rs:210-217 query_identify has no take() or overall deadline (a byte every 1.5 s beats the 2 s read timeout forever); role.rs:783-786 read_activity ignores read_available's over-MAX_LINE signal", and "Cloud agent event queue is unbounded ... Fix: cap the queue (256 events plus a byte budget), per-client idle deadline."

Not in this PR: the per-client idle deadline on the agent socket. Sixteen idle clients can still hold every slot, and a token asker gets "busy". UnixStream::connect in query_identify also stays unbounded, since std has no Unix connect timeout and the audit item was the read drip. All of these are same-user or group cmux only.

Part of manaflow-ai/cmuxterm-hq#5351 (items 2 and 3).

Tests

The tests went in first:

  • event_queue_tests::a_backed_off_queue_refuses_events_past_its_cap
  • role_tests::identify_from_a_daemon_that_drips_bytes_ends_at_its_deadline (a byte every 100 ms against a 300 ms timeout)
  • role_tests::identify_stops_reading_a_line_over_max_line (1 MB with no newline)
  • role_tests::identify_refuses_a_whole_reply_over_max_line (added with the fix; it covers the boundary case review found)
  • role_tests::a_line_over_max_line_ends_the_stream (added with the fix, since read_lines is new)

Results (Linux fast checks, cargo test -p cmux-host --lib -- cloud::):

  • Red at the test-only commit 7013b6534 (run 41159399704): the three tests fail (identify outlived its deadline: Timeout, the 1 MB line read past MAX_LINE, the queue grew past 256).
  • Green at beee6350b (run 41160120623): test result: ok. 27 passed; 0 failed. cargo clippy -p cmux-host --all-targets -- -D warnings printed nothing.

Review: an independent Codex (gpt-6-astra, high) review blocked 84006ba0d. A line whose newline arrived in the read that crossed MAX_LINE got past the cap; the write had the full timeout; a read error dropped buffered lines; and a timing assertion was tight. beee6350b fixes all four. The delta review returned SHIP at beee6350b5c453d3250d430bca0a60ac5e7b88fe.

Risk

Low. A healthy daemon's identify reply is a few hundred bytes and arrives at once. A real event burst over 256 while the agent is backed off now loses the newest events, with a log line, instead of growing memory. No UI change.

Agent session: agent session

šŸ¤– Generated with Claude Code