Skip to content

fix(renderer): prevent render loop stall by re-checking immediateRerenderRequested in loop() finally block - #1279

Closed
AH64-dll wants to merge 1 commit into
anomalyco:mainfrom
AH64-dll:fix/render-loop-race
Closed

AH64-dll wants to merge 1 commit into
anomalyco:mainfrom
AH64-dll:fix/render-loop-race

Conversation

@AH64-dll

Copy link
Copy Markdown

Problem

The render loop can silently stall in idle mode (_isRunning = false) when a requestRender() call arrives in the window between the if/else scheduling decision and the finally block that sets this.rendering = false.

Race condition

  1. loop() runs, at the scheduling decision sees _isRunning = false and immediateRerenderRequested = false -> goes to else branch, clears timeout, schedules nothing
  2. Between the else branch and the finally block, a requestRender() fires -- rendering is still true -> only sets immediateRerenderRequested = true and returns early (line 1507-1509)
  3. finally sets this.rendering = false but does not check immediateRerenderRequested
  4. activateFrame() finally sets this.updateScheduled = false
  5. No timeout was scheduled, and the flag is set but nobody checks it
  6. Render loop is dead until an external event (SIGWINCH, mouse, keyboard) triggers another requestRender()

This manifests as the TUI going black during burst event processing (e.g. when the user presses Enter to submit a prompt and the agent starts working).

Fix

In loop()'s finally block, re-check immediateRerenderRequested. If it was set during the race window (after the scheduling decision but before rendering = false), re-issue via setImmediate.

setImmediate (not process.nextTick) is needed because activateFrame() keeps updateScheduled = true until its own finally after loop() returns. process.nextTick fires before microtasks resume -- before activateFrame's finally -- so requestRender() would be blocked by the !updateScheduled guard.

Related

AH64-dll added a commit to AH64-dll/opencode that referenced this pull request Jul 19, 2026
Updates @opentui/core to include the fix for the render loop race condition
where immediateRerenderRequested could be set between the scheduling decision
and the finally block, causing the render loop to silently stall.

See: anomalyco/opentui#1279
@simonklee

Copy link
Copy Markdown
Member

Thank you for the contribution. The proposed race cannot occur as described.
This change does not prevent a reproduced lost render request, and it can
report the renderer as idle before the next frame runs. Closing this because it
does not fix the defect and introduces incorrect behavior.

@simonklee simonklee closed this Aug 27, 2026
camalolo added a commit to camalolo/opencode that referenced this pull request Nov 5, 2026
requestRender() calls arriving between loop()'s flag-clear and its
finally block were silently dropped (immediateRerenderRequested lost),
killing the frame loop mid-burst - blank screen forever after mount,
most visibly on Windows through ConPTY. Applies the fix from
anomalyco/opentui#1279 (re-check immediateRerenderRequested in the
finally, re-issue via setImmediate) as a repo patch until we move off
the 0.4.5 line.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants