mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-08 10:33:24 +09:00
[MobileGL, MG_Backend, MG_Remote, scripts] (P7): the split wire comes up before the first frontend object, and the caps mirror stops deciding a family's fate from a placeholder
F1. `MG_State::Init()` runs before `MG_Backend::Init()`, and the second is what
starts the session - so `GLContext`'s `TextureState` constructor materialised its
eleven default texture objects, each of them emitted `resource_create` through
`FamilyIsLive`, and `CapsMirror::ServerConsumes(0x400)` answered every one of them
from a generation-0 placeholder with `callMask=0`. All eleven records were dropped
and the whole event was one WARN line:
MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this
family emits NOTHING and the legacy pull path runs for it (R-8). callMask=0x0,
caps generation 0
It is NOT a race. The line reproduces byte-for-byte on lavapipe and on the Adreno
830, because the two Init calls are two statements on one thread. And "the legacy
pull path runs for it" is false on the client under split: there is no pull path
on this side of the wire, so withholding is not a fallback, it is a drop that only
a later respecify's self-healing create ever undoes.
Two of the three options the brief offered do not apply, and both facts are worth
recording. (b) "extend the P6.5 fix to inproc" is already the tree: `FinishStartup`
step 7 is the one funnel every transport reaches and it will not return without a
real snapshot. (a) "a bounded wait in Consumes()" cannot work alone and would
deadlock: at `MG_State::Init()` no handshake is in flight, and the one that will
land is started later BY THE SAME THREAD.
So the fix is the ORDER, with the rule behind it as the gate:
* `MobileGL::Initialize` calls `MG_Backend::InitSplitRolesBeforeState()` before
`MG_State::Init()`. `Init()`'s split arm becomes one body, `InitSplitBackend()`,
shared with the direct callers that still reach `Init()`; the monolith arm keeps
its place after `MG_State::Init()`, because moving it would move pull `.text`.
* `CapsMirror::ServerConsumes()` refuses to answer from a placeholder. It waits,
bounded, when a bring-up is genuinely in flight on ANOTHER thread, and otherwise
dies as `Fatal{CapsBeforeFirstSnapshot, "<family>"}` through `SessionFail` -
a new family word, one row in `FatalFamilies.def`, no new abort site.
* Two arms are exempt and say why in place: the server arm (`MGPipeServerArm()` -
the apply thread and the spawn server, where the client's mirror is the wrong
object rather than a late one), and a process whose own configuration never
asked for a split transport (`MG_Config::SplitTransportRequestedByConfig`, set
by `MG_ConfigLoader::InitTransport`, which is what tells a real run apart from a
fixture that assigns `MG_Config::Transport` by hand).
The read accessors keep P5's ruling untouched: `LogBackendInfo` reads
`GetRendererInfo()` during `MG_Backend::Init()` and an abort there is a process that
cannot start. Only the DECISION changed. The warning was not made quieter and the
callMask was not defaulted to all-ones.
`MOBILEGL_TEST_DELAY_FIRST_CAPS_MS` (server side, disaggregated builds, inert unless
set) holds back the FIRST CapsSnapshot. On spawn/tcp that is a genuinely late
snapshot in another process and it exercises the client's step-7 wait; on inproc it
only makes `Start` slower, which is itself the proof that the only window a client
can read a placeholder in is the one before `Start` is called.
G1: `.text` 0xa52203 and the 30570-symbol list identical to p7-before/pull-syms.txt.
Census: 79 abort sites unchanged, 44 family words (was 43). Link ratchet: 186,
unchanged.
This commit is contained in:
@@ -24,8 +24,10 @@
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
// CONTRACT-P6 4.3: the one value in the handshake that a same-process session cannot fake.
|
||||
@@ -38,6 +40,42 @@
|
||||
namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
namespace {
|
||||
// F1 (P7 wave 2). THE ONLY KNOB THAT CAN MAKE THE FIRST CapsSnapshot LATE.
|
||||
//
|
||||
// MOBILEGL_TEST_DELAY_FIRST_CAPS_MS delays the server's FIRST publication, and only
|
||||
// it - a re-send (R-12) is never delayed, because the question is about the window
|
||||
// before the client has any mask at all. Read once, on the first Accept, so a lane
|
||||
// that does not set it pays one getenv per session and nothing else.
|
||||
//
|
||||
// WHAT IT PROVES, AND WHERE. Under spawn / unix: / tcp:// the publication happens in
|
||||
// the SERVER's process, so the delay is a genuinely late snapshot from the client's
|
||||
// point of view and it exercises the client's step-7 wait (ClientSession.cpp:1284).
|
||||
// Under inproc there is no second process: Accept runs inside ClientSession::Start on
|
||||
// the client's own thread, so the delay simply makes Start slower - which is itself
|
||||
// the proof that under inproc the first snapshot CANNOT be late relative to session
|
||||
// start, and therefore that the only window in which the client could read a
|
||||
// placeholder is the one BEFORE Start is called. That window is F1's defect, and
|
||||
// MobileGL::Initialize closes it by ordering rather than by waiting.
|
||||
//
|
||||
// It is a TEST knob: it exists only in a disaggregated build and does nothing at all
|
||||
// unless it is set, so no production path can reach a delay it did not ask for.
|
||||
void DelayFirstCapsSnapshotForTest() {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
static Bool spent = false;
|
||||
if (spent) return;
|
||||
spent = true;
|
||||
const char* text = std::getenv("MOBILEGL_TEST_DELAY_FIRST_CAPS_MS");
|
||||
if (text == nullptr || *text == '\0') return;
|
||||
const long ms = std::strtol(text, nullptr, 10);
|
||||
if (ms <= 0) return;
|
||||
MGLOG_W("MG_Remote server: MOBILEGL_TEST_DELAY_FIRST_CAPS_MS=%ld - the FIRST "
|
||||
"CapsSnapshot is held back by that many milliseconds. This is F1's "
|
||||
"test-only lever and must never be set in a measured run",
|
||||
ms);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
|
||||
#endif
|
||||
}
|
||||
|
||||
// File-local on purpose: two callers, both handshake facts. A process-id helper on a
|
||||
// public header invites uses that are not.
|
||||
std::uint32_t SelfProcessId() {
|
||||
@@ -615,6 +653,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
// ---- 6. the first CapsSnapshot, if there is a backend to take it from.
|
||||
if (m_backend != nullptr) {
|
||||
DelayFirstCapsSnapshotForTest();
|
||||
const MobileGLResult published = PublishCapsSnapshot();
|
||||
if (published != MOBILEGL_OK) {
|
||||
return published;
|
||||
|
||||
Reference in New Issue
Block a user