mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-07-31 12:49:03 +00:00
The renderer state mutex is unfair on all platforms (os_unfair_lock on macOS, a futex based lock elsewhere). A thread that unlocks and right away locks again wins over a sleeping waiter, because the waiter first has to be woken up and scheduled. The termio parse thread does exactly this under heavy pty output: it relocks the mutex for every batch and never sleeps in between, so the renderer can starve in updateFrame for as long as the output lasts. Fix this by letting the parse thread stay off the lock until the renderer had its turn. `renderer.State` gets two atomics: a waiter count (`demand`) and a generation counter (`handoff_gen`). The renderer takes the mutex through lockDemand/unlockDemand which update these, and the parse thread calls yieldToDemand between batches. If a waiter exists it sleeps on a futex until the renderer took and released the lock, with a 1ms timeout so a lost wake can not stall IO forever. All the atomics are monotonic on purpose: they are only a hint for scheduling, the mutex still protects the terminal state itself. When the renderer is not waiting the cost for the parse loop is a single relaxed atomic load per batch.