macOS: free surface synchronously in deinit on main thread (#13364)

Since the renderer thread now emits scrollbar events on almost every
frame, there's always a `.scrollbar` message for the dying surface in
the app mailbox.

The OS runtime seems to schedule `appTick` and `ghostty_surface_free`
differently across macOS pre-26, 26 and 27.

On macOS 26.x, `ghostty_app_free` happens after
`App.scrollbar(_:target:v:)`, leaving `surface.userdata` pointing at a
freed `SurfaceView`.

When `deinit` runs on the main thread, free the surface synchronously
instead of detaching to a task. This fixes both crashes mentioned in
https://github.com/ghostty-org/ghostty/pull/9512 and
https://github.com/ghostty-org/ghostty/issues/13359.

### AI Disclosure 

I used Claude to analyze the backtrace, but the code is written and
tested by myself.
This commit is contained in:
Mitchell Hashimoto
2026-07-26 14:42:00 -07:00
committed by GitHub

View File

@@ -24,6 +24,13 @@ extension Ghostty {
}
deinit {
guard !Thread.isMainThread else {
// The surface remains registered with the app and holds unretained
// userdata until it is freed. When already on the main thread, free
// it synchronously so teardown completes before we disappear.
ghostty_surface_free(surface)
return
}
// deinit is not guaranteed to happen on the main actor and our API
// calls into libghostty must happen there so we capture the surface
// value so we don't capture `self` and then we detach it in a task.