From c60fcc17c5b2f3fc87ee4fef4b5cc051f75419bc Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 23:59:55 -0400 Subject: [PATCH] [Fix] (Pipe): the device-loss debug knob re-arms when set to a new value. --- MobileGL/MG_Pipe/PipeApply.cpp | 15 +++++++++++- MobileGL/MG_Pipe/PipeSessionFail.h | 6 +++-- MobileGL/MG_Test/Wire/FatalFamilyTest.cpp | 30 +++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index d6f3a439..db0d5699 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -186,9 +186,22 @@ namespace MobileGL::MG_Pipe { } } #endif - if (at <= 0) return false; + static std::atomic armedAt{0}; static std::atomic seen{0}; static std::atomic fired{false}; + if (at <= 0) { + // Unset (or 0) disarms: the same value set again counts afresh. + if (armedAt.load(std::memory_order_relaxed) != 0) armedAt.store(0, std::memory_order_relaxed); + return false; + } + // A NEW VALUE RE-ARMS. One server process outlives many sessions, so a second loss is + // injected by setting the knob again (another value, or 0 and then a value): the count + // restarts from the checks made after it was seen. + if (long previous = armedAt.load(std::memory_order_acquire); + previous != at && armedAt.compare_exchange_strong(previous, at, std::memory_order_acq_rel)) { + seen.store(0, std::memory_order_release); + fired.store(false, std::memory_order_release); + } if (fired.load(std::memory_order_acquire)) return false; if (seen.fetch_add(1, std::memory_order_acq_rel) + 1 < at) return false; if (fired.exchange(true, std::memory_order_acq_rel)) return false; diff --git a/MobileGL/MG_Pipe/PipeSessionFail.h b/MobileGL/MG_Pipe/PipeSessionFail.h index dd63dd84..3e8ca55b 100644 --- a/MobileGL/MG_Pipe/PipeSessionFail.h +++ b/MobileGL/MG_Pipe/PipeSessionFail.h @@ -119,8 +119,10 @@ namespace MobileGL::MG_Pipe { // A DEBUG KNOB THAT MAKES ONE DEVICE CHECK REPORT A LOSS, so the containment above can be // driven without a GPU that faults on demand. MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT=N (or, on // Android, the property debug.mobilegl.inject_device_lost_at) makes the N-th device check the - // backends make after the knob was first seen nonzero answer "lost", once per process. The - // backends ask at their frame boundaries and readbacks; unset (the default) it is one getenv. + // backends make after the knob was first seen nonzero answer "lost", once per value: setting + // another value (or 0 and then a value again) re-arms it, so one server process can lose + // several sessions in turn. The backends ask at their frame boundaries and readbacks; unset + // (the default) it is one getenv. bool MGPipeDebugDeviceLossDue(); #endif diff --git a/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp b/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp index fd1aadee..bcd158f2 100644 --- a/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp +++ b/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp @@ -250,3 +250,33 @@ TEST(FatalFunnelSeam, ADeviceLossWithNoArmedSessionDiesThroughSessionFail) { << "an unarmed device loss must keep the inproc shape's death"; #endif } + +#if !defined(_WIN32) +// THE DEBUG KNOB RE-ARMS ON A NEW VALUE. One server process serves many sessions, so a device test +// that loses a second session in the same process sets the knob again. Each value fires once, at its +// N-th check; the same value set again after 0 counts afresh. In a death-test child, so the knob's +// process-wide counters start at zero. Red with the old once-per-process latch: the second value +// never fires (exit code 2). +TEST(FatalFunnelSeam, TheDeviceLossKnobFiresOncePerValueAndReArmsOnANewOne) { + EXPECT_EXIT( + { + const auto checks = [](int n) { + int fired = 0; + for (int i = 0; i < n; ++i) fired += MobileGL::MG_Pipe::MGPipeDebugDeviceLossDue() ? 1 << i : 0; + return fired; + }; + ::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT"); + if (checks(3) != 0) std::_Exit(1); + ::setenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT", "2", 1); + if (checks(4) != 0b0010) std::_Exit(1); // the 2nd check only, then never again + ::setenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT", "1", 1); + if (checks(3) != 0b001) std::_Exit(2); // a new value re-arms + ::setenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT", "0", 1); + if (checks(2) != 0) std::_Exit(3); + ::setenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT", "1", 1); + if (checks(2) != 0b01) std::_Exit(4); // 0 then the same value again re-arms too + std::_Exit(0); + }, + ::testing::ExitedWithCode(0), ""); +} +#endif