From b8a8a660e16eecdfed18ce78205abe7e6750a5d3 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 26 Jul 2026 10:59:14 -0400 Subject: [PATCH] [Refactor] (Lifecycle): own MobileGL's lifecycle from the EGL layer instead of ELF static ctor/dtor - the first EGL/WGL entry point lazily initializes via a thread-safe, re-init-capable EnsureInitialized (AutoInit is gone), the last eglTerminate with no initialized display and nothing current tears the whole library down deterministically inside the EGL lifecycle, and the global singletons move to leak-at-exit heap storage so process exit runs no backend destructors at all (AutoDestroy and the Windows DllMain abandon hook are gone); fixes the exit-time SIGABRT from undefined static-destruction order - the DirectGLES buffer-pool mutex abort on Android clean exits, and the pre-existing macOS QueryTest/ProgramTest 'Subprocess aborted' gtest failures now pass (ctest 411/411) --- MobileGL/GlobalObjects.cpp | 8 +- MobileGL/Init.cpp | 89 ++++++++----------- MobileGL/Init.h | 6 ++ MobileGL/MG_Backend/BackendObjects.h | 2 +- .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 3 +- .../MG_Backend/DirectVulkan/DirectVulkan.h | 2 +- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 31 +++++-- .../GLImpl/Framebuffer/GL_Framebuffer.cpp | 3 +- .../GLImpl/Framebuffer/GL_Framebuffer.h | 2 +- .../MG_Impl/GLImpl/Texture/ProxyTexture.cpp | 3 +- .../MG_Impl/GLImpl/Texture/ProxyTexture.h | 2 +- MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp | 5 +- MobileGL/MG_State/EGLState/Core.cpp | 23 ++++- MobileGL/MG_State/EGLState/Core.h | 6 +- MobileGL/MG_State/GLState/Core.cpp | 3 +- MobileGL/MG_State/GLState/Core.h | 2 +- 16 files changed, 119 insertions(+), 71 deletions(-) diff --git a/MobileGL/GlobalObjects.cpp b/MobileGL/GlobalObjects.cpp index f5c40733..2b3cc0e1 100644 --- a/MobileGL/GlobalObjects.cpp +++ b/MobileGL/GlobalObjects.cpp @@ -14,7 +14,13 @@ namespace MobileGL { } // namespace MG_Config namespace MG_Backend { - UniquePtr pActiveBackendObject; + // Leak-at-exit storage: the UniquePtr itself lives on the heap and is + // never destroyed by the runtime, so process exit runs no backend + // destructors (static destruction order across TUs is undefined). + // Deterministic teardown happens inside the EGL lifecycle instead: + // the last eglTerminate calls MobileGL::Destroy(), which .reset()s + // these singletons while the process is still healthy. + UniquePtr& pActiveBackendObject = *new UniquePtr(); GlobalBackendFunctionsTable gBackendFunctionsTable; } // namespace MG_Backend } // namespace MobileGL diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 2f711d5d..c59e5d52 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -15,9 +15,18 @@ #include #include +#include +#include + namespace MobileGL { namespace { - Bool g_isInitialized = false; + std::atomic g_isInitialized = false; + thread_local Bool tl_initializing = false; + + std::mutex& InitMutex() { + static std::mutex mutex; + return mutex; + } void DestroyImpl(Bool logLifecycle) { if (!g_isInitialized) { @@ -65,59 +74,35 @@ namespace MobileGL { MGLOG_I("MobileGL initialized"); } + void EnsureInitialized() { + if (g_isInitialized.load(std::memory_order_acquire)) { + return; + } + // Re-entrant call while this thread is already inside Initialize() + // (e.g. an init step routing back through a public entry point). + if (tl_initializing) { + return; + } + const std::lock_guard lock(InitMutex()); + if (g_isInitialized.load(std::memory_order_acquire)) { + return; + } + tl_initializing = true; + Initialize(); + tl_initializing = false; + } + void Destroy() { DestroyImpl(true); } -#if defined(__linux__) || defined(__APPLE__) - __attribute__((constructor)) static void AutoInit() { - Initialize(); - } - - __attribute__((destructor)) static void AutoDestroy() { - if (MG_Config::Features.TraceSkipAutodestroy) { - return; - } -#if defined(__APPLE__) - // macOS injected dylibs can run destructors after logging/backend static state is already torn down. - return; -#else - DestroyImpl(false); -#endif - } -#endif - -#ifdef _WIN32 - namespace { - // Called on DLL_PROCESS_DETACH, which runs BEFORE the CRT executes this - // DLL's static destructors. Releasing (deliberately leaking) the global - // singletons here keeps those destructors from tearing down the backend - // inside a dying process - other threads have already been terminated, - // so Vulkan/GLES cleanup crashes with the process half-dead. The process - // is exiting; the OS reclaims everything. - // No logging here: this runs under the loader lock after every other - // thread was terminated, where the log mutex/heap/stdio may be in any - // state. Plain pointer stores only. - void AbandonAtProcessExit() { - MG_Backend::DirectVulkan::pVulkanRenderer.release(); - MG_Backend::pActiveBackendObject.release(); - MG_State::pGLContext.release(); - MG_State::pEGLContext.release(); - MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.release(); - MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.release(); - } - } // namespace -#endif + // MobileGL's lifecycle is owned entirely by the host-API layers + // (EGL/WGL/CGL): initialization happens lazily on the first entry point + // via EnsureInitialized(), and full teardown happens deterministically + // when the last EGL display is terminated with nothing current (EGLImpl + // calls Destroy()). There is intentionally no static constructor, no + // static destructor, and no DllMain: the global singletons use + // leak-at-exit storage (see GlobalObjects.cpp), so a process that exits + // without eglTerminate simply leaks them to the OS instead of running + // backend destructors during static teardown. } // namespace MobileGL - -#ifdef _WIN32 -// Initialization is NOT done here: MobileGL::Initialize() loads libraries -// (Vulkan/ANGLE) and spins up glslang, none of which is safe under the loader -// lock. The WGL host layer calls Initialize() lazily on its first entry point. -extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { - if (reason == DLL_PROCESS_DETACH) { - MobileGL::AbandonAtProcessExit(); - } - return TRUE; -} -#endif diff --git a/MobileGL/Init.h b/MobileGL/Init.h index 349f6c9b..724f1a8e 100644 --- a/MobileGL/Init.h +++ b/MobileGL/Init.h @@ -11,6 +11,12 @@ namespace MobileGL { void Initialize(); + // Thread-safe, idempotent, and re-entrant wrapper around Initialize(). + // Host layers (EGL/WGL/CGL entry points) call this lazily on first use so + // MobileGL's lifecycle never depends on ELF/DLL static constructors, and + // so a fresh init can follow a full Destroy() (e.g. after the last + // eglTerminate). + void EnsureInitialized(); void Destroy(); namespace MG_Util::Debug { diff --git a/MobileGL/MG_Backend/BackendObjects.h b/MobileGL/MG_Backend/BackendObjects.h index d95f12dc..1a148824 100644 --- a/MobileGL/MG_Backend/BackendObjects.h +++ b/MobileGL/MG_Backend/BackendObjects.h @@ -13,6 +13,6 @@ #include "DirectVulkan/BackendObject_DirectVulkan.h" namespace MobileGL::MG_Backend { - extern UniquePtr pActiveBackendObject; + extern UniquePtr& pActiveBackendObject; extern GlobalBackendFunctionsTable gBackendFunctionsTable; } // namespace MobileGL::MG_Backend diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index cf9ca0ab..f9abfddb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -20,7 +20,8 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { - UniquePtr pVulkanRenderer = nullptr; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pVulkanRenderer = *new UniquePtr(); namespace { // Generation of the live VulkanRenderer instance, mirroring diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 27bb4f96..8e268e0d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -12,7 +12,7 @@ #include "Renderer/VulkanRenderer.h" namespace MobileGL::MG_Backend::DirectVulkan { - extern UniquePtr pVulkanRenderer; + extern UniquePtr& pVulkanRenderer; // Generation of the live VulkanRenderer instance, mirroring DirectGLES's // g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 42590ad7..cb3af392 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -8,6 +8,7 @@ #include "EGLImpl.h" #include "../GetProcAddress.h" +#include #include #include #include @@ -25,6 +26,17 @@ namespace MobileGL::MG_Impl::EGLImpl { return MG_State::pEGLContext.get(); } + // Entry points that can legitimately be an application's FIRST EGL + // call (display/proc-address/string queries) lazily bring MobileGL + // up here, so the library needs no static constructor and can + // re-initialize after the last eglTerminate tore everything down. + // Teardown-ish entry points keep using GetState() and fail benignly + // when MobileGL is not initialized. + EGLStateContext* GetStateEnsureInitialized() { + MobileGL::EnsureInitialized(); + return GetState(); + } + MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) { auto* backendObject = MG_Backend::pActiveBackendObject.get(); if (!backendObject && state) { @@ -189,7 +201,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_FALSE; } @@ -210,7 +222,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLDisplay GetDisplay(NativeDisplayType display) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_NO_DISPLAY; } @@ -315,6 +327,14 @@ namespace MobileGL::MG_Impl::EGLImpl { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { backendObject->ReleaseEGLResources(); } + // The last initialized display is gone and nothing is current on any + // thread: tear the whole library down deterministically inside the + // EGL lifecycle (backend, GL/EGL state, glslang). A later EGL call + // re-initializes lazily via GetStateEnsureInitialized(); process exit + // then has nothing left to destroy. + if (!state->HasAnyInitializedDisplay() && !state->HasAnyCurrentContext()) { + MobileGL::Destroy(); + } return EGL_TRUE; } @@ -347,7 +367,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLBoolean BindAPI(EGLenum api) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_FALSE; } @@ -380,7 +400,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } char const* QueryString(EGLDisplay display, EGLint name) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return nullptr; } @@ -643,7 +663,7 @@ namespace MobileGL::MG_Impl::EGLImpl { EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list) { (void)attrib_list; - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_NO_DISPLAY; } @@ -739,6 +759,7 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!name) { return nullptr; } + MobileGL::EnsureInitialized(); MGLOG_D("eglGetProcAddress(%s)", name); void* proc = MG_Impl::GetProcAddress(name); diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index 30a955d7..71e90b10 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -2144,6 +2144,7 @@ namespace MobileGL::MG_Impl::GLImpl { } namespace FramebufferImpl { - UniquePtr pDefaultFramebufferInfo; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pDefaultFramebufferInfo = *new UniquePtr(); } // namespace FramebufferImpl } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h index 08164f69..8e843d49 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h @@ -78,6 +78,6 @@ namespace MobileGL::MG_Impl::GLImpl { SharedPtr stencilAttachment; }; - extern UniquePtr pDefaultFramebufferInfo; + extern UniquePtr& pDefaultFramebufferInfo; } // namespace FramebufferImpl } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp index c16df634..156f7d76 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp @@ -14,7 +14,8 @@ #include namespace MobileGL::MG_Impl::GLImpl::TextureImpl { - UniquePtr pProxyTextureManager; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pProxyTextureManager = *new UniquePtr(); Bool IsProxyTextureTarget(TextureUploadTarget target) { switch (target) { diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h index 59196829..f83f3faf 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h @@ -23,5 +23,5 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { UnorderedMap> m_proxyTexturesMap; }; - extern UniquePtr pProxyTextureManager; + extern UniquePtr& pProxyTextureManager; } // namespace MobileGL::MG_Impl::GLImpl::TextureImpl diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp index 09663a0e..605b53de 100644 --- a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp @@ -138,8 +138,9 @@ namespace MobileGL::MG_Impl::WGLImpl { void EnsureInitialized() { // Initialize() loads backend libraries and glslang, which must not run // under the loader lock; first WGL call is the earliest safe moment. - static std::once_flag once; - std::call_once(once, [] { MobileGL::Initialize(); }); + // MobileGL::EnsureInitialized (not a local once_flag) so a fresh init + // can follow a full teardown from the last eglTerminate. + MobileGL::EnsureInitialized(); } EGLDisplay EnsureDisplay() { diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index dcc1f6f4..6e604182 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -368,6 +368,26 @@ namespace MobileGL { return true; } + Bool EGLContext::HasAnyInitializedDisplay() const { + const std::lock_guard lock(m_mutex); + for (const auto& [handle, displayObject] : m_displays) { + if (displayObject.Initialized) { + return true; + } + } + return false; + } + + Bool EGLContext::HasAnyCurrentContext() const { + const std::lock_guard lock(m_mutex); + for (const auto& [threadId, current] : m_threadCurrents) { + if (current.Context != nullptr) { + return true; + } + } + return false; + } + Bool EGLContext::ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs, EGLint configSize, EGLint* numConfig) { const std::lock_guard lock(m_mutex); @@ -1415,6 +1435,7 @@ namespace MobileGL { } } // namespace EGLState - UniquePtr pEGLContext; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pEGLContext = *new UniquePtr(); } // namespace MG_State } // namespace MobileGL diff --git a/MobileGL/MG_State/EGLState/Core.h b/MobileGL/MG_State/EGLState/Core.h index 41821186..f38a94ca 100644 --- a/MobileGL/MG_State/EGLState/Core.h +++ b/MobileGL/MG_State/EGLState/Core.h @@ -39,6 +39,10 @@ namespace MobileGL { Bool IsDisplayInitialized(EGLDisplayHandle display) const; Bool InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor); Bool TerminateDisplay(EGLDisplayHandle display); + // Whole-library idle checks used by EGLImpl::Terminate to decide + // when the last eglTerminate may tear MobileGL down entirely. + Bool HasAnyInitializedDisplay() const; + Bool HasAnyCurrentContext() const; // Config Bool ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs, @@ -262,6 +266,6 @@ namespace MobileGL { }; } // namespace EGLState - extern UniquePtr pEGLContext; + extern UniquePtr& pEGLContext; } // namespace MG_State } // namespace MobileGL diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 6101ce9c..856a5d18 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -721,5 +721,6 @@ namespace MobileGL::MG_State { } } // namespace GLState - UniquePtr pGLContext; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pGLContext = *new UniquePtr(); } // namespace MobileGL::MG_State diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 51a93d90..01f1b5eb 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -252,7 +252,7 @@ namespace MobileGL { }; } // namespace GLState - extern UniquePtr pGLContext; + extern UniquePtr& pGLContext; // True when relaxed GL semantics apply. Strict core rules are enforced only when the // current EGL context explicitly requested a core profile (core bit in