fix(cmux-tui-core): a key skips the creating-input lock while nothing is creating; the lock becomes a leaf (R-DAEMON-STATE S1 step 4)

Merged
LA
2d ago
+184-32

Summary

R-DAEMON-STATE stage S1 step 4 (handoff/rewrites/R-DAEMON-STATE/DESIGN.md, section 3 and Appendix A.2 step 16). Base feat-cmux-next (S1.2, #34171, and S1.3, #34146, have landed). Behind the same per-mux switch CMUX_TUI_RDS_INPUT (default off).

Every key to a live terminal first asks whether creating input of its terminal must go first (wait_for_creating_input_flush, terminal-input-while-creating-v1). It took the daemon-wide creating-input lock even when no terminal was being created, and that lock's holders (begin_creating_input, knows_terminal_input_target, finish_creating_input) looked up the live terminal under it, which takes the session lock. So a key waited for any creation in progress and, behind it, for any holder of the session lock. The S1 census measured a full wait on the creating-input lock (300.3 ms, hosted run 41648095321).

With the switch on:

  • CreatingInputs keeps the number of creating terminals in an atomic, stored under its lock after every insert and removal (Release). A key reads it (Acquire) and returns at once when it is zero. No queue can be ahead of it then: a terminal's queue is added before the terminal goes live, and removed only after every queued byte was written to it.
  • The holders of the creating-input lock look up the live terminal in the published routes, not under the session lock. No holder waits for the session lock while it holds the creating-input lock, so a key that takes that lock (while another terminal is being created) never waits behind one. The routes carry the host-id catalog index for this: terminal_catalog_by_host becomes a copy-on-write route table in the RouteSnapshot, and one lookup (route_table::live_terminal) serves the session state and the snapshot.
  • Why the creation checks keep their answers: a commit publishes its routes before it releases the session lock, and finish_creating_input runs after the commit (the old path could not call it under the session lock: it would deadlock), so begin, knows and finish see what they saw under the session lock.

With the switch off, every path takes the locks it took on the base. The by-host index is copy-on-write on both arms, as the other three route tables have been since step 1.

Testing

a_key_waits_for_no_creating_input_lock (cmux-tui/crates/cmux-tui-core/src/server/tests/rds_input_lane.rs): two keys through the real line path while another thread holds (1) the creating-input lock, (2) the session lock while a reader waits for it under the creating-input lock, with another terminal being created so the key cannot skip that lock. Each holder has an old-path control (switch off: the key must wait).

Focused command: ./scripts/verify-cmux-tui-hosted.sh --filter rds_input_lane.

Compile: fleet job 862158b530775c4020d371bd at 8a502885dc1 passed (CmuxNext 282 s, app 94 s, cmux-tui 61 s).

Observed / Not run

  • Observed: the hosted runs and the fleet compile above.
  • Not run: the full cmux-tui suite on this head (with the stack, after the merge with feat); macOS execution of the focused test; the real-app A/B (after S1 lands).

Review follow-ups (head 8ee8220c728)

The review (comment 6613998735, SHIP at 8a502885dc1) left notes; taken in 3b2e29f1389:

  • The begin_creating_input comment now states the lane's linearization (it said the check takes the session state under the creating lock, which is the old path only).
  • The lane's live-terminal lookup takes the old read on a poisoned session state, as Mux::surface does since #34171's bb0265f43d5.
  • The stack's unused_qualifications red at mux/tests/signaled_mutex.rs:120 is fixed in #34171 (10f16693a03), merged here.
    Green at 8ee8220c728: hosted run https://github.com/manaflow-ai/cmux/actions/runs/41713709790, Linux rds_input_lane 6 of 6 passed.

Changelog

none

Proof

No capture: daemon-only change behind a default-off switch, no UI.