mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[Fix] (MG_Backend): stop DirectGLES twin destructors calling a dead driver at exit
The static twin registries destroy their backend objects from __run_exit_handlers, and a twin destructor then jumps through g_GLESFuncs into a driver library that exit() may already have torn down - a latent SIGSEGV that DriverBench has been dumping core with on every exit, and that any relink shuffling static destructor order can hand to the trace-replay binary (a byte-perfect replay then "fails with status Segmentation fault"). A process-teardown flag now short-circuits the program, VAO and texture twin destructors: past exit() the driver reclaims every GPU object anyway, so the skip is a deliberate leak of nothing. The flag is set by a std::atexit handler registered lazily on first registry use - by then every static everywhere has finished constructing, so the handler runs BEFORE any static destructor. A registry-destructor hook was tried first and is wrong: tests and cache resets destroy temporary registry instances mid-run, which latched the flag while the process was alive (caught by DirectGLESBackendTexture.DestructorDeletesIdAndScrubsBindingCache). 421/421 unit tests, the retrace subset exits cleanly on both backends, and the 52-entry integration suite passes.
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
@@ -114,6 +115,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return source;
|
||||
}
|
||||
|
||||
namespace {
|
||||
Bool g_processTeardown = false;
|
||||
std::once_flag g_teardownSentinelOnce;
|
||||
} // namespace
|
||||
|
||||
Bool InProcessTeardown() { return g_processTeardown; }
|
||||
void EnsureProcessTeardownSentinel() {
|
||||
std::call_once(g_teardownSentinelOnce,
|
||||
[] { std::atexit(+[] { g_processTeardown = true; }); });
|
||||
}
|
||||
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType) {
|
||||
if (shaderType != GL_VERTEX_SHADER || source.find("gl_BaseInstance") == String::npos) {
|
||||
return source;
|
||||
@@ -1388,6 +1400,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
BackendVertexArrayObject::~BackendVertexArrayObject() {
|
||||
if (InProcessTeardown()) {
|
||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||
}
|
||||
if (m_backendVAOId != 0) {
|
||||
NoteVAOIdDeleted(m_backendVAOId);
|
||||
g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId);
|
||||
@@ -1641,6 +1656,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
BackendTextureObject::~BackendTextureObject() {
|
||||
if (InProcessTeardown()) {
|
||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||
}
|
||||
if (m_backendTextureId == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -3780,6 +3798,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
#endif
|
||||
if (InProcessTeardown()) {
|
||||
return; // see InProcessTeardown(): the driver may be unloaded already
|
||||
}
|
||||
if (m_backendProgramId != 0) {
|
||||
MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId);
|
||||
g_GLESFuncs.glDeleteProgram(m_backendProgramId);
|
||||
|
||||
@@ -21,9 +21,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||
|
||||
// True once the process has entered exit(): past that point the EGL library and
|
||||
// the driver may already be unloaded, so a backend twin's destructor must not
|
||||
// call into g_GLESFuncs (the observed crash is a jump through an unmapped driver
|
||||
// pointer from __run_exit_handlers) nor touch statics in other TUs (cross-TU
|
||||
// destruction order is unspecified). Deliberate leak: the process is exiting and
|
||||
// the driver reclaims GPU objects. The flag is set by a std::atexit handler that
|
||||
// EnsureProcessTeardownSentinel() registers lazily on first registry use - by
|
||||
// then every static everywhere has finished constructing, so this handler is
|
||||
// guaranteed to run BEFORE any static destructor (atexit is LIFO). A destructor
|
||||
// hook on the registry itself was tried first and is WRONG: tests and cache
|
||||
// resets destroy temporary registry instances mid-run, which would latch the
|
||||
// flag while the process is very much alive.
|
||||
Bool InProcessTeardown();
|
||||
void EnsureProcessTeardownSentinel();
|
||||
|
||||
template <typename StateObject, typename BackendObject>
|
||||
class StateBackendObjectRegistry {
|
||||
public:
|
||||
|
||||
using StatePtr = SharedPtr<StateObject>;
|
||||
using StateWeakPtr = std::weak_ptr<StateObject>;
|
||||
using BackendPtr = SharedPtr<BackendObject>;
|
||||
@@ -43,6 +59,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
BackendPtr& GetOrCreate(const StatePtr& stateObj) {
|
||||
MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null");
|
||||
|
||||
// Twin creation is the moment a driver-owned id starts needing a guarded
|
||||
// destructor; cold path, so the once-guard costs nothing per draw.
|
||||
EnsureProcessTeardownSentinel();
|
||||
auto& entry = m_entries[stateObj.get()];
|
||||
if (entry.stateRef.expired()) {
|
||||
// The previous owner of this address is gone and the allocator handed it
|
||||
|
||||
Reference in New Issue
Block a user