Skip to content

Releases: cattyhouse/ghostty

Test build: CVDisplayLinkStop freeze fix, stripped (macOS arm64)

Choose a tag to compare

@cattyhouse cattyhouse released this 04 Oct 08:22
59c2dc0

Same patched code as fix-cvdisplaylinkstop plus a workflow strip step for the main executable (symbols 109k to 2.7k, binary 20MB to 16.8MB). Unsigned, ad-hoc signed, local testing only.

Test build: CVDisplayLinkStop freeze fix (macOS arm64)

Choose a tag to compare

@cattyhouse cattyhouse released this 27 Sep 05:04

Unofficial test build. Not signed with an Apple Developer ID and not
notarized. It is ad-hoc signed and carries no quarantine attribute, so it runs
locally, but it will be reported as "rejected" by spctl and should not be
used as a daily driver.

What this is

Ghostty built from f3fa60a9a on top of b40acce58, macOS arm64, with a fix
for a permanent freeze on external display hotplug.

The bug

CVDisplayLinkStop is a blocking handshake and, after a display
reconfiguration, it can never return: CoreVideo signals the display link's IO
thread and then waits once, with no predicate re-check, for an
acknowledgement that never arrives (measured: the caller waits on a condvar at
one address while the link's IO threads wait on a different one and stay in
waitUntil).

Ghostty called it on the render thread from syncDisplayLink on any frame with
nothing to draw, so one wedge permanently disabled the surface: no redraws, no
cursor blink, no input processing, while the rest of the app stayed responsive.
Only kill recovered.

What the fix does

Nothing blocks a caller on CoreVideo any more. Display links are stopped and
released on a worker thread with a deadline; if the deadline expires the link
is marked dead, is never touched or released again, and rendering continues on
the existing event-driven path. hasVsync reports false for a dead link.
draw_now, which is the display link's output callback userdata, moved to a
heap allocation so a callback that outlives the surface cannot point into
freed memory, and the renderer-thread join in Surface.deinit is bounded.

Expected behaviour when testing

Repro (see below): disconnect and reconnect an external display a few times,
closing or switching tabs/windows in between. It does not reproduce on the
first toggle.

  • Best case: no freeze, and no relevant log line.
  • Also correct: no freeze, but the log contains
    CVDisplayLinkStop did not return within ...ms, continuing without vsync rather than blocking. This means the wedge still happens but no longer
    costs the surface; that surface loses vsync until it is recreated.
  • Bad: it still freezes. Please report with a sample.

Check the log (messages are written at os_log error/fault level, so they are
persisted and visible after the fact):

log show --predicate 'processID == <pid>' --last 15m --style compact 2>/dev/null \
  | grep -iE "display link|draw_now|surface"

Confirm you are running this build: patched-f3fa60a9aâ€Ķ in
Ghostty.app/Contents/Info.plist under CFBundleShortVersionString and
GhosttyCommit.

Reverting

Quit this app and use your normal Ghostty install; this build is a separate
bundle and does not replace anything.

Contents

  • Ghostty-patched-macos-arm64.zip — the app.
  • cvdisplaylinkstop-fix.patch — the source change in this build, applies to
    b40acce58.