[Refactor] (P13 W5 S4): the persistent-map tracker moves to MG_State as MG_Record; the adoption tier's verdict stays in MG_Remote.

This commit is contained in:
2026-10-06 12:09:56 -04:00
parent 17e36589fa
commit 0f5658f56e
20 changed files with 194 additions and 107 deletions
+4 -1
View File
@@ -656,7 +656,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# MarkGpuWritten producers are on the server's side of the line, and the
# block-granularity persistent-map push that tier T2 makes mandatory.
MobileGL/MG_Remote/Client/GpuWritePending.cpp
MobileGL/MG_Remote/Client/PersistentMapTracker.cpp
# P13 W5: the adoption tier's verdict (the tracker itself moved to the record arm below).
MobileGL/MG_Remote/Client/AdoptTierChoice.cpp
MobileGL/MG_Remote/Server/ServerSession.cpp
MobileGL/MG_Remote/Server/PipeApplier.cpp
MobileGL/MG_Remote/Server/ServerLoop.cpp
@@ -694,6 +695,8 @@ if (MOBILEGL_BUILD_RECORD_ARM)
# arms and the probe that measures the first against the second, shared by the
# renderer's bring-up and the Vulkan POST.
MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp
# P5 b1: the block-granularity persistent-map push the record arm makes mandatory (T2).
MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
)
endif()
+2 -2
View File
@@ -1964,14 +1964,14 @@ endif()
# healthy pull build into a link error, which is why this decision is made here.
if (MOBILEGL_BUILD_DISAGGREGATED)
mgl_itest_probe_for_symbol(MGL_ITEST_PERSISTENT_MAP_TRACKER
"${MGL_ITEST_ROOT}/MobileGL/MG_Remote/Client" "IsLivePersistentMap")
"${MGL_ITEST_ROOT}/MobileGL/MG_State/GLState/BufferState" "IsLivePersistentMap")
if (MGL_ITEST_PERSISTENT_MAP_TRACKER)
message(STATUS "Integration tests: package b1's persistent-map tracker is present "
"(${MGL_ITEST_PERSISTENT_MAP_TRACKER}) - PersistentCoherentMapScenario's "
"membership assertion is live")
target_compile_definitions(MobileGLIntegrationTest PRIVATE -DMGITEST_PERSISTENT_MAP_TRACKER=1)
else()
message(STATUS "Integration tests: no source under MobileGL/MG_Remote/Client names "
message(STATUS "Integration tests: no source under MobileGL/MG_State/GLState/BufferState names "
"IsLivePersistentMap - package b1's tracker has not landed, so "
"PersistentCoherentMapScenario's membership assertion SKIPS")
endif()
@@ -23,7 +23,7 @@
// build that never compiles MG_Remote, so keying on it would turn a healthy pull build into a
// link error.
#if defined(MGITEST_PERSISTENT_MAP_PEEK_LIVE) && defined(MGITEST_PERSISTENT_MAP_TRACKER)
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#define MGITEST_PERSISTENT_MAP_TRACKER_LIVE 1
#endif
@@ -94,7 +94,7 @@ namespace MGITest {
MobileGL::MG_State::GLState::BufferObject* buffer = FrontendBuffer(bufferName);
if (buffer == nullptr) return false;
*outLive = static_cast<bool>(
MobileGL::MG_Remote::Client::PersistentMapTracker::IsLivePersistentMap(*buffer));
MobileGL::MG_Record::PersistentMapTracker::IsLivePersistentMap(*buffer));
return true;
#else
(void)bufferName;
@@ -50,7 +50,7 @@ namespace MGITest {
// has to be made before the compiler sees it rather than by __has_include.
bool PersistentMapTrackerAvailable();
// MG_Remote::Client::PersistentMapTracker::IsLivePersistentMap() for this buffer - b1-v1.md
// MG_Record::PersistentMapTracker::IsLivePersistentMap() for this buffer - b1-v1.md
// 4.1 item 2. The membership set is meant to be exactly the early-out chain of
// SyncPersistentMappedRange; asking it here is what makes a drift between the two fail in a
// named test rather than silently stop the push.
+11 -1
View File
@@ -36,11 +36,13 @@
// DEFINED below, so MG_Backend names no MG_Remote symbol of its own to reach Session::Fail. P13 W5:
// in every build, for the record arm's fail / latch half.
#include <MG_Pipe/PipeSessionFail.h>
#include <MG_Pipe/PipeClientSeam.h>
#if MOBILEGL_BUILD_DISAGGREGATED
// R-6's tier gate. One spelling, asked at the one place the decline is decided. Outside the
// MOBILEGL_PIPE_VERIFY block above on purpose: the tier is a property of the BUILD, not of the
// comparator, and a split build without the comparator still declines every acquisition.
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Remote/Client/AdoptTierChoice.h>
// P5c (ct), CONTRACT-P5C.md §6 layer 2: MGPipeApplierReset's role guard asks
// ServerLoop::OnApplyThread() - the one predicate that tells the GL thread from the thread
// that owns g_applier under an active transport - and ClientSession::Active(), which is what
@@ -182,6 +184,14 @@ namespace MobileGL::MG_Pipe {
MGPipeRecordLatchHook MGPipeRecordLatchHookInstalled() { return g_recordLatchHook; }
MGPipeRecordLatchedHook MGPipeRecordLatchedHookInstalled() { return g_recordLatchedHook; }
// P13 W5: the client session's device-loss answer (PipeClientSeam.h). Constant-initialized for
// the same static-init reason as the hooks above.
namespace {
constinit MGPipeClientDeviceLostProbe g_clientDeviceLostProbe = nullptr;
} // namespace
void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe) { g_clientDeviceLostProbe = probe; }
Bool MGPipeClientDeviceLost() { return g_clientDeviceLostProbe != nullptr && g_clientDeviceLostProbe(); }
#if MOBILEGL_BUILD_RECORD_ARM
// ----------------------------------------------------------------------------
// THE BACKEND-SIDE SESSION-FAIL SEAM (P7 wave 0). PipeSessionFail.h holds the argument;
+25
View File
@@ -0,0 +1,25 @@
// MobileGL - MobileGL/MG_Pipe/PipeClientSeam.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// P13 W5: THE CLIENT SESSION'S ANSWERS THE RECORD ARM ASKS FOR, WITHOUT NAMING MG_Remote. The record
// arm's client half (the persistent-map tracker, the verb port) runs in a library without a
// transport too; there the answers are the monolith's (no session, so nothing was lost). With
// MG_Remote linked, ClientSession.cpp installs the real probe at static init.
#pragma once
#include <Includes.h>
namespace MobileGL::MG_Pipe {
// Has the calling process's client session latched a lost device (ClientSession::DeviceLost)?
using MGPipeClientDeviceLostProbe = Bool (*)();
void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe);
Bool MGPipeClientDeviceLost();
} // namespace MobileGL::MG_Pipe
@@ -0,0 +1,49 @@
// MobileGL - MobileGL/MG_Remote/Client/AdoptTierChoice.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
#include "AdoptTierChoice.h"
#include <Config.h>
#include <MG_Remote/FatalFunnel.h>
#include <MG_Remote/Transport/AdoptTier.h>
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
// shared segments dies by name there, and a server over shared segments serves the T2
// its client settled. So a settled session reaching this with 0/1 is a T2 session and
// says nothing more - the one line was the handshake's.
if (tier <= 1 && Transport::AdoptTierSettledAtHandshake()) return true;
// A map_persistent NO handshake settled (a session-free caller) is still a NAMED
// refusal rather than a silent T2: falling back would make `MOBILEGL_IPC_ADOPT_TIER=0`
// look like a working T0 run and silently produce pmap bytes it must not produce.
// Anything above 2 is not a tier at all.
// `dl` (CONTRACT-P6 5.2): the family word every death here carries.
if (tier <= 1) {
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
"MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u "
"reached a map_persistent that no handshake settled; T0 and T1 are not "
"implemented and only T2 (emulate) runs.",
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
} else {
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
"MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is "
"not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the "
"P5 default).",
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
}
}
} // namespace MobileGL::MG_Remote::Client
@@ -0,0 +1,30 @@
// MobileGL - MobileGL/MG_Remote/Client/AdoptTierChoice.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
// P13 W5: the adoption tier's verdict at map_persistent, split out of PersistentMapTracker when the
// tracker left MG_Remote for the record arm (it runs without a transport; this does not).
#pragma once
#include <Includes.h>
namespace MobileGL::MG_Remote::Client {
// R-6's tier gate, read at use. True for MOBILEGL_IPC_ADOPT_TIER=2, the only implemented
// tier. The tiers are design/07's: T0 = the client allocates an AHardwareBuffer and the
// server imports it (P11 package B), T1 = the server exports an opaque fd (closed), T2 =
// decline and push. 0 and 1 are SETTLED AT THE HANDSHAKE (P11 A1, Transport/AdoptTier.h):
// a stream refuses them by name and runs T2, a client over shared segments dies by name
// there. So a settled session answers true here; only a map_persistent no handshake
// settled is still a named refusal. It is asked
// by MGPipeApplyMapPersistent, which is where the decline is decided, so the client's
// three adoption call sites keep their existing "null means declined" branch and the
// map-persistent-roundtrips counter keeps counting ATTEMPTS in both arms (E3(c) asserts
// mpr is equal between the monolith and the split arm, which is only true if the decline
// happens after the count, on the applier's side of the emission).
Bool AdoptTierIsEmulate();
} // namespace MobileGL::MG_Remote::Client
+13 -2
View File
@@ -11,6 +11,7 @@
#include "ClientSession.h"
#include <MG_Remote/FatalFunnel.h>
#include <MG_Pipe/PipeClientSeam.h>
#include <MG_Remote/Handshake.h>
#include <MG_Remote/Transport/LinkMetrics.h>
@@ -24,7 +25,7 @@
#include "../Transport/InProcessTransport.h"
#include "WireTables.h"
#include "EmitTables.h"
#include "PersistentMapTracker.h"
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include "ServerProbe.h"
#include <MGGitHash.h>
@@ -646,6 +647,16 @@ namespace MobileGL::MG_Remote::Client {
return context != nullptr && context->IsFromEndedWireSession();
}
// P13 W5: the record arm's client half (the persistent-map tracker) asks this through MG_Pipe's
// client seam, which has no MG_Remote to name. Registered at static init: the tracker may ask
// before any session starts, and must get this answer, not the seam's monolith default.
namespace {
[[maybe_unused]] const bool g_deviceLostProbeRegistered = [] {
MG_Pipe::MGPipeSetClientDeviceLostProbe(&ClientSession::DeviceLost);
return true;
}();
} // namespace
String ClientSession::LastLossReason() {
const std::lock_guard<std::mutex> lock(LossReasonMutex());
return LossReason();
@@ -757,7 +768,7 @@ namespace MobileGL::MG_Remote::Client {
"the reset serial, persistent maps) start over",
static_cast<unsigned long long>(SessionsStarted()));
ForgetWireTablesOfEndedSession();
PersistentMapTracker::Instance().ForgetEndedSession();
MG_Record::PersistentMapTracker::Instance().ForgetEndedSession();
MG_Pipe::MGPipeForgetEndedSession();
}
+5 -5
View File
@@ -32,7 +32,7 @@
#include "ClientSession.h"
#include "GpuWritePending.h"
#include "PersistentMapTracker.h"
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include "../Server/ServerLoop.h"
#include "../Server/PipeApplier.h"
@@ -237,7 +237,7 @@ namespace MobileGL::MG_Remote::Client {
// push still has to run - a coherent map is read by the GPU on any of them - but there
// is no shader that could write one, so no mark walk.
void BeforeReadOnlyVerb() {
PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
MG_Pipe::MGPipeDrainDeferredDestroys();
}
@@ -717,7 +717,7 @@ namespace MobileGL::MG_Remote::Client {
const void* clientIndices, Uint64 clientIndexBytes,
const MG_Pipe::MGPDrawIndirect* indirect) {
VerbChannel session = ChannelFor(slot);
PersistentMapTracker::Instance().PushDrawConsumers();
MG_Record::PersistentMapTracker::Instance().PushDrawConsumers();
MG_Pipe::MGPipeDrainDeferredDestroys();
// Snapshot before marking THIS draw's potential GPU writes: resolving
// a GPU-produced EBO here must not clear its pending mark for this draw.
@@ -1491,7 +1491,7 @@ namespace MobileGL::MG_Remote::Client {
// slot would inherit a call site that was already correct.
void EmitDispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
VerbChannel session = ChannelFor("DispatchCompute");
PersistentMapTracker::Instance().PushDrawConsumers();
MG_Record::PersistentMapTracker::Instance().PushDrawConsumers();
MarkGpuWritesForDispatch();
MG_Pipe::MGPGridInfo record{};
@@ -1510,7 +1510,7 @@ namespace MobileGL::MG_Remote::Client {
void EmitDispatchComputeIndirect(GLintptr indirect) {
VerbChannel session = ChannelFor("DispatchComputeIndirect");
PersistentMapTracker::Instance().PushDrawConsumers();
MG_Record::PersistentMapTracker::Instance().PushDrawConsumers();
MarkGpuWritesForDispatch();
MG_Pipe::MGPGridInfo record{};
+1 -1
View File
@@ -90,7 +90,7 @@ namespace MobileGL::MG_Remote::Server {
// ---------------------------------------------------------------------------------
//
// OnApplyThread() is the predicate under EVERY role guard on both hot paths -
// RefuseLegacyBufferArmFromApplyThread in BufferObject's accessors, PersistentMapTracker::
// RefuseLegacyBufferArmFromApplyThread in BufferObject's accessors, MG_Record::PersistentMapTracker::
// OnServerRole, ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt, RunsAsTheServerRole,
// MGPipeRefuseAllocatorFromApplyThread, RefuseLegacyTextureArmFromApplyThread. simpleperf at
// head 56a77348 (FCL + Minecraft, VD12, Adreno 830) measured it at 2.66% self / 3.64%
+2 -1
View File
@@ -35,7 +35,8 @@
#include <MG_Pipe/PipeApply.h>
// R-6's tier gate, and the ONE spelling of it (b1's file, unchanged by this package): the
// MapPersistent arm below asks it the same question MGPipeApplyMapPersistent asks.
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Remote/Client/AdoptTierChoice.h>
#include <MG_Remote/Protocol/generated/protocol_generated.h>
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
#include <MG_Util/Debug/Log.h>
@@ -13,11 +13,13 @@
#include <atomic>
#include <MG_Pipe/PipeMutation.h>
#if MOBILEGL_BUILD_DISAGGREGATED
// The client role's persistent-map tracker. MG_State reaching into MG_Remote/Client is the
// layering ARCHITECTURE.md:575 names - the CLIENT role IS MG_State plus MG_Impl - and the
// edge exists only in a build that has the transport at all.
// MG_State reaching into MG_Remote/Client is the layering ARCHITECTURE.md:575 names - the CLIENT
// role IS MG_State plus MG_Impl - and the edge exists only in a build that has the transport at all.
#include <MG_Remote/Client/GpuWritePending.h>
#include <MG_Remote/Client/PersistentMapTracker.h>
#endif
#if MOBILEGL_BUILD_RECORD_ARM
// The client role's persistent-map tracker (P13 W5: the record arm's, so beside this file).
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Util/Debug/Log.h>
#include <cstdlib>
@@ -60,8 +62,8 @@ namespace MobileGL::MG_State::GLState {
// of MG_Config::Transport and OnServerRole() is now one relaxed load of the apply
// thread's key (ServerLoop.h), so the monolith answer costs a load each.
void RefuseLegacyBufferArmFromApplyThread(const char* accessor) {
if (!MG_Remote::Client::PersistentMapTracker::PushIsArmed()) return;
if (!MG_Remote::Client::PersistentMapTracker::OnServerRole()) return;
if (!MG_Record::PersistentMapTracker::PushIsArmed()) return;
if (!MG_Record::PersistentMapTracker::OnServerRole()) return;
FatalLegacyBufferArmFromApplyThread(accessor);
}
#endif
@@ -97,7 +99,7 @@ namespace MobileGL::MG_State::GLState {
// tracker is a leaked singleton whose entries are raw pointers, and an entry that
// outlives its object is the one failure this set must not have. Forget is a no-op
// for a buffer that was never a member.
MG_Remote::Client::PersistentMapTracker::Instance().Forget(*this);
MG_Record::PersistentMapTracker::Instance().Forget(*this);
#endif
// P3a D-L: the buffer's death crosses as resource_destroy, which is the catalogue
// call for it - no seventh NotifyStateObjectDestroyed raiser is added, because that
@@ -419,11 +421,11 @@ namespace MobileGL::MG_State::GLState {
// overwrite the server shadow through the monolith adapter without crossing the wire -
// and "do nothing" is spelled Fatal, not `return`, because a server-side caller here
// is a role violation and not a shape the design tolerates.
if (MG_Remote::Client::PersistentMapTracker::PushIsArmed()) {
if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) {
if (MG_Record::PersistentMapTracker::PushIsArmed()) {
if (MG_Record::PersistentMapTracker::OnServerRole()) {
FatalLegacyBufferArmFromApplyThread("SyncPersistentMappedRange");
}
MG_Remote::Client::PersistentMapTracker::Instance().PushBlocksFor(*this);
MG_Record::PersistentMapTracker::Instance().PushBlocksFor(*this);
return;
}
#endif
@@ -447,7 +449,7 @@ namespace MobileGL::MG_State::GLState {
// push does, and a second route to the same record is a second thing to keep in step.
if (size == 0) return;
// Assert ownership at the final producer entry too, before serial/aggregate updates.
if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) {
if (MG_Record::PersistentMapTracker::OnServerRole()) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"PushMappedSpanBlock\"} - the server role (the apply "
"thread) reached the CLIENT persistent-map producer for buffer lifetime %llu "
"[%zu, +%zu). Under an active transport the server's staged copy is the draw's "
@@ -474,10 +476,10 @@ namespace MobileGL::MG_State::GLState {
}
void BufferObject::NotePersistentMapStateChanged() {
if (!MG_Remote::Client::PersistentMapTracker::PushIsArmed()) return;
MG_Remote::Client::PersistentMapTracker::Instance().NoteMapStateChanged(*this);
if (!MG_Record::PersistentMapTracker::PushIsArmed()) return;
MG_Record::PersistentMapTracker::Instance().NoteMapStateChanged(*this);
// Teardown must retire membership, but only the client publishes live-host-write state.
if (MG_Remote::Client::PersistentMapTracker::OnServerRole()) return;
if (MG_Record::PersistentMapTracker::OnServerRole()) return;
// THE LIVE-HOST-WRITES BIT (ARCHITECTURE.md 12, CONTRACT-P5.md section 3). A live
// WRITE map - persistent or not - mutates the shadow with no call, no serial and no
@@ -511,7 +513,7 @@ namespace MobileGL::MG_State::GLState {
// carries are real, current and the cheapest honest ones there are. A zero-length
// record would have been the alternative and it is illegal by contract rule A.
if (m_size == 0) return;
const Uint64 blockBytes = MG_Remote::Client::PersistentMapTracker::BlockBytes();
const Uint64 blockBytes = MG_Record::PersistentMapTracker::BlockBytes();
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 TURNS THE WHOLE MECHANISM OFF, STATE RECORD
// INCLUDED (E3(a)). An earlier cut skipped the blocks and still shipped a whole buffer
// here, which meant an unmap delivered the bytes the control exists to withhold - and
@@ -930,7 +932,7 @@ namespace MobileGL::MG_State::GLState {
// P11 B2 made this reachable (a T0 adoption), so the tracker lets go of the
// shadow range WHILE THE SHADOW IS STILL OURS: AdoptPersistentMap frees it, and
// an untrack after that would mprotect pages the buffer no longer owns.
MG_Remote::Client::PersistentMapTracker::Instance().Forget(*this);
MG_Record::PersistentMapTracker::Instance().Forget(*this);
#endif
m_resource.AdoptPersistentMap(pushedBase);
#if MOBILEGL_BUILD_RECORD_ARM
@@ -200,7 +200,7 @@ namespace MobileGL {
// the private NotifySubData - the same serial, the same defined-content flag, the
// same record - so the split arm and the monolith arm differ in HOW the span is
// cut and in nothing else. Called only by
// MG_Remote::Client::PersistentMapTracker, which owns the cutting.
// MG_Record::PersistentMapTracker, which owns the cutting.
void PushMappedSpanBlock(SizeT offset, SizeT size);
// Called on every event that can move the tracker's membership predicate: map,
@@ -1,4 +1,4 @@
// MobileGL - MobileGL/MG_Remote/Client/PersistentMapTracker.cpp
// MobileGL - MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
@@ -7,9 +7,8 @@
// End of Source File Header
#include "PersistentMapTracker.h"
#include "ClientSession.h"
#include <MG_Remote/FatalFunnel.h>
#include <MG_Remote/Transport/AdoptTier.h>
#include <MG_Pipe/PipeSessionFail.h>
#include <MG_Pipe/PipeClientSeam.h>
#include <MG_Pipe/MGPipeTypes.h>
#include <MG_State/GLState/BufferState/BufferObject.h>
@@ -17,7 +16,7 @@
#include <MG_State/GLState/TextureState/TextureObjectBuffer.h>
#include <MG_State/GLState/VertexArrayState/VertexArrayObject.h>
#include <MG_Util/Debug/Log.h>
#include <MG_Remote/Server/ServerLoop.h>
#include <MG_Backend/MGPipe/PipeInputs.h>
#include <xxhash.h>
@@ -30,7 +29,7 @@
#include <ucontext.h>
#include <unistd.h>
namespace MobileGL::MG_Remote::Client {
namespace MobileGL::MG_Record {
using MG_State::GLState::BufferObject;
@@ -714,7 +713,9 @@ namespace MobileGL::MG_Remote::Client {
// guard, two acquire loads and an out-of-line thread::id comparison. It stays out of line
// here for WireTables.cpp's reason beside RunsAsTheServerRole: inlining it would put a
// server header inside PersistentMapTracker.h, which MG_State's BufferObject.cpp includes.
Bool PersistentMapTracker::OnServerRole() { return Server::ServerLoop::OnApplyThread(); }
// P13 W5: the apply-thread question through MG_Pipe's role probe (MG_Remote installs it; a library
// without a transport has no server role, so it is false there).
Bool PersistentMapTracker::OnServerRole() { return MG_Pipe::MGPipeServerArm(); }
// SyncPersistentMappedRange's early-out chain (BufferObject.cpp:341-353), in its order,
// read as a membership test. Every line here has a line there; if one of them moves, the
@@ -757,7 +758,7 @@ namespace MobileGL::MG_Remote::Client {
// A buffer mapped on a LOST context (a thread still on one, or a session that is lost)
// never becomes a member: the push before every verb would carry its bytes to whichever
// session is current, and a fresh one never created the buffer.
if (IsLivePersistentMap(buffer) && !ClientSession::DeviceLost()) {
if (IsLivePersistentMap(buffer) && !MG_Pipe::MGPipeClientDeviceLost()) {
const auto range = buffer.GetMappedRange();
// The mprotect arm registers beside the hash arm: tracked buffers are pushed
// from the fault bitmap, everything else keeps the content scan. A failed
@@ -862,7 +863,7 @@ namespace MobileGL::MG_Remote::Client {
void PersistentMapTracker::PushBlocksFor(BufferObject& buffer) {
if (!PushIsArmed()) return;
if (OnServerRole()) {
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map "
MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map "
"producer belongs to the client; the server consumes transported bytes");
}
PushBlocksForChecked(buffer);
@@ -1084,7 +1085,7 @@ namespace MobileGL::MG_Remote::Client {
void PersistentMapTracker::PushAllMembers() {
if (!PushIsArmed()) return;
if (OnServerRole()) {
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map "
MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map "
"producer belongs to the client; the server consumes transported bytes");
}
if (m_livePersistentMaps.empty()) return;
@@ -1101,7 +1102,7 @@ namespace MobileGL::MG_Remote::Client {
void PersistentMapTracker::PushDrawConsumers() {
if (!PushIsArmed()) return;
if (OnServerRole()) {
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map "
MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map "
"producer belongs to the client; the server consumes transported bytes");
}
if (m_livePersistentMaps.empty()) return;
@@ -1258,38 +1259,5 @@ namespace MobileGL::MG_Remote::Client {
PersistentMapTracker::Instance().PushAllMembers();
}
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
// shared segments dies by name there, and a server over shared segments serves the T2
// its client settled. So a settled session reaching this with 0/1 is a T2 session and
// says nothing more - the one line was the handshake's.
if (tier <= 1 && Transport::AdoptTierSettledAtHandshake()) return true;
// A map_persistent NO handshake settled (a session-free caller) is still a NAMED
// refusal rather than a silent T2: falling back would make `MOBILEGL_IPC_ADOPT_TIER=0`
// look like a working T0 run and silently produce pmap bytes it must not produce.
// Anything above 2 is not a tier at all.
// `dl` (CONTRACT-P6 5.2): the family word every death here carries.
if (tier <= 1) {
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
"MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u "
"reached a map_persistent that no handshake settled; T0 and T1 are not "
"implemented and only T2 (emulate) runs.",
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
} else {
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
"MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is "
"not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the "
"P5 default).",
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
}
}
} // namespace MobileGL::MG_Remote::Client
} // namespace MobileGL::MG_Record
@@ -1,4 +1,4 @@
// MobileGL - MobileGL/MG_Remote/Client/PersistentMapTracker.h
// MobileGL - MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
@@ -61,7 +61,7 @@ namespace MobileGL::MG_State::GLState {
class BufferObject;
}
namespace MobileGL::MG_Remote::Client {
namespace MobileGL::MG_Record {
// The mprotect tracker's per-map slot, declared at namespace scope rather than in the
// .cpp's anonymous namespace so PersistentMapTracker's membership map can CACHE a
@@ -270,18 +270,5 @@ namespace MobileGL::MG_Remote::Client {
// draw-path binding walks move to the client.
void PushPersistentMapsBeforeVerb();
// R-6's tier gate, read at use. True for MOBILEGL_IPC_ADOPT_TIER=2, the only implemented
// tier. The tiers are design/07's: T0 = the client allocates an AHardwareBuffer and the
// server imports it (P11 package B), T1 = the server exports an opaque fd (closed), T2 =
// decline and push. 0 and 1 are SETTLED AT THE HANDSHAKE (P11 A1, Transport/AdoptTier.h):
// a stream refuses them by name and runs T2, a client over shared segments dies by name
// there. So a settled session answers true here; only a map_persistent no handshake
// settled is still a named refusal. It is asked
// by MGPipeApplyMapPersistent, which is where the decline is decided, so the client's
// three adoption call sites keep their existing "null means declined" branch and the
// map-persistent-roundtrips counter keeps counting ATTEMPTS in both arms (E3(c) asserts
// mpr is equal between the monolith and the split arm, which is only true if the decline
// happens after the count, on the applier's side of the emission).
Bool AdoptTierIsEmulate();
} // namespace MobileGL::MG_Remote::Client
} // namespace MobileGL::MG_Record
+10 -9
View File
@@ -27,7 +27,8 @@
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Remote/Client/GpuWritePending.h>
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Remote/Client/AdoptTierChoice.h>
#include <MG_Remote/Transport/AdoptTier.h>
#endif
@@ -38,7 +39,7 @@ namespace {
#if MOBILEGL_BUILD_DISAGGREGATED
using MG_State::GLState::BufferObject;
using MG_Remote::Client::GpuWriteProducer;
using MG_Remote::Client::PersistentMapTracker;
using MG_Record::PersistentMapTracker;
// A backend that MINTS a persistent mapping, for the one case that needs the adopted arm
// (TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo). Only AcquirePersistentMap is
@@ -390,7 +391,7 @@ TEST_F(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) {
// ...and the consequence, which is the one that would actually corrupt something.
const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush);
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush) - before,
static_cast<Uint64>(kSize))
<< "the declined twin's 4096 bytes and nothing else: the adopted buffer must contribute no "
@@ -414,7 +415,7 @@ TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) {
ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*buffer));
const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush);
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 4u)
<< "a 256 KiB span at a 64 KiB block size is four records, not one";
@@ -433,7 +434,7 @@ TEST_F(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) {
auto buffer = MakeBuffer(22u, kSize);
buffer->AcquireMemoryRange(Range1D{0, kSize},
BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent);
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 2u);
EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast<Uint64>(kSize));
buffer->ReleaseMemory(false);
@@ -482,7 +483,7 @@ TEST_F(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited)
<< "the rising-edge state record went out with the push disabled";
const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush);
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 0u);
EXPECT_EQ(MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush), before)
<< "MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 must ship nothing, so that "
@@ -621,10 +622,10 @@ TEST_F(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) {
// Fresh state ships everything once, then re-arms; with nothing written a second push
// ships nothing (and, with no edges, hashes nothing).
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 1u);
EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast<Uint64>(kSize));
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 1u);
// The one page IS protected: a write through the application's pointer faults into the
@@ -634,7 +635,7 @@ TEST_F(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) {
mapped[kSize - 1] = 0x5A;
EXPECT_GT(PersistentMapTracker::FaultEpochForTest(), epochBefore)
<< "the sub-page map's page was not protected: the write did not fault";
MG_Remote::Client::PushPersistentMapsBeforeVerb();
MG_Record::PushPersistentMapsBeforeVerb();
EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 2u);
EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast<Uint64>(2 * kSize))
<< "the push is clamped to [begin, end) even though the protected page is wider";
@@ -45,7 +45,7 @@
// Neither revert can be hidden by the other, which is the property R-16 asks for.
#include <Config.h>
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PipeResource.h>
#include <MG_Util/Debug/Log.h>
@@ -68,7 +68,7 @@ namespace {
using MobileGL::SizeT;
using MobileGL::Uint64;
using MobileGL::Uint8;
using MobileGL::MG_Remote::Client::PersistentMapTracker;
using MobileGL::MG_Record::PersistentMapTracker;
using MobileGL::MG_State::GLState::MapAlignedData;
using MobileGL::MG_State::GLState::ShadowAllocationBytesFor;
@@ -638,7 +638,7 @@ TEST(RemoteClientControls, PersistentMapProducerRefusesTheApplyThread) {
const auto child = RunInChild([] {
StartControlSession();
Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void*) {
PersistentMapTracker::Instance().PushAllMembers();
MG_Record::PersistentMapTracker::Instance().PushAllMembers();
return MOBILEGL_OK;
}, nullptr);
});
+1 -1
View File
@@ -45,7 +45,7 @@
#include <MG_Remote/Client/EmitTables.h>
#include <MG_Remote/Client/WireTables.h>
#include <MG_Remote/Client/ClientSession.h>
#include <MG_Remote/Client/PersistentMapTracker.h>
#include <MG_State/GLState/BufferState/PersistentMapTracker.h>
#include <MG_Remote/Client/BackendObject_Remote.h>
#include <MG_Remote/Server/ServerLoop.h>
#include <MG_Remote/Server/ServerSession.h>