mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-16 06:23:23 +09:00
[MG_Pipe, MG_Backend, MG_Impl] (P6/sm, P6/st): four guards that were compiled in and could never arm, and the crash behind one of them
CONTRACT-P6 3.2 (D1c), 3.3 (D13) and 3.4 (D10).
Every guard below asked a question a SERVER PROCESS answers wrongly - "is a
ClientSession active" (never, there) or "am I on the apply thread" (every
thread is, there) - so all four were present, compiled, and permanently
disarmed in the one shape they exist for. That is worse than absent: it reads
as coverage.
D13 FIRST, THEN THE WIDENING. MGPipeRoleSplitActive() meant "the REHEARSAL
knob is on AND the transport is real", so widening it in place would have left
a narrow name describing a wide predicate. It is renamed to
MGPipeRoleSplitRehearsalActive() across its 12 sites - including the
DualBlockScenario assertion message that embeds it - and the wider predicate
is introduced BESIDE it.
MGPipeBlocksAreDistinct() IS TRUE FOR TWO DIFFERENT REASONS, which is the
whole point: under the rehearsal the two blocks are distinct objects in one
process; under SPAWN they are distinct because they are in different ADDRESS
SPACES, and no knob is involved. The spawn retrace runs with
role-split-state=0 - measured off the lane's own log - so every guard gated on
the rehearsal was off there.
THE CRASH. MGPipeServerBlockNoteIdentity early-returned on the rehearsal, so a
spawn server's gPipeInputs.ContextIdentity() stayed nullptr for the life of the
process. DirectGLES' framebuffer binding-slot memo compares that identity FIRST
and with NO generation, against a cache variable that also starts nullptr - so
nullptr == nullptr reads as a CACHE HIT and the function returns a slot pointer
nothing ever filled. `sm` widens the gate; `st` lands the named Fatal
PipeInputs.h had already asked for, so a reopening says which memo and which
identity instead of corrupting memory three frames later. Guarded on
MOBILEGL_PIPE_PUSH: in a pull build that identity is the live GLContext's
address and is never null, so the test would be dead code and G1 measures that.
DISJUNCTS, NOT SUBSTITUTIONS - and this was measured the hard way. The first
attempt REPLACED OnApplyThread() with MGPipeServerArm() and
ClientSession::Active() with MGPipeSessionLive(). 14 RemoteGuards and
RemoteClientControls cases went green by omission: a ServerLoop fixture with no
client session has only the original facts, so swapping them out disarmed every
test that drives the apply thread directly. The originals stay; the new
predicates are added beside them.
AND THE PROBE HAD TO BE THE RIGHT ONE. Installing
ClientSession::ApplyThreadIsInsideApplier as the apply-thread probe made every
GL-thread call a role violation whenever the applier happened to be busy - the
inproc retrace died on Fatal{RoleViolation, "MGPipeSlots"} raised from the GL
thread. "Am I the apply thread" and "is the apply thread inside the applier"
are different questions; the second is R-1's mutual-exclusion probe and is
about another thread entirely. ServerLoop::OnApplyThread is the one the guards
mean.
Three of D1c's six predicates land here, the three the four guards need. The
other three (MGPipeSplitActive, MGPipeShouldDial, MGPipeBackendIsLocal) have no
D10 site and are not added as dead code; their call sites are ContextEpoch and
ActiveXfbState, which this commit does not touch.
D14 holds: the non-disaggregated build gets constexpr false definitions, not
runtime-false predicates, so every consult folds at compile time. G1
re-measured - 0 symbol diff, .text unchanged at 0xa52203.
Two test names added, both falsifiable by construction: the spawn case asserts
the blocks are distinct with the rehearsal knob OFF, and the monolith case
asserts the predicate is not simply a constant. Unit suite 2353 tests, one
pre-existing environmental failure. All six OpenRA arms green.
This commit is contained in:
@@ -171,7 +171,10 @@ namespace MobileGL::MG_Pipe {
|
||||
// crash exactly where the rehearsal exists to produce a named one. The red is the
|
||||
// census (Harness/dualblock-expected-fatals.txt), so the marker keeps the strict
|
||||
// grammar with the knob's own name as the reason.
|
||||
if (MGPipeRoleSplitActive()) {
|
||||
// D10: rides MGPipeBlocksAreDistinct() - under spawn the two blocks are distinct
|
||||
// whatever the rehearsal knob says, and a barrier pull across a real process
|
||||
// boundary is exactly the thing this Fatal exists to name.
|
||||
if (MGPipeBlocksAreDistinct()) {
|
||||
StrictBarrierPullFatal(field, verb, "MOBILEGL_IPC_ROLE_SPLIT_STATE=1");
|
||||
}
|
||||
if (!MGPipeApplierCurrentRecordIsBarriered()) {
|
||||
@@ -286,16 +289,54 @@ namespace MobileGL::MG_Pipe {
|
||||
|
||||
// ---- P5f (f1): the dual-block rehearsal's selection functions (PipeInputs.h) -----------
|
||||
//
|
||||
// MGPipeRoleSplitActive is deliberately NOT latched: it is two global loads, asked once per
|
||||
// MGPipeRoleSplitRehearsalActive is deliberately NOT latched: it is two global loads, asked once per
|
||||
// verb on the fill side and once per verb boundary on the stamp side, and a latch is a
|
||||
// second thing a test that flips the knob mid-process would have to know about.
|
||||
Bool MGPipeRoleSplitActive() {
|
||||
Bool MGPipeRoleSplitRehearsalActive() {
|
||||
return MG_Config::Ipc.RoleSplitState &&
|
||||
MG_Config::Transport != MG_Config::TransportMode::Monolith;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// ---- D1c's predicates (CONTRACT-P6 3.2) -------------------------------------------------
|
||||
namespace {
|
||||
// Set once by ServerMain, before any GL work. A spawn server is the server for the whole
|
||||
// life of the process; there is no thread in it that is not.
|
||||
Bool g_serverProcessRole = false;
|
||||
// MG_Remote's "is the calling thread inside the applier". Null until a session installs
|
||||
// it, which is the honest answer for a process that has no applier.
|
||||
Bool (*g_applyThreadProbe)() = nullptr;
|
||||
Bool g_sessionLive = false;
|
||||
} // namespace
|
||||
|
||||
void MGPipeSetServerProcessRole(Bool isServerProcess) { g_serverProcessRole = isServerProcess; }
|
||||
void MGPipeSetApplyThreadProbe(Bool (*probe)()) { g_applyThreadProbe = probe; }
|
||||
void MGPipeSetSessionLive(Bool live) { g_sessionLive = live; }
|
||||
|
||||
Bool MGPipeServerArm() {
|
||||
// THE PROCESS FACT FIRST, and it short-circuits: in a spawn server every thread is a
|
||||
// server thread, and asking the probe would be asking MG_Remote a question it answers
|
||||
// only about the applier.
|
||||
if (g_serverProcessRole) return true;
|
||||
return g_applyThreadProbe != nullptr && g_applyThreadProbe();
|
||||
}
|
||||
|
||||
Bool MGPipeSessionLive() { return g_sessionLive; }
|
||||
|
||||
Bool MGPipeBlocksAreDistinct() {
|
||||
// TWO DIFFERENT REASONS, ONE ANSWER. The rehearsal makes two blocks in one process;
|
||||
// spawn makes them two blocks in two ADDRESS SPACES, where no knob is involved and the
|
||||
// distinctness is a fact about the machine. The guards that used to ask only the first
|
||||
// question were therefore disarmed in the one shape where the answer matters most - a
|
||||
// spawn server's block is never the client's, whatever MOBILEGL_IPC_ROLE_SPLIT_STATE
|
||||
// says.
|
||||
return MGPipeRoleSplitRehearsalActive() ||
|
||||
MG_Config::Transport == MG_Config::TransportMode::Spawn;
|
||||
}
|
||||
#endif // MOBILEGL_BUILD_DISAGGREGATED
|
||||
|
||||
PipeInputs& MGPipeClientInputs() {
|
||||
return MGPipeRoleSplitActive() ? gPipeInputsClientBlock : gPipeInputs;
|
||||
return MGPipeRoleSplitRehearsalActive() ? gPipeInputsClientBlock : gPipeInputs;
|
||||
}
|
||||
|
||||
void MGPipeClientClearVerbBoundary() {
|
||||
@@ -312,7 +353,15 @@ namespace MobileGL::MG_Pipe {
|
||||
Bool MGPipeServerContextIsLive() { return g_serverContextLive; }
|
||||
|
||||
void MGPipeServerBlockNoteIdentity() {
|
||||
if (!MGPipeRoleSplitActive()) return;
|
||||
// D10: THE WIDER PREDICATE, and the narrow one was a latent crash rather than a missing
|
||||
// optimisation. Gated on the REHEARSAL, this early-returned for the whole life of a
|
||||
// spawn server - because that knob is off by default and nothing in the spawn shape
|
||||
// turns it on - so the server's gPipeInputs.ContextIdentity() stayed nullptr forever.
|
||||
// DirectGLES' fb-slot memo compares that identity FIRST and with no generation, so a
|
||||
// nullptr against its own nullptr initialiser reads as a CACHE HIT and hands out a slot
|
||||
// that was never filled. `st` lands the named Fatal at that site; this is the gate that
|
||||
// stops it being reachable in the first place.
|
||||
if (!MGPipeBlocksAreDistinct()) return;
|
||||
// The server cannot name the client's GLContext - under a real transport it is in
|
||||
// another process - so the identity the server block carries is the server's OWN
|
||||
// served-context clock: MGPipeApplierContextSerial moves exactly when the served
|
||||
|
||||
Reference in New Issue
Block a user