fix(tui): stop talking to the terminal when nobody is using it - #1
Merged
Conversation
Terminal.Gui runs its main loop 25 times a second whether or not anything has changed, and rewrites cursor state every time round. Measured on an idle TUI with nothing happening at all: ~315 bytes/second of escape sequences, in 25 separate writes per second, for as long as the tool is open. On a local terminal that is invisible. Over SSH it is 25 packets a second the far end can never stop servicing, and a link that is congested never gets a quiet moment to catch up. The loop now steps down when the tool is left alone - 10/s after ten seconds untouched, 4/s after a minute - and the first key or mouse event puts it straight back to the library's own rate. Two stages rather than one keeps the cost off the case that would be noticed: a pause to think slows the waking key by ~45ms, and only walking away costs the full quarter second. Measured behind a pty that answers the driver's queries like a real terminal: typing normally 29-49ms -> 29-49ms (10 keypresses, unchanged) idle after 20s 315 B/s -> 128 B/s idle after 70s 315 B/s -> 54 B/s waking keypress 25ms -> 60-248ms (4 samples: 248, 60, 235, 70) This is a candidate fix for "the UI goes laggy after a few minutes", not a confirmed one, and it is worth being straight about which. The lag does not reproduce locally: twelve minutes idle held keypress latency flat at 44-73ms, CPU at 2.1% and file handles constant, and 150 open-and-close cycles of the channel editor held latency flat at ~60ms with no handle growth either. The constant output is what the tool was doing wrong regardless of whether it turns out to be the cause. Terminal.Gui 2.4.18-develop.31 behaves identically on the same measurement, so there is nothing to pick up from a version bump. The policy is a pure function of how long it is since the last input, so it is unit tested directly: rates, the two thresholds, that it is monotonic, and that it never speeds the loop up past what the library asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkDFej82QnbjMJYFcwYyAZ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The one item from your list on the 21st that did not make v0.6.0: "the UI seems to have gone laggy after a few minutes". The previous session could not reproduce it in a 90-second check and asked which terminal you were on; that question died with the session, so this picks it up from the measurements instead.
What the tool was doing wrong
Terminal.Gui runs its main loop 25 times a second whether or not anything has changed, and rewrites cursor state every time round. Sitting there with nothing happening at all, the TUI was emitting ~315 bytes a second, in 25 separate writes per second, for as long as it was open. Locally that is invisible. Over SSH it is 25 packets a second the far end can never stop servicing, and a link that is congested never gets a quiet moment to catch up.
The loop now steps down when the tool is left alone, and the first key or mouse event puts it straight back to the library's own rate:
Two stages rather than one keeps the cost off the case that would be felt. Ten seconds is far longer than any pause in typing, so a burst of keystrokes never pays anything; only walking away costs the full quarter second, once, on the key that wakes it.
Measured, before and after
Behind a pty that answers the driver's size and cursor queries like a real terminal:
Four further samples of the waking keypress after 65s idle, which is the only thing this makes worse: 248, 60, 235 and 70 ms, with the keypress after it back to 29-58 ms.
What this is not
A confirmed fix for your lag. It does not reproduce on a local terminal, and I went looking properly rather than for 90 seconds:
So the constant output is what the tool was doing wrong regardless, and it is the only mechanism I can see that could produce "fine at first, laggy after a few minutes" over a link. If it still goes laggy after this, the useful thing to say is which terminal and what connection - SSH, tmux, mosh, Windows Terminal - because the remaining suspects all live there.
Testing
TuiIdlePolicy.RateForis a pure function of how long it is since the last input, so it is unit tested directly: the rates, both thresholds, that it is monotonic across two minutes of idling, and that it never speeds the loop up past what the library asked for. 66 tests pass. The test project now references the CLI project (withInternalsVisibleTo) to reach it.Merging this wants a
v0.6.1tag to ship it; the CHANGELOG section is already written for the release notes.🤖 Generated with Claude Code
https://claude.ai/code/session_01FkDFej82QnbjMJYFcwYyAZ