Repository navigation
fix(cmux-tui): the reap grace holds at most 16 closed terminals; past it the longest-closed ends at once (STATUS 7998) - #33117
Conversation
…(red, STATUS 7998)
… it the longest-closed ends at once (STATUS 7998) Each closed tab's terminal waited out the whole reap grace (30 s in the app) with its shell, and a shell cannot outlive its PTY (4 /dev/ptmx handles a terminal: 3 in its host, 1 daemon custody copy). A script closing about 5 tabs a second (the L6 storm) held about 150 PTYs at once on top of the live ones and reached the machine's 511 in 48 s, so every new tab was refused. The reaper now ends, on the same pass that sees them, the longest-closed terminals past MAX_TERMINALS_IN_REAP_GRACE (16): a recent close stays undoable with its live shell; an older one in a burst is reaped as at the end of its grace (screen archived, never a kept or attached terminal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oses and reaps (STATUS 7998) A create after a reap trips the scoped-projection cross-check on the base too (diag step 0ff771ada37a: a normal reap at the grace end, then new_workspace, panics the same way); that is a separate defect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugbot needs on-demand usage enabledBugbot uses usage-based billing for this team and requires on-demand usage to be enabled. A team admin can enable on-demand usage in the Cursor dashboard. |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
red: cmux-next checks (god files, concurrency, crash safety, l10n); no finished base run on 1382711 to compare; do not merge until green; see lane-rules Shipped in:
|
|
Found 6 test failures on Blacksmith runners: Failures
|
|
Review (subagent, correctness first): SHIP at 99a11c6 I read the code only. I did not build or run tests. 1. Which terminals the cap can end.
2. Attached terminals. No busy loop. An 3. Bookkeeping. Every terminal past the cap is in 4. Ordering. The sort is 5. Test. The test is meaningful. In the loop, the pass at the 17th and 18th close ends t0 and t1. Without the cap logic nothing is reaped ( Non-blocking notes:
|
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
Refs STATUS 7998 (VIDEO EXTRA: 50 tabs closed at once kept all 208
/dev/ptmxhandles 10 s later and released them by 60 s; at the L6 storm's close rate, closed-but-held PTYs reach the machine's 511 in about 48 s).Cause
What keeps a closed terminal's host process and its PTYs alive 10-60 s after the close is the reap grace (
mux/terminal_reap.rs; the app starts the daemon with--terminal-reap-grace-seconds 30,DaemonLauncher.terminalReapGraceSeconds). A closed tab's terminal keeps its shell for the grace so the close can be undone with the live shell. A shell cannot outlive its PTY, so "keep the process but release its PTY" is not possible: the PTY goes when the shell ends. Measured on the fleet (#32838's diag stepb01dd8303d952fd412d5199f): each terminal holds 4 handles (3 in its host, 1 daemon custody copy), all for one PTY, and all come back when the grace ends. #32838 (merged) ends those terminals only when free PTYs fall under 64, which a storm passes faster than a reclaim pass.Fix
The reap grace holds at most
MAX_TERMINALS_IN_REAP_GRACE(16) closed terminals. On each reaper pass (a topology change wakes it), the closed terminals past the cap, longest closed first, are reaped at once through the reaper's own guarded close (screen archived as at any reap; never a kept terminal or one a client is attached to). A recent close stays undoable with its live shell. At the L6 storm's rate (about 5 closes a second) the closed-but-held PTYs stay at 16 instead of about 150.Tests
mux::terminal_reap::tests::closed_terminals_past_the_grace_cap_end_at_once(18 workspaces, closed in a burst inside the grace, reaper after each close): the 2 longest-closed end, the 16 recent stay running, a placed terminal stays.ab09b157bbb11cd3ebfc7dd6,left: [](nothing reaped inside the grace).terminal_reap15/15 (6e61198b30d403d5cd9eb30e),mux::561/561 (ec1ad3cf9f845906cccd7371; the firstmux::step got no Blacksmith run). At a1d0156,pty_re10/10 (82f02d601d92727c5028bbcf) and fmt passed (f05cb8eb6fabb67d2a95f5c9). The cap test there failed on a base defect (a create right after any reap trips the projection cross-check, cmux-tui: after a terminal reap, the journal states other closed-but-running terminals as absent (scoped projection cross-check) #33059, repro step0ff771ada37aaf789ff0a381), so the test now makes every workspace before it closes and reaps.acpmux/src/hub/additional_dirs.rs:99(step9ecd84819970ac4e5443dc59on feat 1114b77), fixed by acpmux: fix the base clippy red in additional_dirs' test (cloned_ref_to_slice_refs) #33064; not this PR.Observed / Not run
Observed: the red step and the per-process PTY counts in #32838.
Not run: the L6 storm journey again (needs VIDEO EXTRA's kit); a GUI run.
Decision
The cap value, 16: lead decision (a script burst is the case that hit the PTY limit; 16 undoable live closes is enough for a person). A close past it is reopened from its saved screen with a new shell.
Changelog
Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes PTY exhaustion when a script or agent closes many tabs in a burst: the reap grace now holds at most 16 closed terminals instead of letting every closed tab keep its shell (and so its PTY) for the full grace. Past the cap, the longest-closed terminals are reaped at once, with the screen archived as at any reap; a recent close stays undoable with its live shell.
Written for commit 99a11c6. Summary will update on new commits.