cmux-tui: each terminal gets a fair share of the terminal connection lane (on top of #27719's 48-of-64 cap)
Fixes #27698 (the per-terminal part)
Summary
Rescoped (lead decision 2026-11-01 05:20Z: "build on it"). #27719 (fbb9e39343e) is on feat-cmux-next. It caps connections from processes inside the session's terminals at 48 of the daemon's 64, so 16 stay for the app. This PR keeps that cap and adds only a fair share per terminal inside the terminal lane. Before this, one terminal (an agent's hooks in a loop, a script running cmux in a pane) could take all 48 terminal connections, so every other terminal's agents were refused server.busy.
- A client is the terminal the peer runs in: its process session (
getsid), which #27719's accept loop already reads (TerminalSessions::terminal_of_peer, the same match ascontains_peer). A terminal's shell, the agent in it, its hooks and acmuxrun in it count as one client. - A terminal's first connection gets in whenever the lane has room. A further one gets in only while that terminal then holds no more than the lane has left free. One terminal alone stops at 24 of the 48, so a second terminal still finds room. Several terminals in a loop together can still fill the lane (about 7); the 16 app slots of #27719 stay theirs either way.
- The per-terminal count and the lane count change together under one lock (
ConnectionStats::claim_terminal_slot), so two claims never both take the last place a share allows. A hung-up client's reclaim (#27669) gives back its terminal's share too: the lease records its terminal. - A peer with no readable pid is not found in a terminal, so it is not in the terminal lane at all (as with #27719). The share applies only to connections #27719 already puts in the lane.
Out of this PR now (the earlier design, see the history): the app's own socket, the RLIMIT_NOFILE budget, and the removal of the 2 s write stall. #27719 covers the app lockout. The others can come back as their own PRs if the A/B data asks for them.
History: 21646a45f20 merges feat (conflicts take feat's side). 6d91a64778a reverts this branch's earlier design to feat. Then the three commits below are the change (red test, fix, test and doc follow-up from the review).
Changelog
Fixed: one terminal opening many connections to the session daemon no longer takes every terminal connection; other terminals' agents still connect.
Testing
server/tests/outbound_and_scheduler.rs::one_terminal_cannot_take_the_terminal_lane_from_the_others: terminal 101 claims connections in a loop; it must stop at most at half the lane, terminals 102 and 103 must each get one, and the app's slots must be untouched.
- Red at 66808c66545 (the client id threaded through with no behavior change, plus the test): fleet step 74e90a163f9834b0cc464cf0 (
cmux-tui-rust-check.sh test one_terminal_cannot_take_the_terminal_lane, macOS). - Green at 20f7f9c17fd (the test also claims again after the release and must reach the same count, so a leaked share fails it): results in a comment. At 8fc1e8aba71: compile 4fb9c091cd1a5ccb9eb2d9a6 passed;
test server::c74aa48508c5 and clippy b7e7a3f2c43e in a comment.
Observed / Not run
- Observed: the fleet steps above (results in a comment on this PR when they end).
- Not run: an Austin's-day A/B of this narrower change. The earlier A/B (comment above) measured the app-socket design.
No capture: daemon change, no UI.
Owner: PL-daemon (session cmux-b6) owns cmux-tui-core.