diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 7a38bce0..e1841903 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -893,6 +893,12 @@ namespace MobileGL::MG_Backend::DirectGLES { DestroyEGLContext(); #if MOBILEGL_BUILD_DISAGGREGATED NoteNativeContextGone(); + // P12: under a transport this object is the SERVER's backend and its destruction is the + // end of a session (ServerLoop's apply thread, after the final drain). The in-process + // display server runs the next session in this same process, so the twins this one built + // - naming ids of the context just destroyed - must not answer for the next client's + // handles (Managers.h, DropEveryTwinForEndedServerSession). Monolith keeps its twins. + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) DropEveryTwinForEndedServerSession(); #endif } diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 6f32888a..4577f7a9 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -188,9 +188,18 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace { Bool g_processTeardown = false; std::once_flag g_teardownSentinelOnce; +#if MOBILEGL_BUILD_DISAGGREGATED + // P12: raised only while DropEveryTwinForEndedServerSession empties the twin tables, on the + // apply thread that is the only reader of the twins (Managers.h). + Bool g_serverSessionTwinTeardown = false; +#endif } // namespace +#if MOBILEGL_BUILD_DISAGGREGATED + Bool InProcessTeardown() { return g_processTeardown || g_serverSessionTwinTeardown; } +#else Bool InProcessTeardown() { return g_processTeardown; } +#endif void EnsureProcessTeardownSentinel() { std::call_once(g_teardownSentinelOnce, [] { std::atexit(+[] { g_processTeardown = true; }); }); @@ -396,6 +405,26 @@ namespace MobileGL::MG_Backend::DirectGLES { return false; } } + + // P12: see Managers.h. Every table is replaced by an empty one, holders first (views before + // the textures they view, framebuffers before their attachments, VAOs before the buffers they + // fetch from), so the last reference to each twin goes here and its destructor - answered + // InProcessTeardown() - touches neither the driver nor another TU's statics. + void DropEveryTwinForEndedServerSession() { + if (g_processTeardown) return; + g_serverSessionTwinTeardown = true; + SamplerViewImpl::g_backendSamplerViews = {}; + FramebufferImpl::g_backendFramebufferObjects = {}; + VertexArrayImpl::g_backendVertexArrayObjects = {}; + PrgramImpl::g_backendProgramObjects = {}; + SamplerImpl::g_backendSamplerObjects = {}; + TextureImpl::g_backendTextureObjects = {}; + RenderbufferImpl::g_backendRenderbufferObjects = {}; + BufferImpl::g_backendBufferResources = {}; + g_serverSessionTwinTeardown = false; + MGLOG_I("DirectGLES: the ended server session's twins are dropped (every kind, no driver call); the next " + "session in this process starts from empty twin tables"); + } #endif // The one sentence that decides the arm, written once so that a test can drive every diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index f31b3a95..296f17be 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -3608,5 +3608,26 @@ namespace MobileGL::MG_Backend::DirectGLES { // delete opcode may already have released (the idempotent-second-path shape the notice // arms document). Bool ReleaseTwinsForWireObjectDeath(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeKind kind); + + // P12 (on-screen server window): A SERVER SESSION ENDS IN A PROCESS THAT OUTLIVES IT. + // + // The twin tables above are process globals, and until P12 every server process served ONE + // session: the exec'd supervisor forks a child per session and the child exits with it, so the + // twins a session built died with its process (InProcessTeardown's deliberate leak). The + // in-process display server (mobilegl_server_serve_inprocess, the display Activity's process) + // runs sessions ONE AFTER ANOTHER IN ONE PROCESS, and the next session's client mints its + // handles from the same {slot, gen} space again. A twin the previous session left behind then + // answers for the new session's object: a program twin names a program of the DESTROYED + // context, so the link fails with an empty log and every draw no-ops (Espryt on Adreno and on + // llvmpipe alike), a stale VAO/buffer twin feeds the driver a dead name (a SIGSEGV inside + // Adreno's glDrawElements), and a stale live generation ahead of the new client's refuses its + // handle as ProtocolCorruption. + // + // So the server backend's destruction - the end of a session under a transport, on the apply + // thread, after DestroyEGLContext destroyed the context those ids belonged to - drops EVERY + // twin of every kind, with InProcessTeardown() answering true for the duration so no twin + // destructor calls into the driver (there is no current context, and the ids name nothing). + // A no-op once exit() has begun (the statics may already be gone). Monolith never calls it. + void DropEveryTwinForEndedServerSession(); #endif } // namespace MobileGL::MG_Backend::DirectGLES diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index e840edf1..c77b17ac 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -5431,3 +5431,72 @@ TEST(DirectGLESVertexInputDraw, TheIndexArmTakesItsBufferAndItsSerialFromTheReco GTEST_SKIP() << "the record arm of the index-buffer sync is compiled only under MOBILEGL_PIPE_PUSH"; } #endif // MOBILEGL_PIPE_PUSH + +// P12 (on-screen server window): A SERVER SESSION ENDS IN A PROCESS THAT OUTLIVES IT. +// +// The in-process display server (the display Activity's process) runs its sessions one after +// another in ONE process, and each new client mints its handles from the same {slot, gen} space +// again. The twin tables are process globals, so the twins the previous session built - naming ids +// of the context its backend destroyed - answered for the next session's objects: on the device +// the second on-screen OpenRA replay linked no program (ssim 0.00004) and the third crashed inside +// Adreno's glDrawElements; on llvmpipe the second replay failed the same way. The server backend's +// destruction under a transport - the end of a session - now drops every twin (without a driver +// call), and monolith keeps its twins. Red once: remove the DropEveryTwinForEndedServerSession +// call from ~BackendObject_DirectGLES and the first half of this case fails at every kind. +#if MOBILEGL_BUILD_DISAGGREGATED +TEST(EsprytServerSession, TheServerBackendsDestructionDropsEveryTwinTheSessionBuilt) { + using namespace MobileGL; + namespace GL = MG_Backend::DirectGLES; + const auto previousTransport = MG_Config::Transport; + const MG_Pipe::MGPipeHandle program{7u, 3u}; + const MG_Pipe::MGPipeHandle texture{4u, 2u}; + const MG_Pipe::MGPipeHandle buffer{5u, 1u}; + // A session's twins: LIVE entries at the generations its client minted. A null twin object is + // enough for the program and texture kinds - what the next session trips over is the live + // entry itself (its generation, and the driver id a real twin would carry). + const auto populate = [&] { + ASSERT_NE(GL::PrgramImpl::g_backendProgramObjects.GetOrCreateByHandle(program), nullptr); + ASSERT_NE(GL::TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(texture), nullptr); + GL::BufferImpl::g_backendBufferResources.GetOrCreate(buffer) = + MakeShared(); + ASSERT_EQ(GL::PrgramImpl::g_backendProgramObjects.LiveGenAt(program.Slot), 3u); + ASSERT_EQ(GL::TextureImpl::g_backendTextureObjects.LiveGenAt(texture.Slot), 2u); + ASSERT_NE(GL::BufferImpl::g_backendBufferResources.FindByHandle(buffer), nullptr); + }; + + // Under a transport this backend is the SERVER's: its destruction ends the session. + MG_Config::Transport = MG_Config::TransportMode::Spawn; + populate(); + { GL::BackendObject_DirectGLES serverBackend; } + EXPECT_EQ(GL::PrgramImpl::g_backendProgramObjects.LiveGenAt(program.Slot), 0u) + << "the ended session's program twin is still live: the next session's client mints {7, 0} " + "and is refused as ProtocolCorruption, or adopts a program of the destroyed context"; + EXPECT_EQ(GL::TextureImpl::g_backendTextureObjects.LiveGenAt(texture.Slot), 0u) + << "the ended session's texture twin is still live"; + EXPECT_EQ(GL::BufferImpl::g_backendBufferResources.FindByHandle(buffer), nullptr) + << "the ended session's buffer twin is still live"; + // The next session starts from empty tables: its first handle at that slot is a fresh twin. + MG_Pipe::MGPipeHandle nextSession{7u, 0u}; + auto* fresh = GL::PrgramImpl::g_backendProgramObjects.GetOrCreateByHandle(nextSession); + ASSERT_NE(fresh, nullptr); + EXPECT_EQ(*fresh, nullptr); + + // Monolith keeps its twins: its backend is not a session's, and nothing here changed for it. + GL::PrgramImpl::g_backendProgramObjects = {}; + MG_Config::Transport = MG_Config::TransportMode::Monolith; + populate(); + { GL::BackendObject_DirectGLES monolithBackend; } + EXPECT_EQ(GL::PrgramImpl::g_backendProgramObjects.LiveGenAt(program.Slot), 3u); + EXPECT_EQ(GL::TextureImpl::g_backendTextureObjects.LiveGenAt(texture.Slot), 2u); + EXPECT_NE(GL::BufferImpl::g_backendBufferResources.FindByHandle(buffer), nullptr); + + GL::PrgramImpl::g_backendProgramObjects = {}; + GL::TextureImpl::g_backendTextureObjects = {}; + GL::BufferImpl::g_backendBufferResources = {}; + MG_Config::Transport = previousTransport; +} +#else +TEST(EsprytServerSession, TheServerBackendsDestructionDropsEveryTwinTheSessionBuilt) { + GTEST_SKIP() << "a server backend exists only in the split build (MOBILEGL_BUILD_DISAGGREGATED)"; +} +#endif