Repository navigation
fix(cli): mime serve's view orders every print with its draws and starts a live display of its own each time it comes back - #139
Merged
Conversation
…rts a live display of its own each time it comes back test_a_scripted_session_on_a_terminal_... failed with "the live region is gone" on CI after #138 (PR #126's run 41839710392, [60]; PR #136's run 41762639938, [100]). The failure message showed no frames, so the test now prints, on a failure of its end checks, the screen after the stop and what the terminal received where the strip was first drawn and at its end, each bounded, and whether the reader saw the terminal's end. Reproduced naturally with that capture: the session test at its three widths beside test_serve_view.py, three pytest runs at once on two xdist workers each, all under taskset on four cores with two busy loops of our own. The transcript shows the cause. rich's live display finds the top of its region from the height it was last drawn at, under its own lock, and draws the region again only after letting that lock go. Serve's ready line (the serve loop's thread) and the view's draw (its own thread) each wrote the same stale reset, the second erasing only part of the region the first had drawn: five lines of a READY frame, "Ctrl+C to stop" among them, stayed in the scrollback. The end of the transcript was complete and the reader saw the terminal's end, so the clear was not lost. Every print through the view's console now holds the lock its draws hold (_ordered_console): serve's own lines, the events, the draws and writes to the redirected standard streams come one after the other. The same transcript showed a second defect of the same kind: the view kept one live display across the pause around the load report, whose shape was the spinner's, so the first line once the view was back erased the report's last line wherever the building phase takes two lines (60 and 80 columns, every run). The view now starts a live display of its own each time it comes back. Tests (tests/cli/test_serve_view.py), each with a control that fails its check on every run: a draw made on another thread while the ready line is being printed, between its reset and its region, leaves no frame of the strip (the control: a console whose prints are not ordered with the draws); and the first line once the view is back erases nothing printed while it was away (the control: one live display kept across the pause). Both fail on every run on the code before this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NicholasEhsanRoy
force-pushed
the
v0.3
branch
from
November 4, 2026 15:48
a25d8ea to
9e1df39
Compare
NicholasEhsanRoy
force-pushed
the
v0.3-terminal-race
branch
from
November 4, 2026 15:48
cbc0766 to
9cbb254
Compare
…d moved (#140), so this PR's entry goes to docs/development/changes_0.3.0.dev1.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGvZFhw6o3WBo28MJ9998e
NicholasEhsanRoy
added a commit
that referenced
this pull request
Nov 4, 2026
…tay in the development record's Fixed section Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGvZFhw6o3WBo28MJ9998e
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.
test_a_scripted_session_on_a_terminal_shows_each_event_once_above_a_strip_that_follows_the_statusfailed with "the live region is gone" after #138 (run 41839710392,[60]; earlier run 41762639938,[100]). #138 made the reader wait for the terminal's end; it could not reproduce the failure naturally. This PR captures the failure, reproduces it, and fixes the cause inServeDisplay.Capture
On a failure of its end checks, the session test now prints the screen after the stop (lines numbered), what the terminal received where the strip was first drawn and at its end (control sequences written out, each part at most 4000 characters), and whether the reader saw the terminal's end. A planted failure shows the message.
Reproduction and cause
Conditions: the session test at its three widths beside
tests/cli/test_serve_view.py, three pytest runs at once on two xdist workers each, all undertaskseton four cores with two busy loops of our own (and on two cores with three, for one arm), on a machine other sessions were also loading. Unchanged code: 2 failures in 225 executions, both at[60], both "the live region is gone", both with the transcript below.The captured transcript (60 columns) shows the cause. After the load report, the view's draw (its own thread) wrote
\r\x1b[2K\x1b[1A\x1b[2Kand a 7-line READY region; then serve's ready line (the serve loop's thread) wrote the same 2-line reset, its line and the region. Five lines of the first frame (READY · step 0,Ctrl+C to stop, the table's header) stayed in the scrollback. rich's live display finds the top of its region from the height it was last drawn at under its own lock, but draws the region again only after letting that lock go, so a draw between the two leaves the print moving up by a stale height. The end of the transcript was complete and the reader saw the terminal's end: the clear was not lost. An in-process probe with nothing injected (ServeDisplayon a pseudo-terminal going through serve's load report and ready line, on one contended core) leaves the same ghost in 4 of 5100 trials, the raw bytes showing the same two writes with the same stale reset.The same transcript shows a second defect of the same kind, on every run: the view kept one live display across the pause around the load report, still holding the spinner's shape, so the first line once the view was back erased the report's last line (
derived graph: ...) wherever the building phase takes two lines (60 and 80 columns).Fix
_ordered_console): serve's own lines, the events, the draws, and writes to the redirected standard streams from any thread come one after the other. A line of serve's own can now wait for a draw under way, which stays bounded by the writer's flush wait.Tests
In
tests/cli/test_serve_view.py, each with a control that fails its check on every run:Both fail on 60 of 60 runs on the code before this change (10 runs, 3 widths each).
After the fix, the same loop: 0 failures in 180 executions, and the probe 0 ghosts in 3000 trials. At a base rate near 1% neither comparison discriminates on its own (with the unchanged code's rate, no failure in 180 has probability about 0.2, no ghost in 3000 about 0.1). What shows the fix is the pair of tests above, whose interleaving is forced: 60 of 60 failures before, none after, and their controls fail on every run. Stress: 20 of 20 runs passed on
tests/cli/test_serve_terminal.pyandtests/cli/test_serve_view.py.Not changed here:
RunDisplay(mime run) restarts one live display across its pause too, and prints Ctrl+C's acknowledgement beside rich's own refresh thread; the same class, not this test's failure.🤖 Generated with Claude Code