From b32140e614cb21e752aab2d695db40b376aef22c Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 29 Sep 2026 20:15:29 -0400 Subject: [PATCH] [Feat] (MG_Remote, P11 B2): T0 is the default - an unset MOBILEGL_IPC_ADOPT_TIER asks T0 (ask 0x80) and falls back to T2 quietly (MGLOG_D, still counted: client T0Fallbacks, server T0Refusals), while an explicit 0 keeps every named line; revision 5's schema comment and pin fixed up --- MobileGL/Config.h | 17 +++-- MobileGL/ConfigLoader.cpp | 12 ++-- MobileGL/MG_Remote/Client/ClientSession.cpp | 46 +++++++++--- MobileGL/MG_Remote/Client/ClientSession.h | 6 ++ .../MG_Remote/Client/PersistentMapTracker.cpp | 4 ++ MobileGL/MG_Remote/Protocol/protocol.fbs | 5 +- MobileGL/MG_Remote/Server/ServerSession.cpp | 71 +++++++++++++------ MobileGL/MG_Remote/Server/ServerSession.h | 8 +++ MobileGL/MG_Remote/Transport/AdoptTier.cpp | 36 +++++++--- MobileGL/MG_Remote/Transport/AdoptTier.h | 31 +++++--- scripts/ci/protocol_revision_pins.json | 2 +- 11 files changed, 173 insertions(+), 65 deletions(-) diff --git a/MobileGL/Config.h b/MobileGL/Config.h index dced182b..2d61289b 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -500,6 +500,9 @@ namespace MobileGL::MG_Config { Offscreen = 0, // a window surface is the client's own window, as before (the default) Server = 1, // a window surface is the SERVER's window: WindowKind::ServerOwned }; + // IpcTable::AdoptTier when MOBILEGL_IPC_ADOPT_TIER is unset (or unparseable): T0 by default, + // with QUIET fallbacks. Not a tier a user can type - the knob admits 0..2. + inline constexpr Uint32 kAdoptTierUnset = 3; struct IpcTable { // MOBILEGL_IPC_SERVER_PATH: where to find libMobileGLServer. P6 consumes it; P5 // lands the parse because t1's ctest ENVIRONMENT blocks and add_trace_replay_test's @@ -626,13 +629,13 @@ namespace MobileGL::MG_Config { // arm and the red-once of the no-reply gate (FencePollScenario). Uint32 PollEscalate = 64; // MOBILEGL_IPC_ADOPT_TIER, THE ZERO-COPY SWITCH (ruling ID-P11-14, CONTRACT-P11 B2): - // 2 = emulate (T2: the client keeps the shadow and pushes), the default and today's - // behaviour byte for byte; 0 = T0 (the client allocates each adopted store as an - // AHardwareBuffer and the server imports it); 1 = T1, closed - refused by name, then T2. - // A T0 this session cannot use (a stream plane, the server's allow switch, no - // AHardwareBuffer, a failed server POST) is T2 plus one named line, never a Fatal - // (MG_Remote/Transport/AdoptTier.h). - Uint32 AdoptTier = 2; + // UNSET (kAdoptTierUnset, the default since 2026-09-29, the user's decision) = T0 where the + // session can run it, T2 QUIETLY where it cannot (MGLOG_D only, still counted); + // 0 = T0 asked by name - a T0 this session cannot use (a stream plane, the server's allow + // switch, no AHardwareBuffer, a failed server POST) is T2 plus one named line; + // 1 = T1, closed - refused by name, then T2; 2 = emulate (T2: the client keeps the shadow + // and pushes). Never a Fatal (MG_Remote/Transport/AdoptTier.h). + Uint32 AdoptTier = kAdoptTierUnset; // MOBILEGL_IPC_ALLOW_ADOPT_T0 (server): 1 (default) = grant a client's T0 ask when this // platform and the backend's POST self-test allow it; 0 = never grant it (the session // runs T2 and says so). Read by the server role only; kept through ServerSpawn. diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 0785f384..d02abdfd 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -433,9 +433,10 @@ namespace MobileGL::MG_ConfigLoader { // torn read rather than a divergence. The batch is therefore off whenever the // shadow comparer is armed. if (MG_Config::Features.PipeVerify) ipc.BatchWaits = 0; - // 2 (T2) is the default; 0 asks for T0 and 1 is refused by name - both settled at the - // handshake from the data plane (P11 A1/B2, MG_Remote/Transport/AdoptTier.h). - ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", 2, 0, 2); + // UNSET is T0 with quiet fallbacks (the default since 2026-09-29); 0 asks for T0 by name, 1 is + // refused by name, 2 is T2 - settled at the handshake from the data plane (P11 A1/B2, + // MG_Remote/Transport/AdoptTier.h). An unparseable value is warned about and reads as unset. + ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", MG_Config::kAdoptTierUnset, 0, 2); // P11 B2: the server's own T0 allow switch (Config.h). ipc.AllowAdoptT0 = QueryEnvUint32("MOBILEGL_IPC_ALLOW_ADOPT_T0", 1, 0, 1); ipc.VerbBarrier = QueryEnvUint32("MOBILEGL_IPC_VERB_BARRIER", 1, 0, 1); @@ -532,10 +533,11 @@ namespace MobileGL::MG_ConfigLoader { // log that does not name it makes every run ambiguous about its own configuration. MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB wire-deferred=%uMiB spin=%uus event-wait=%ums " "persistent-block=%uKiB create-window=%u pbo-readback-sync=%u poll-escalate=%u " - "adopt-tier=%u allow-t0=%u verb-barrier=%u run-ahead=%u present-credit=%u control-timeout=%ums " + "adopt-tier=%s allow-t0=%u verb-barrier=%u run-ahead=%u present-credit=%u control-timeout=%ums " "cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s", ipc.RingMb, ipc.StageMb, ipc.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs, - ipc.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate, ipc.AdoptTier, + ipc.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate, + ipc.AdoptTier == 0 ? "0" : ipc.AdoptTier == 1 ? "1" : ipc.AdoptTier == 2 ? "2" : "unset(T0)", ipc.AllowAdoptT0, ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.ColdStartMs, static_cast(ipc.StrictErrors), static_cast(ipc.Audit), diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index b4f0da33..76ba841c 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -823,7 +823,10 @@ namespace MobileGL::MG_Remote::Client { return result; } ResetT0(false); - m_adoptT0Asked = adoptAsk == 0; + m_adoptT0Asked = Transport::AdoptAskIsT0(adoptAsk); + m_adoptT0Quiet = Transport::AdoptTierIsDefault(); + if (terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream && Transport::AdoptTierWantsT0()) + m_t0Fallbacks = 1; // a stream carries T2 only (the settle said so, by name or at D) // The four SegmentRefs are what a spawn client MAPS (P6). Under inproc the mapping // already exists, so what they are good for here is the cross-check that the two // sides agree about the geometry at all - which is the assertion that would @@ -1127,7 +1130,10 @@ namespace MobileGL::MG_Remote::Client { return result; } ResetT0(false); - m_adoptT0Asked = adoptAsk == 0; + m_adoptT0Asked = Transport::AdoptAskIsT0(adoptAsk); + m_adoptT0Quiet = Transport::AdoptTierIsDefault(); + if (stream && Transport::AdoptTierWantsT0()) + m_t0Fallbacks = 1; // a stream carries T2 only (the settle said so, by name or at D) if (stream) { // PH-7 (4). A Stream Welcome without a nonce of exactly the minted width names no // data connection this client could open; refused rather than guessed at. @@ -2728,6 +2734,13 @@ namespace MobileGL::MG_Remote::Client { "AHardwareBuffers the server imports"); } else { m_adoptT0Decision = 0; + ++m_t0Fallbacks; + if (m_adoptT0Quiet) { + // T0 BY DEFAULT (knob unset) FALLS BACK QUIETLY - counted, said at MGLOG_D. + MGLOG_D("MG_Remote client: T0 by default is not available (no kCapAdoptT0; the server's log " + "says why at MGLOG_D); the session runs T2"); + return false; + } MGLOG_W("MG_Remote client: Refuse{AdoptT0Unavailable, \"no kCapAdoptT0\"} - " "MOBILEGL_IPC_ADOPT_TIER=0 asked for T0 and the server did not publish kCapAdoptT0 (its " "log names why: its allow switch, no AHardwareBuffer, or its POST self-test); the " @@ -2853,21 +2866,34 @@ namespace MobileGL::MG_Remote::Client { } void ClientSession::ResetT0(Bool releaseStores) { - if (m_adoptT0Asked) { - MGLOG_I("MG_Remote client: T0 session totals - %s; stores imported %llu, bytes %llu, declined %llu, " - "released %llu, held at teardown %zu", - m_adoptT0Decision == 1 ? "ran T0" : m_adoptT0Decision == 0 ? "fell back to T2" : "never decided", - static_cast(m_t0Imported), static_cast(m_t0Bytes), - static_cast(m_t0Declined), static_cast(m_t0Released), - m_t0Stores.size()); + if (m_adoptT0Asked || m_t0Fallbacks != 0) { + // A default (unset-knob) session that did not run T0 says its totals at MGLOG_D. + const bool quiet = m_adoptT0Quiet && m_adoptT0Decision != 1; + const char* ran = + m_adoptT0Decision == 1 ? "ran T0" : m_adoptT0Decision == 0 || m_t0Fallbacks ? "fell back to T2" : "never decided"; +#define MGL_T0_CLIENT_TOTALS(LOG) \ + LOG("MG_Remote client: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ + "fallbacks %llu, released %llu, held at teardown %zu", \ + ran, m_adoptT0Quiet ? " (default)" : "", static_cast(m_t0Imported), \ + static_cast(m_t0Bytes), static_cast(m_t0Declined), \ + static_cast(m_t0Fallbacks), static_cast(m_t0Released), \ + m_t0Stores.size()) + if (quiet) { + MGL_T0_CLIENT_TOTALS(MGLOG_D); + } else { + MGL_T0_CLIENT_TOTALS(MGLOG_I); + } +#undef MGL_T0_CLIENT_TOTALS + (void)ran; } if (releaseStores) { for (auto& [key, store] : m_t0Stores) Transport::AdoptT0::ReleaseHeld(store); } m_t0Stores.clear(); m_adoptT0Asked = false; + m_adoptT0Quiet = false; m_adoptT0Decision = -1; - m_t0Imported = m_t0Bytes = m_t0Declined = m_t0Released = 0; + m_t0Imported = m_t0Bytes = m_t0Declined = m_t0Released = m_t0Fallbacks = 0; m_prePublish = nullptr; } diff --git a/MobileGL/MG_Remote/Client/ClientSession.h b/MobileGL/MG_Remote/Client/ClientSession.h index 16f920eb..0670d491 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.h +++ b/MobileGL/MG_Remote/Client/ClientSession.h @@ -252,6 +252,10 @@ namespace MobileGL::MG_Remote::Client { Uint64 T0StoresImported() const { return m_t0Imported; } Uint64 T0BytesImported() const { return m_t0Bytes; } Uint64 T0Declined() const { return m_t0Declined; } + // 1 when this session's knob asked for T0 (0, or unset) and it runs T2 instead: a stream + // data plane, or no kCapAdoptT0 at the first map_persistent. Counted whether the fallback + // was named (knob 0) or quiet (unset, the default). + Uint64 T0Fallbacks() const { return m_t0Fallbacks; } // Test seam: 0 = queue each Offer before its record (the product order); > 0 = queue it // that many ms AFTER the record is published, from another thread; < 0 = never queue it. // What the host tests use to prove a late Offer still lands and a lost one is a named @@ -595,12 +599,14 @@ namespace MobileGL::MG_Remote::Client { // the counters; and the one-shot hook EmitAndWaitTails runs between EncodeRecord (the // seq exists) and PublishAndNotify (the record is visible) - where the Offer is queued. Bool m_adoptT0Asked = false; + Bool m_adoptT0Quiet = false; // the knob is unset: fallbacks at MGLOG_D Int32 m_adoptT0Decision = -1; std::unordered_map m_t0Stores; Uint64 m_t0Imported = 0; Uint64 m_t0Bytes = 0; Uint64 m_t0Declined = 0; Uint64 m_t0Released = 0; + Uint64 m_t0Fallbacks = 0; std::function m_prePublish; void ResetT0(Bool releaseStores); }; diff --git a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp index 1991de80..8fa63e8b 100644 --- a/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp +++ b/MobileGL/MG_Remote/Client/PersistentMapTracker.cpp @@ -1247,6 +1247,10 @@ namespace MobileGL::MG_Remote::Client { Bool AdoptTierIsEmulate() { const Uint32 tier = MG_Config::Ipc.AdoptTier; if (tier == 2) return true; + // P11 B2: UNSET (T0 by default) reaching the T2 path - settled or not - is T2, quietly: a + // session-free caller (a unit case, a harness) runs the P5 default it always ran, and a + // settled session already counted its fallback at the handshake or its first map_persistent. + if (tier == MG_Config::kAdoptTierUnset) return true; // P11 A1 (CONTRACT-P11 ยง1): THE VERDICT IS THE HANDSHAKE'S, NOT THIS CALL'S. Both roles // settle the tier from the knob and the data plane before the first record // (Transport/AdoptTier.cpp): a stream refuses T0/T1 by name and runs T2, a client over diff --git a/MobileGL/MG_Remote/Protocol/protocol.fbs b/MobileGL/MG_Remote/Protocol/protocol.fbs index e9be0e01..d4cf2852 100644 --- a/MobileGL/MG_Remote/Protocol/protocol.fbs +++ b/MobileGL/MG_Remote/Protocol/protocol.fbs @@ -71,8 +71,9 @@ table LinkTerms { eventWindowBytes: ulong; // P11 B2 (MG_Remote/CONTRACT-P11.md B2), appended. In the Hello: the persistent-map adoption // tier the client ASKS for - 0 = T0 (it allocates each adopted store as an AHardwareBuffer and - // hands it over the aux socket), 2 = T2 (emulate: shadow + push). 1 (T1) is closed and never - // sent; a stream plane always asks 2. In the Welcome: the server's echo of the ask it + // hands it over the aux socket), 0x80 = T0 by DEFAULT (the client's knob is unset: the same T0, + // but the server names a refusal only at debug level), 2 = T2 (emulate: shadow + push). 1 (T1) + // is closed and never sent; a stream plane always asks 2. In the Welcome: the server's echo of the ask it // recorded. It is not the tier: T0 needs the server's POST self-test, which runs at the first // native bind (the GPU context does not exist yet), and is published as kCapAdoptT0 in the // CapsSnapshot that bind republishes. Absent (an older peer) reads 2. diff --git a/MobileGL/MG_Remote/Server/ServerSession.cpp b/MobileGL/MG_Remote/Server/ServerSession.cpp index 2f3d579b..faa7ac28 100644 --- a/MobileGL/MG_Remote/Server/ServerSession.cpp +++ b/MobileGL/MG_Remote/Server/ServerSession.cpp @@ -686,16 +686,20 @@ namespace MobileGL::MG_Remote::Server { // for, before a byte of Welcome. A stream refuses T0/T1 by name and serves T2; over // shared segments the client settles it (Transport/AdoptTier.h). 0/1 never die here. (void)Transport::SettleAdoptTierAtHandshake(stream, Transport::AdoptTierSide::Server); - // ---- P11 B2: the client's ask (LinkTerms.adoptTier). 2 or, over shared segments, 0; - // anything else is not an ask this revision knows. Recorded, echoed in the Welcome, and - // granted or refused by name at the first native bind (SettleAdoptT0AtBind). - if (terms->adoptTier() != 2 && !(terms->adoptTier() == 0 && !stream)) + // ---- P11 B2: the client's ask (LinkTerms.adoptTier). 2 or, over shared segments, 0 (T0 + // by name) or kAdoptAskT0Default (T0 by default: refusals quiet); anything else is not an + // ask this revision knows. Recorded, echoed in the Welcome, and granted or refused at the + // first native bind (SettleAdoptT0AtBind). + if (terms->adoptTier() != 2 && !(Transport::AdoptAskIsT0(terms->adoptTier()) && !stream)) return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "adoptTier is neither 2 nor, over shared segments, 0", 2, terms->adoptTier()); - m_adoptT0Asked = terms->adoptTier() == 0; + "adoptTier is neither 2 nor, over shared segments, a T0 ask (0 or 0x80)", 2, + terms->adoptTier()); + m_adoptAsk = terms->adoptTier(); + m_adoptT0Asked = Transport::AdoptAskIsT0(m_adoptAsk); + m_adoptT0Quiet = m_adoptAsk == Transport::kAdoptAskT0Default; m_adoptT0Settled = false; m_adoptT0 = false; - m_t0Stores = m_t0Bytes = m_t0Declined = 0; + m_t0Stores = m_t0Bytes = m_t0Declined = m_t0Refusals = 0; m_adoptInbox.Clear(); m_adoptInbox.Attach(&transport); @@ -794,7 +798,7 @@ namespace MobileGL::MG_Remote::Server { } auto negotiated = ::MobileGL::Wire::CreateLinkTerms(builder, terms->dataPlane(), ::MobileGL::Wire::WireForm::StructImage, maxReply, cmdWindow, stageWindow, eventWindow, - static_cast(m_adoptT0Asked ? 0 : 2)); + static_cast(m_adoptT0Asked ? m_adoptAsk : 2)); auto welcome = ::MobileGL::Wire::CreateWelcome( builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, // CONTRACT-P6 4.3: THE SERVER'S OWN PID, not an echo of the client's. @@ -1030,17 +1034,29 @@ namespace MobileGL::MG_Remote::Server { // P11 B2: the T0 arm's proof for this session, then the inbox's leftovers (an Offer whose // record never came; its client has released the store on the session's end). if (m_accepted && m_adoptT0Asked) { - MGLOG_I("MG_Remote server: T0 session totals - %s; stores imported %llu, bytes %llu, declined %llu, " - "stale offers %llu, malformed %llu, unclaimed %zu", - m_adoptT0 ? "ran T0" : m_adoptT0Settled ? "not granted (T2)" : "no native bind (T2)", - static_cast(m_t0Stores), static_cast(m_t0Bytes), - static_cast(m_t0Declined), - static_cast(m_adoptInbox.StaleOffers()), - static_cast(m_adoptInbox.MalformedOffers()), m_adoptInbox.Kept()); + // A default ask that ended on T2 says its totals at MGLOG_D, like its refusal. + const bool quiet = m_adoptT0Quiet && !m_adoptT0; + const char* ran = m_adoptT0 ? "ran T0" : m_adoptT0Settled ? "not granted (T2)" : "no native bind (T2)"; +#define MGL_T0_SERVER_TOTALS(LOG) \ + LOG("MG_Remote server: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ + "refused %llu, stale offers %llu, malformed %llu, unclaimed %zu", \ + ran, m_adoptT0Quiet ? " (default ask)" : "", static_cast(m_t0Stores), \ + static_cast(m_t0Bytes), static_cast(m_t0Declined), \ + static_cast(m_t0Refusals), \ + static_cast(m_adoptInbox.StaleOffers()), \ + static_cast(m_adoptInbox.MalformedOffers()), m_adoptInbox.Kept()) + if (quiet) { + MGL_T0_SERVER_TOTALS(MGLOG_D); + } else { + MGL_T0_SERVER_TOTALS(MGLOG_I); + } +#undef MGL_T0_SERVER_TOTALS + (void)ran; } m_adoptInbox.Clear(); m_adoptInbox.Attach(nullptr); - m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = false; + m_adoptT0Asked = m_adoptT0Settled = m_adoptT0 = m_adoptT0Quiet = false; + m_adoptAsk = 2; m_consumer.Detach(); *m_commands = Transport::RingConsumer(); *m_events = Transport::EventRingProducer(); @@ -1094,19 +1110,32 @@ namespace MobileGL::MG_Remote::Server { post.passed = ops->SelfTestExternal(detail, sizeof(detail)); post.detail = detail; post.ran = true; - MGLOG_I("MG_Remote server: T0 POST (%s, sustained-lock pattern) %s - %s", backendName, - post.passed ? "PASSED" : "FAILED", post.detail.c_str()); + if (m_adoptT0Quiet) { + MGLOG_D("MG_Remote server: T0 POST (%s, sustained-lock pattern) %s - %s", backendName, + post.passed ? "PASSED" : "FAILED", post.detail.c_str()); + } else { + MGLOG_I("MG_Remote server: T0 POST (%s, sustained-lock pattern) %s - %s", backendName, + post.passed ? "PASSED" : "FAILED", post.detail.c_str()); + } } if (!post.passed) why = std::string("the ") + backendName + " POST self-test failed: " + post.detail; } } if (!why.empty()) { + ++m_t0Refusals; + const char* word = MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed" + : !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer" : "POST"; + if (m_adoptT0Quiet) { + // THE DEFAULT ASK (MOBILEGL_IPC_ADOPT_TIER unset) FALLS BACK QUIETLY: every host + // session would otherwise carry this line. Counted (T0Refusals, the totals line). + MGLOG_D("MG_Remote server: T0 by default not granted (%s) - %s; the session runs T2", + word, why.c_str()); + return; + } MGLOG_W("MG_Remote server: Refuse{AdoptT0Unavailable, \"%s\"} - the client asked T0 " "(MOBILEGL_IPC_ADOPT_TIER=0) and %s; kCapAdoptT0 is not published and the session " "runs T2 (emulate)", - MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed" - : !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer" : "POST", - why.c_str()); + word, why.c_str()); return; } m_adoptT0 = true; diff --git a/MobileGL/MG_Remote/Server/ServerSession.h b/MobileGL/MG_Remote/Server/ServerSession.h index d04c95a9..77554a88 100644 --- a/MobileGL/MG_Remote/Server/ServerSession.h +++ b/MobileGL/MG_Remote/Server/ServerSession.h @@ -315,6 +315,11 @@ namespace MobileGL::MG_Remote::Server { Uint64 T0StoresImported() const { return m_t0Stores; } Uint64 T0BytesImported() const { return m_t0Bytes; } Uint64 T0Declined() const { return m_t0Declined; } + // 1 when this session asked T0 and was not granted it (allow switch, no AHardwareBuffer, + // POST): counted whether the refusal was named (knob 0) or quiet (the default ask). + Uint64 T0Refusals() const { return m_t0Refusals; } + // The ask was the client's DEFAULT (kAdoptAskT0Default): a refusal is said at MGLOG_D. + Bool AdoptT0Quiet() const { return m_adoptT0Quiet; } AdoptInbox& T0Inbox() { return m_adoptInbox; } private: @@ -355,12 +360,15 @@ namespace MobileGL::MG_Remote::Server { // P11 B2 (T0). Set by Accept from the Hello, by SettleAdoptT0AtBind on the apply thread; // the inbox and the counters are the apply thread's. Bool m_adoptT0Asked = false; + Bool m_adoptT0Quiet = false; + Uint8 m_adoptAsk = 2; Bool m_adoptT0Settled = false; Bool m_adoptT0 = false; AdoptInbox m_adoptInbox; Uint64 m_t0Stores = 0; Uint64 m_t0Bytes = 0; Uint64 m_t0Declined = 0; + Uint64 m_t0Refusals = 0; }; // Leak-at-exit like every other MG_Remote singleton (ID-8): no frontend destructor may diff --git a/MobileGL/MG_Remote/Transport/AdoptTier.cpp b/MobileGL/MG_Remote/Transport/AdoptTier.cpp index e135e037..378cad55 100644 --- a/MobileGL/MG_Remote/Transport/AdoptTier.cpp +++ b/MobileGL/MG_Remote/Transport/AdoptTier.cpp @@ -22,15 +22,20 @@ namespace MobileGL::MG_Remote::Transport { const char* SideName(AdoptTierSide side) { return side == AdoptTierSide::Client ? "client" : "server"; } } // namespace + bool AdoptTierIsDefault() { return MG_Config::Ipc.AdoptTier == MG_Config::kAdoptTierUnset; } + + bool AdoptTierWantsT0() { return MG_Config::Ipc.AdoptTier == 0 || AdoptTierIsDefault(); } + std::uint32_t AdoptTierAskFor(bool streamDataPlane) { - return (MG_Config::Ipc.AdoptTier == 0 && !streamDataPlane) ? 0u : 2u; + if (streamDataPlane || !AdoptTierWantsT0()) return 2u; + return AdoptTierIsDefault() ? kAdoptAskT0Default : 0u; } std::uint32_t SettleAdoptTierAtHandshake(bool streamDataPlane, AdoptTierSide side) { const std::uint32_t knob = MG_Config::Ipc.AdoptTier; - if (knob > 2) { - // Unreachable through ConfigLoader (it admits 0..2); a direct assignment is the only - // way here, and "T" of a 7 would be a lie. + if (knob > 2 && knob != MG_Config::kAdoptTierUnset) { + // Unreachable through ConfigLoader (it admits 0..2 or leaves the knob unset); a direct + // assignment is the only way here, and "T" of a 7 would be a lie. SessionFail(MGFatalFamily::UnimplementedAdoptTier, "MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is not an " "adoption tier; the values are 0 (T0), 1 (closed) and 2 (emulate, the default)", @@ -39,9 +44,17 @@ namespace MobileGL::MG_Remote::Transport { g_settled.store(true, std::memory_order_release); if (knob == 2) return 2; if (streamDataPlane) { - // A1, UNCHANGED: ONE LINE PER SESSION, on the side that holds the knob. The tcp lane's - // server is a shared fixture that never sees the client's environment, so each side - // says it for itself; neither side dies, because T2 is the only tier a stream carries. + // A1, UNCHANGED FOR A NAMED KNOB: ONE LINE PER SESSION, on the side that holds the knob. + // The tcp lane's server is a shared fixture that never sees the client's environment, + // so each side says it for itself; neither side dies, because T2 is the only tier a + // stream carries. AN UNSET KNOB (T0 by default) SAYS IT AT MGLOG_D: every tcp session + // would otherwise carry the line. The client counts it (ClientSession::T0Fallbacks). + if (AdoptTierIsDefault()) { + MGLOG_D("MG_Remote %s: T0 by default is not available on a stream data plane (tcp): the " + "session runs T2 (MOBILEGL_IPC_ADOPT_TIER unset; set 0 to have this named)", + SideName(side)); + return 2; + } MGLOG_W("MG_Remote %s: Refuse{AdoptTierOnStream, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u names a " "shared-mapping adoption tier and this session's data plane is a stream (tcp): the two " "sides share no memory to map, so the session runs T2 (emulate: the client keeps the " @@ -61,10 +74,11 @@ namespace MobileGL::MG_Remote::Transport { "could run it); the session runs T2 (emulate). Set 0 for T0 or 2 for T2"); return 2; } - // knob 0 over shared segments: asked in the Hello. Nothing is said yet - the answer is the - // server's, at its first native bind, and a T0 this session cannot use is named once, at - // the first map_persistent (ClientSession::AdoptT0). - return 0; + // knob 0 (or unset) over shared segments: asked in the Hello. Nothing is said yet - the + // answer is the server's, at its first native bind, and a T0 this session cannot use is + // named once (knob 0) or said at MGLOG_D (unset), at the first map_persistent + // (ClientSession::AdoptT0). + return AdoptTierAskFor(false); } bool AdoptTierSettledAtHandshake() { return g_settled.load(std::memory_order_acquire); } diff --git a/MobileGL/MG_Remote/Transport/AdoptTier.h b/MobileGL/MG_Remote/Transport/AdoptTier.h index d497d628..b3c51183 100644 --- a/MobileGL/MG_Remote/Transport/AdoptTier.h +++ b/MobileGL/MG_Remote/Transport/AdoptTier.h @@ -13,9 +13,11 @@ // and the server imports it; T1 = the server exports an opaque fd (CLOSED, ID-P11-1); T2 = the // resource owner declines and the client keeps the shadow and pushes. // -// MOBILEGL_IPC_ADOPT_TIER IS THE SWITCH (ruling ID-P11-14): 2, the default, is today's T2 byte -// for byte; 0 asks for T0; 1 is refused by name and runs T2. T0 that this session cannot use is -// never a Fatal - it is T2 plus one named line: +// MOBILEGL_IPC_ADOPT_TIER IS THE SWITCH (ruling ID-P11-14). UNSET - the default since 2026-09-29, +// the user's decision - asks for T0 and falls back to T2 QUIETLY (MGLOG_D only, still counted), +// so a tcp session or a host session is not one line longer; 0 asks for T0 BY NAME (the table +// below); 1 is refused by name and runs T2; 2 is T2 byte for byte. T0 that this session cannot use +// is never a Fatal - it is T2 plus one named line (knob 0) or one D line (unset): // // data plane side knob 0 knob 1 // Stream (tcp) either `Refuse{AdoptTierOnStream, "T0"}`, T2 `Refuse{AdoptTierOnStream, "T1"}`, T2 @@ -40,15 +42,28 @@ namespace MobileGL::MG_Remote::Transport { enum class AdoptTierSide : std::uint8_t { Client, Server }; + // THE HELLO'S T0 ASK WHEN THE KNOB IS UNSET (LinkTerms.adoptTier, protocol.fbs): T0 as asked + // by 0, except that the server names a refusal only at MGLOG_D. The ask travels because only + // the server knows why it did not grant T0 (its allow switch, its platform, its POST). + inline constexpr std::uint32_t kAdoptAskT0Default = 0x80; + // An ask that is a T0 ask (0 by name, kAdoptAskT0Default by default). + inline bool AdoptAskIsT0(std::uint32_t ask) { return ask == 0 || ask == kAdoptAskT0Default; } + // This process's knob asks for T0 (0, or unset). + bool AdoptTierWantsT0(); + // This process's knob is unset: T0 fallbacks are quiet. + bool AdoptTierIsDefault(); + // The tier the client's Hello asks for (LinkTerms.adoptTier): 0 when the knob says 0 and the - // plane it proposes is shared segments, else 2. Pure - the lines are the settle's. + // plane it proposes is shared segments, kAdoptAskT0Default when the knob is unset on shared + // segments, else 2. Pure - the lines are the settle's. std::uint32_t AdoptTierAskFor(bool streamDataPlane); // Settles this side's handshake answer from MOBILEGL_IPC_ADOPT_TIER and the negotiated data - // plane and returns the tier this side asks (client) or serves by its own knob (server): 0 - // only for a client asking T0 over shared segments, 2 otherwise. Logs at most one line per - // call (A1's stream refusal, the T1 refusal). Never aborts for 0/1; a knob above 2 (only a - // direct assignment can make one) is still Fatal{UnimplementedAdoptTier}. + // plane and returns the ask this side made (client) or serves by its own knob (server): + // AdoptTierAskFor's value for a client asking T0 over shared segments, 2 otherwise. Logs at + // most one line per call (A1's stream refusal - at MGLOG_D when the knob is unset - and the + // T1 refusal). Never aborts for 0/1/unset; any other value (only a direct assignment can + // make one) is still Fatal{UnimplementedAdoptTier}. std::uint32_t SettleAdoptTierAtHandshake(bool streamDataPlane, AdoptTierSide side); // True once a handshake in this process has settled the tier. The at-use check trusts a diff --git a/scripts/ci/protocol_revision_pins.json b/scripts/ci/protocol_revision_pins.json index 51c4e859..48d45546 100644 --- a/scripts/ci/protocol_revision_pins.json +++ b/scripts/ci/protocol_revision_pins.json @@ -3,5 +3,5 @@ "2": "b30928e6fc93ddc015fd86f634a6820f775a6f2f6de5a848d273981e706b2597", "3": "da09f0acb48cf245703f28556ef5a38bc9e25241ebbf7b6baa9ab1765b7ddd50", "4": "6357c3a07ae258d2825dc42bfd610712b15a38a3beb7a96b5f56ea2988705e7f", - "5": "f83f4c5ba6d15fac9c9f00c12b246c5f706f10c80719accd4ce116cb1a090c10" + "5": "4632750fd68b889cb6df709df9d150f9910468c0c530399de01bef87ba793904" }