fix(cmux-tui-core): no frame passes a key the input port holds; only the person's keys answer before the write (R-DAEMON-STATE S1.6b)
Summary
R-DAEMON-STATE stage S1 step 6b (handoff/rewrites/R-DAEMON-STATE/DESIGN.md, section 3, S1.6b). Base feat-cmux-next (the S1 stack below it has landed: #34171, #34174, #34261, #34544). Behind the same per-mux switch CMUX_TUI_RDS_INPUT (default off). It closes the two gaps S1.6a left before the default can turn on (S1.7).
1. No other frame passes a key the input port holds. With S1.6a a key to a hosted terminal goes to the terminal's input port and the request returns; the connection's other frames (viewer size, cell pixels, receipted input, clear history, Kitty limits, the clipboard reply, terminate) still wrote directly, so while the port was behind, such a frame could reach the host before an earlier key (a resize the key caused, before the key; a clear history before queued input).
- The port counts the frames it took (
accepted) and finished (finished). EachHostAttachmentgets the terminal'sPortOrder(set by the hosted constructor andPtyTerminalRuntime::replace_runtime, so it follows every connection change), and every frame it writes besides the port's own first waits until the port finished every frame it had taken (PortOrder::barrier, at most 2 s, as one blocked frame write). Frames taken later do not extend the wait. - The wait returns at once while the port's writer waits for a reconnect (the install it waits for may need the runtime lock that the waiting caller holds; the direct frame then fails on the dead connection, as today). Each route publication (
replace_runtime,input_route_failed) ends that wait at once (InputPort::route_changed, a port-owned route epoch), so after a replacement installs, the port writes the keys it holds to it before any other frame (review 1). The wait also ends by the caller's own deadline (control requests, cell pixels). The clipboard replier waits the same way. - With the switch off the port takes nothing, and the wait is one uncontended lock.
2. Only the person's keys answer before the write. S1.6a answered every hosted write when the port took it, so the callers that read the write's result never saw an error: a script's cmux send exited 0 for bytes never written, the creating-input flush never gave bytes back, the resource input effect was never marked indeterminate, and the image paste released its reservation before the paste was out.
Surface::write_bytesandwrite_pastenow answer when the port wrote the frame, with its error (InputReply::Written; the port reports each frame's result on a one-slot channel; the caller waits at most 15 s).- Only the person's key answers when the port took it (
InputReply::Accepted): thesendandsend-keyhandlers pass it when the client is attached to the terminal it types into (cmd_terminal_io::input_surface, the publishedtypes_intofact). So the person's keys never wait, and automation waits behind them, never the reverse. - Each frame waits for a reconnect until its own deadline,
HOST_INPUT_RECONNECT_WAITafter the port took it, as the old path's write waited from its call: frames queued behind a host that does not come back fail together, and a later loss gives its writes their own wait (review 1). - A
Writtenwait that ends before the port's writer took its frame cancels the frame (the error says not written, and it never is). One that ends during the write answersInputMayBeWritten, and the creating-input flush does not give those bytes back (review 1). - The
terminal-input-unclaimedgive-back of the plan is dropped (DESIGN 2.6.1): the app takes that notice only on its control connection, while keys come on each view's own connection, and the app never retried a failed key (the old path only logged it).
Testing
Focused command: ./scripts/verify-cmux-tui-hosted.sh --filter hosted (Linux test (linux)).
- Review 1 (FIX at 3a0af4bea58), red at 2133c10ae2f (
test/rds-s16b-r1-red-never-merge), run https://github.com/manaflow-ai/cmux/actions/runs/41805607059: 37 passed, 5 failed:a_frame_sent_after_a_replacement_install_waits_for_the_keys_the_port_held(only the direct frame reached the new host; the held key was lost at the old deadline),a_later_host_loss_waits_for_its_own_reconnect(the write failed at once: broken pipe),a_write_whose_wait_ends_before_the_port_takes_it_is_never_written(the timed-out frame was written later),a_write_whose_wait_ends_during_its_write_may_be_written(no may-be-written mark),the_creating_input_flush_keeps_input_that_may_have_been_written(the flush gave the bytes back). The guards passed:a_frame_sent_while_the_port_waits_for_a_reconnect_does_not_wait_for_it,writes_queued_behind_a_lost_host_fail_together,receipted_input_sent_after_a_key_the_port_holds_reaches_the_host_after_it. Green at bbc44c24ae4: run https://github.com/manaflow-ai/cmux/actions/runs/41806402684 (--filter hosted): 68 passed, 0 failed; run https://github.com/manaflow-ai/cmux/actions/runs/41806407494 (--filter creating_input): 13 passed, 0 failed. Then ee8896f2f14 merged feat (own-diff patch-id unchanged, dcf17daefdf6), and f9c75f1f7a7 removes four unnecessary qualifications in the new tests:lint (linux)at bbc44c24ae4 failed clippyunused_qualificationson them. A--filter hostedrun at f9c75f1f7a7 is https://github.com/manaflow-ai/cmux/actions/runs/41808538122. - Part 1, red at 8b9f34a9be8 (
test/rds-s16b-red-never-merge), hosted run https://github.com/manaflow-ai/cmux/actions/runs/41785324344:a_frame_sent_after_a_key_the_port_holds_reaches_the_host_after_itFAILED (a direct frame passed the key the paused port held; a test-only pause of the port's writer makes it deterministic). Green at 91af73e2666, run https://github.com/manaflow-ai/cmux/actions/runs/41785327211: 32hostedtests passed. - Part 2, red at 189ba5886e5 (
test/rds-s16b2-red-never-merge), run https://github.com/manaflow-ai/cmux/actions/runs/41789122770:an_automation_write_answers_after_the_port_wrote_itFAILED ("an automation write answered before the port wrote it") andan_automation_write_gets_the_error_of_its_frameFAILED ("an automation write the port could not write answered ok: ()"); 32 passed, 2 failed. Green at 9afa30090da, run https://github.com/manaflow-ai/cmux/actions/runs/41789172931 (--filter hosted): 60 tests passed, 0 failed, the 6 port tests among them. - Run https://github.com/manaflow-ai/cmux/actions/runs/41789125612 (
--filter input, 9afa30090da): 110 passed, 2 failed:server::tests::input_lane::a_tab_created_or_closed_elsewhere_keeps_the_key_off_the_session_lockanda_mutation_that_routes_nothing_keeps_the_key_off_the_session_lock. Both are switch-off tests, and this PR does not change their path (with the switch off,enqueue_on_input_portreturns before the port). A focused run puts every matching test in one process, and the route generations are process-wide slots (route_table.rs: each test needs its own process). Another test's surface with the same id moves the slot, so the kept terminal is looked up again under the session lock. Each test alone at 9afa30090da passes: runs https://github.com/manaflow-ai/cmux/actions/runs/41793133820 and https://github.com/manaflow-ai/cmux/actions/runs/41793137368. Full mode runs each cmux-tui-core test in its own process. server::tests::rds_input_lane::the_persons_keys_answer_when_taken_and_other_writes_when_writtenchecks the reply decision (attached view:Accepted; another client:Written).
Compile: fleet job c54f4109061f5034d3d37607 at bbc44c24ae4: CmuxNext, app and cmux-tui passed (earlier: job fe0b3807b0b401ce8479adeb at 9afa30090da, 8fb4caa7859635bf8d66fd39 at 3a0af4bea58: passed).
Observed / Not run
- Observed: the hosted runs and the fleet compile above. The S1 typing A/B build 2 (S1.1-S1.6a, before this change): the daemon part of a key with the switch on was p99 1.56-2.29 ms, max 3.50-3.75 ms; with it off, max 11.2-14.1 ms (STATUS 11230).
lint (linux)is red in both runs on base findings, not on files of this PR: clippyredundant_cloneincmux-git/tests/sync_barrier.rs, and the panic ratchet oncmux-tuiandcmux-vault-egress.- Not run: the typing A/B rounds. Build ddcaa13c6cf3dddb49590e94 was a plain Release build (its tag did not match
release_bench_tags, so it had nodebug.*methods; both rounds NOT RUN, STATUS 11551). The bench build of f9c75f1f7a7 is fleet job df3f41c4e9e942c9d4190629 (tag chrome-parity-rds-s1-ab3-f9c75f1f7a7-v3). Also not run: macOS execution of the focused tests. - Deferred to S1.6c (DESIGN section 3): review 1 items 3 and 4. The 2 s order wait keeps order only while the port drains within it, and a key that moves the grid waits in the barrier while it holds the sizing lock. The fix puts the order-sensitive control frames on the port.
Changelog
none
Proof
No capture: daemon-only change behind a default-off switch, no UI.
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes two ordering gaps in the input lane (CMUX_TUI_RDS_INPUT, default off) so the design stage can proceed to default-on.
- Every frame a
HostAttachmentwrites besides the port's own (viewer size, cell pixels, receipted input, clear history, Kitty limits, clipboard reply, terminate) now waits for the port to finish the keys it took first. The wait is at most 2 s and returns immediately while the port's writer waits for a reconnect. - Automation writes (a script's
send, the creating-input flush, resource input effects, image pastes) now answer when the port actually wrote the frame, with its error, instead of as soon as the port took it. Only the person's key (a client attached to the terminal it types into) answers on accept. - Each frame waits for a reconnect from when the port took it, and a route publication ends the wait at once, so frames queued behind a dead host fail together and keys the port holds reach a live replacement before any direct frame. A write whose wait ends first is cancelled, or marked possibly written when its frame is mid-write, so the flush does not give back bytes it may still type.