From dab2a1930ced7ac4f10ee74515688680584faeba Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 26 May 2026 09:43:16 -0500 Subject: [PATCH] qt: wake the renderer CV before ghostty_surface_free in dtor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the renderer thread is parked in presentVulkanDmabuf's condition_variable wait (added in f48465cda) when GhosttySurface is destroyed, ghostty_surface_free's per-surface tear-down race with our mutex/CV destruction once the dtor body returns — manifests as a SEGV (address boundary error) when fish kills the parent shell and the surface dtors fan out faster than the renderer can wake from its 100 ms timeout. Set m_hidden=true + notify_all the compositor CV BEFORE handing the surface to libghostty for tear-down. The renderer wakes immediately, sees m_hidden, bails without parking, returns to the xev loop, and libghostty's shutdown path joins the thread cleanly while our mutex/CV are still alive. Same pattern as the Hide / PlatformSurface destroy handlers (which already notify_all for the same reason). Co-Authored-By: claude-flow --- qt/src/GhosttySurface.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/qt/src/GhosttySurface.cpp b/qt/src/GhosttySurface.cpp index 7d1fc903b..a4026d1b2 100644 --- a/qt/src/GhosttySurface.cpp +++ b/qt/src/GhosttySurface.cpp @@ -214,6 +214,22 @@ GhosttySurface::~GhosttySurface() { // QPointer auto-nulls on a destroyed QObject, so .data() is safe. delete m_inspectorWindow.data(); + // Wake the renderer thread if it's parked in presentVulkanDmabuf's + // CV wait BEFORE we hand the surface to libghostty for teardown. + // ghostty_surface_free below shuts down + joins the renderer + // thread; if that thread is blocked on our CV, the join either + // hangs for our 100 ms timeout (best case) or races our mutex / + // CV destruction once this body returns (worst case → SEGV when + // the renderer wakes from the timeout and touches the destroyed + // mutex). The predicate also checks m_hidden so the renderer + // bails out without parking another frame. + m_hidden.store(true, std::memory_order_release); + { + std::lock_guard lg(m_compositorMutex); + m_compositorReady = true; + } + m_compositorCv.notify_all(); + // GL teardown must happen with the context current. If makeCurrent // fails (e.g. the ctor failed before m_context could be created), we // still free m_surface — it carries no GL state of its own — and we