Repository navigation
macOS: permanent deadlock during the display reconfiguration that follows wake from sleep — renderer thread blocked forever in CVDisplayLinkStop()
#14150
Issue DescriptionGhostty freezes permanently the instant the machine wakes from sleep. It is a SymptomThe window is completely frozen: no redraw, no keyboard input. The rest of the The deadlockRenderer thread — blocked forever in Main thread — blocked forever on a Zig futex, called from inside a Because the main thread blocks inside The spindump puts the renderer thread's last run 21 ms before the main Where in the sourceThe binary is stripped so I cannot symbolize the // src/renderer/generic.zig, fn syncDisplayLink (~L1150)
// "Create or update the display link and match it to the current surface state."
if (display_id) |id| {
log.info("updating display link display id={}", .{id});
display_link.setCurrentCGDisplay(id) catch |err| { // ~L1185
log.warn("error setting display link display id err={}", .{err});
};
}
const should_run =
// Non-visible windows never vsync
self.visible and
// Only vsync if we have cell changes or animation
(self.cells_rebuilt or self.animationWake() != null); // ~L1193
if (should_run) {
if (!display_link.isRunning()) {
display_link.start() catch {};
}
} else {
display_link.stop() catch {}; // ~L1197 <-- blocks here
}
Both run on the render thread. Either chain — The locking problem. /// This mutex must be held whenever any state used in `drawFrame` is
/// being modified, and also when it's being accessed in `drawFrame`.
draw_mutex: std.Io.Mutex = .init,
Why the display link is running at all:
|
Replies: 13 comments 3 replies
|
Second reporter, same deadlock — twice in five days on Both were captured as 1. The block can happen in Four seconds after a wake from display sleep: That is 2. The wedge can stay silent for a long time, then detonate on surface teardown. The renderer above wedged at 10:50:49. The app stayed completely usable for the next 43 minutes — one tab simply never repainted again. It froze at 11:34:09.9, when I closed that tab, and the main thread was not waiting on Teardown joins the wedged renderer. Two consequences:
Practical note for anyone hitting this: don't Cmd+Q a frozen Ghostty. Quitting runs the same teardown and joins the same thread, so the quit hangs too. 3. An earlier occurrence (22 min) had no Ghostty frame in the blocking path at all — the main thread was inside CoreVideo's own reconfiguration callback: with a SwiftUI dispatch thread piled up behind it on Happy to share fuller stacks from either report if they'd help. I'm now updating to a version with this fix without disabling shaders, and will report back in case of re-occurrence. |
|
Hi! So it seems like this or something related is still an issue on a build that has the fix. I'm on My MacBook Pro was awake the whole time and I had just changed the resolution on my external display. A display reconfiguration on its own seems to be enough to trigger it? I had Claude Code read the spindump and it pointed me at this: The MacBook Pro is a Mac16,7 on macOS 26A5425a. The external display is a BenQ at 70Hz as the main next to the internal 120Hz panel. No custom shaders. Let me know if/how I can help. I will try to reproduce the various sleep and screen adjustment scenarios to gather some additional logs. |
|
Ghostty still occasionally freezes when waking up from screen sleep. |
|
stuck again. 123.log └──❯ ghostty +version
Ghostty 1.3.2-main-+7aab0a039
Version
- version: 1.3.2-main-+7aab0a039
- channel: tip
Build Config
- Zig version : 0.16.0
- build mode : .ReleaseFast
- app runtime : .none
- font engine : .coretext
- renderer : renderer.generic.Renderer(renderer.Metal)
- libxev : kqueue |
|
Same deadlock on What is new relative to the thread so far: 1. Post-#14171 the main-thread variant is the whole story, and the renderer is idle. Renderer thread: 2482/2550 in 2. Three IO threads for one link within a single reconfiguration. 3. The trigger was a display removal while awake, six seconds before sleep. Lid closed and external display (3008 x 1692) disconnected at 15:43:27.03. The main thread answered reconfiguration round 1 at .450, logged its last line at .776 while handling round 2 (the same transient 4. Environment. macOS 26.6.2 (25G83), One remark on direction. The blocking |
|
Still reproducible after wake while an external display was being reconfigured, on Ghostty The renderer has the same The additional detail in this occurrence is that focusing Ghostty after the renderer wedged turned it into a whole-application hang through the surface-focus path: All samples in the macOS hang report showed these same stacks. |
|
@XhstormR @mitchellh can someone reopen this discussion, it is not fully resolved. it is easy to reproduce on |
|
attached 2 files, with the help of |
|
Tested (monitor turn off / on 10 times) with below patch ( all done by LLM), problem fixed:
due to limited knowledge, i didn't review any of the code, i only tested the final app, so i think i am not qualified to propose a PR. fyi @mitchellh @XhstormR |
|
Ghostty became unresponsive as a whole. Activity Monitor showed two Ghostty processes; one was marked "Not Responding" with 27 threads, while the other had 6 threads. The attached captures are for PID 687, the process with 27 threads. I have observed this repeatedly when using Codex together with herdr. So far, this is the context in which I encounter the freeze, but I do not know the exact triggering action or have reliable step-by-step reproduction instructions. I have not established whether either tool causes the hang. Environment: Ghostty I captured a sample and spindump during the freeze on October 7, 2026, around 23:19 PDT. The spindump reports that Ghostty had been unresponsive for 830 seconds before its 10-second sampling window. Both captures show these stacks throughout their sampled main/renderer threads: The spindump contains 282 samples for each of these threads. It reports the renderer last ran 1340.670 seconds earlier and the main thread 829.573 seconds earlier. The earlier sample has the same stacks in all 1587 samples. The captures identify the blocked paths but do not establish the lock owner or a complete deadlock cycle. This resembles the follow-up reports in #14150. This capture is on build 18047 and the main-thread path is windowDidResignKey, rather than windowDidBecomeKey. I have not established that display reconfiguration triggered this occurrence. The current configuration has no custom shaders. The attached configuration was collected after the hang and has not been minimized or confirmed to match the hung process exactly. Attachments: Ghostty sample, Ghostty-only spindump excerpt, version output, current configuration, and Activity Monitor screenshot. Other process sections and personal identifiers were removed from the shared diagnostic copies. Original captures are preserved locally. AI disclosure: OpenAI Codex assisted with capture analysis, privacy filtering, and drafting this report. |
|
Still happens on I asked Claude to summarize:
Main thread: Renderer: In Happy to share the Ghostty section of the |
|
Okay, I've found the follow up issue, will be in 1.4: #14691 |
#14171