fix(cmux-tui): a respawn wave sizes the Kitty budget before its first respawn reserves

Merged
LA
5d ago
+395-34

What

After a kill of everything, the respawns of a start reserved their Kitty image shares one after the other. The first respawn took the whole Kitty budget (it was alone). Each later respawn then waited until that first launch finished and its share shrank. On the fleet (macOS), 11 of 12 respawns waited 34-39 ms in spawn_prelude (trace 655f57b6a2f): the full launch time of the first. Where a launch never finishes, each one waited 500 ms (KITTY_RESERVATION_WAIT) and started with graphics off. That drops a restored screen's Kitty images for good.

The fix:

  • The start sweep (respawn_lost_terminals_at_start) counts its lost terminals before it plans the first respawn: Mux::expect_kitty_image_wave.
  • Each respawn that starts at once names the slot it will reserve with: Mux::expect_kitty_image_surface. The reservation takes that expectation over in the same budget lock.
  • The bucket is sized for owners + expected (KittyImageBudgetState::sized_for), in the reservation and in rebalance. The first respawn gets the wave's final share, and the next ones fit at once.
  • A respawn of a host lost while the session runs (Mux::expect_kitty_image_respawn(old_runtime, slot)) removes the dead runtime's entry and adds the expectation in one budget step. Kept, the terminal counted twice and the hysteresis kept the larger bucket after it (review finding); in two steps, the worker could grow the other terminals in between only to shrink them again.
  • Nothing starts with graphics off, so a restored screen keeps its Kitty images.
  • What no respawn takes (a respawn given up before it reserves, or a wave that ends) is given back when its guard drops. Running terminals then grow back, and a later new tab gets the share of the terminals that exist.
  • The process budget still holds: a reservation is admitted only when every terminal fits its target.

Root cause and trace: STATUS 6194. Design and the lead's approval: STATUS 6221.

Not changed: a split or new tab (Cmd-D) takes its quota after commit (KittyQuota::AfterCommit, prelaunch_hosted), so it never waited on this. Measured below.

Tests (red first)

cmux-ci run --class isolated --script scripts/ci/cmux-tui-rust-check.sh --os macos --arg test --arg kitty_reservation

  • Red at 6f9802867b5 and 543611dde7e (expectation API as a no-op stub):
    • a_respawn_wave_reserves_its_kitty_shares_without_waiting_for_a_launch_in_flight: 11 reservations waited 501-510 ms each.
    • a_wave_given_up_halfway_gives_back_the_kitty_shares_it_never_used
    • a_dropped_kitty_expectation_gives_its_share_back
    • a_start_wave_is_sized_before_its_first_respawn_and_gives_back_the_unplanned
  • Red at 8bb759429f8 (review finding, stub keeps the old share): a_runtime_respawn_does_not_count_its_terminal_twice: capacity 4, expected 2.
  • Green (macOS): kitty 74/74 at 925b21da892; host_death 81/81 and terminal_respawn 40/40 at 1fe985c5e00 (925b21da892 only renames the entry point). Linux kitty passed at 1fe985c5e00.
  • Linux clippy -D warnings at 925b21da892: the only errors are base reds in files this PR does not touch (crates/cmux-tui/src/link/mod.rs:515 too_many_arguments, crates/cmux-tui/src/cli/app/tests.rs:352 unnecessary qualification).

Restore time after a kill of everything (12 terminals, macOS, one worker aws-m4pro-7, interleaved)

kill_all_restore_time_of_12_terminals (ignored measurement), scratch branches cp/reopen-f3-measure-kitty-base / -fix:

runbase 1ecf8a89abe: last terminal (ms)fix 64173f7c31e: last terminal (ms)
1481305
2472177
3417197

The visible terminal is about the same (base 233, 173, 174; fix 225, 174, 189): it reserves first and never waited. The claim of this PR is the last terminal: the 11 later respawns no longer wait for the first launch. (The test polls each terminal in turn with 50 ms sleeps, so single values are quantized.)

Cmd-D x4 (scratch measurement, one run each, macOS fleet)

4 splits one after the other (ms)4 splits at once, each answer (ms)
base 1ecf8a89abe69.8, 18.7, 15.1, 19.644, 73, 85, 97
fix75.8, 12.5, 12.9, 13.517, 63, 52, 41

No split waited on a Kitty reservation in either build. The difference is one run each and is not a claim of this PR. Splits sent at once answer one after the other (about 12-20 ms each). That is a separate cost, not the Kitty wait.

Risk

risk:normal: Kitty image budget sizing and the respawn start path. No auth, socket policy, migration or data delete.

Observed / Not run

  • Observed: the red and green fleet runs above. The trace that found the wait (STATUS 6194).
  • Not run: a 24-terminal, 4-slot run with this fix (the 12-terminal A/B above is the evidence).

Changelog

none