From 8187177c7ff62e9598198a15b329330b2fa6c3a2 Mon Sep 17 00:00:00 2001 From: Amit Mendapara <282290+cristatus@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:11:12 +0530 Subject: [PATCH] [client] app: free overlays after display server shutdown The render thread freed the overlays as soon as it exited, while the main thread could still be dispatching display server input callbacks that iterate them. Free the overlays in lg_shutdown instead, once the render thread has been joined and the display server has shut down. The overlays now outlive the renderer, so overlayFreeImage must not call into it; renderer deinitialization has already destroyed all textures along with the context. --- client/src/app.c | 11 +++++++++-- client/src/main.c | 21 ++++++++++++++------- client/src/overlay_utils.c | 6 +++++- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/client/src/app.c b/client/src/app.c index 8531b373..f18bc947 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -858,6 +858,8 @@ void app_registerOverlay(const struct LG_OverlayOps * ops, const void * params) ops->earlyInit(); } +static bool l_overlaysInitialized = false; + void app_initOverlays(void) { struct Overlay * overlay; @@ -868,9 +870,12 @@ void app_initOverlays(void) if (!overlay->ops->init(&overlay->udata, overlay->params)) { DEBUG_ERROR("Overlay `%s` failed to initialize", overlay->ops->name); - overlay->ops = NULL; + ll_removeNL(g_state.overlays, item); + free(item); + free(overlay); } } + l_overlaysInitialized = true; ll_unlock(g_state.overlays); /* Do not seed ImGui's clock with absolute host uptime. ImGui stores the @@ -1066,9 +1071,11 @@ void app_freeOverlays(void) struct Overlay * overlay; while(ll_shift(g_state.overlays, (void **)&overlay)) { - overlay->ops->free(overlay->udata); + if (l_overlaysInitialized) + overlay->ops->free(overlay->udata); free(overlay); } + l_overlaysInitialized = false; } void app_setOverlay(bool enable) diff --git a/client/src/main.c b/client/src/main.c index 6462d2f1..e24184c5 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -1078,13 +1078,6 @@ static int renderThread(void * unused) app_setState(APP_STATE_SHUTDOWN); - if (g_state.overlays) - { - app_freeOverlays(); - ll_free(g_state.overlays); - g_state.overlays = NULL; - } - lgTimerDestroy(tickTimer); lgTimerDestroy(fpsTimer); @@ -3505,6 +3498,9 @@ static void lg_shutdown(void) } LG_LOCK_FREE(l_cursorRepaint.lock); + // An evdev batch may call input handlers; join before input teardown + evdev_stop(); + if (g_state.transport.ops) { lgInput_dropTransport(); @@ -3540,6 +3536,7 @@ static void lg_shutdown(void) if (g_state.ds) g_state.ds->shutdown(); + lgClipboard_free(); app_releaseAllKeybinds(); @@ -3548,6 +3545,16 @@ static void lg_shutdown(void) if (g_state.ds && g_state.dsInitialized) g_state.ds->free(); + // Input callbacks have stopped; the evdev state and overlays can go + evdev_free(); + + if (g_state.overlays) + { + app_freeOverlays(); + ll_free(g_state.overlays); + g_state.overlays = NULL; + } + renderQueue_setSourceFns(NULL, NULL, NULL); renderQueue_free(); LG_LOCK_FREE(g_state.videoSourceLock); diff --git a/client/src/overlay_utils.c b/client/src/overlay_utils.c index 70d2e23c..f89658d5 100644 --- a/client/src/overlay_utils.c +++ b/client/src/overlay_utils.c @@ -182,5 +182,9 @@ void overlayFreeImage(OverlayImage * image) if (!image->tex) return; - RENDERER(freeTexture, image->tex); + // During shutdown the renderer is freed first, taking all textures with it + if (g_state.lgr) + RENDERER(freeTexture, image->tex); + + image->tex = NULL; }