[Feat] (MG_Pipe): give HasLiveHostWrites its producer, decline every persistent-map acquisition under split, and retire the verify pin that was waiting for exactly this

This commit is contained in:
2026-09-11 14:31:46 -04:00
parent feca3994d1
commit 13827e376f
3 changed files with 72 additions and 9 deletions
+9
View File
@@ -702,6 +702,15 @@ namespace MobileGL::MG_Pipe {
// one was emitted, so this cannot be false - but a zeroed record (null // one was emitted, so this cannot be false - but a zeroed record (null
// handle, size 0) is not the answer if that pre-pass is ever relaxed. // handle, size 0) is not the answer if that pre-pass is ever relaxed.
if (!MGPipeBuildSubDataRecord(handle, at, length, record, /*verbatimShadow=*/true)) return; if (!MGPipeBuildSubDataRecord(handle, at, length, record, /*verbatimShadow=*/true)) return;
#if MOBILEGL_BUILD_DISAGGREGATED
// P5 (b1): the live-host-writes bit rides the content record, because
// "someone may be writing these bytes without telling you" is a fact about the
// CONTENT and not about the storage. It is set from the object's PUBLISHED
// value rather than from a live IsMapped() read so that the record and the
// edge that announced it can never disagree. MGPipeBuildSubDataRecord does not
// take the object, which is why it is set here and not in the builder.
record.HasLiveHostWrites = buffer.HasLiveHostWritesForWire() ? 1 : 0;
#endif
MGPipeApplyResourceSubData(record, base + at); MGPipeApplyResourceSubData(record, base + at);
}); });
if (!encodable) { if (!encodable) {
+49 -6
View File
@@ -26,6 +26,13 @@
#include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h> #include <MG_State/GLState/ProgramState/ProgramArtifactsCodec.h>
#endif #endif
#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>
#endif
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstdlib> #include <cstdlib>
@@ -835,12 +842,18 @@ namespace MobileGL::MG_Pipe {
return true; return true;
} }
#if MOBILEGL_PIPE_VERIFY #if MOBILEGL_PIPE_VERIFY && !MOBILEGL_BUILD_DISAGGREGATED
// D-A4's pin. HasLiveHostWrites is ALWAYS false in this phase and is written by // D-A4's pin, AND P5 (b1) IS THE PHASE IT WAS WAITING FOR. It said "HasLiveHostWrites
// nobody: it exists so the phase that pushes persistent-mapped host writes can set it // is always false in this phase and is written by nobody: it exists so the phase that
// with no new record kind. A producer that landed under it would change what // pushes persistent-mapped host writes can set it with no new record kind", and a
// IsBufferDrawClean answers with no other visible edit, so a verify build refuses to // verify build refused to let such a producer arrive unannounced.
// let one arrive unannounced. //
// The producer is announced: MGPSubData::HasLiveHostWrites, set by
// MGPipeEmitResourceSubData from BufferObject::HasLiveHostWritesForWire and read by
// ApplyBufferWrite below. So the pin is LIFTED FOR A SPLIT BUILD ONLY, and left
// standing everywhere else - in a monolith build nothing sets the bit and the wire is
// still the thing that would say so if something started to. That is the whole value
// of the pin and it survives the phase it was written for.
void PinNoLiveHostWrites(const MGPipeResourceRecord& record, MGPipeHandle res, const char* call) { void PinNoLiveHostWrites(const MGPipeResourceRecord& record, MGPipeHandle res, const char* call) {
if (!record.HasLiveHostWrites) return; if (!record.HasLiveHostWrites) return;
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeLiveHostWrites") MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeLiveHostWrites")
@@ -905,6 +918,16 @@ namespace MobileGL::MG_Pipe {
return false; return false;
} }
PinNoLiveHostWrites(*stored, record.Res, call); PinNoLiveHostWrites(*stored, record.Res, call);
#if MOBILEGL_BUILD_DISAGGREGATED
// P5 (b1): THE PRODUCER. The record's own statement about the resource, applied
// before the serial moves so that a probe re-entered from inside the backend hook
// below already sees it. It is an assignment and not an OR: the bit is a STATE,
// and a content record emitted while nothing maps the buffer is exactly how the
// state goes back to false - which is why the falling edge pushes one block
// (BufferObject::NotePersistentMapStateChanged) rather than relying on the next
// ordinary write to arrive.
stored->HasLiveHostWrites = record.HasLiveHostWrites != 0;
#endif
// THE SERIAL MOVES BEFORE THE BACKEND IS TOLD, and that order is load-bearing: // THE SERIAL MOVES BEFORE THE BACKEND IS TOLD, and that order is load-bearing:
// the backend stamps its own synced serial from this record inside the hook, so a // the backend stamps its own synced serial from this record inside the hook, so a
@@ -1932,6 +1955,26 @@ namespace MobileGL::MG_Pipe {
// the flag exists to announce, and the wire is what refuses to let it arrive unnamed. // the flag exists to announce, and the wire is what refuses to let it arrive unnamed.
PinNoLiveHostWrites(*record, handle.Handle, "map_persistent"); PinNoLiveHostWrites(*record, handle.Handle, "map_persistent");
#if MOBILEGL_BUILD_DISAGGREGATED
// R-6: A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION, ALWAYS.
//
// The mint returns a raw void* that the client stores as the store's base
// (BufferObject.cpp:238 / :603 / :658). Across a process boundary that address is
// meaningless, and under `inproc` it is WORSE than meaningless: it happens to work,
// so a lane that kept adoption alive would be green for a reason spawn cannot
// reproduce, and persistent-map-push - an exit-gate counter - would be structurally
// zero (PipeStats.cpp says so in as many words). Forcing T2 here rather than at the
// three client call sites is what keeps `mpr` identical between the arms: the
// roundtrip is COUNTED above, unconditionally, because a decline costs the same round
// trip as a mint.
//
// The frontend already tolerates a decline in all three places, and has since P3a.
if (MG_Config::Transport != MG_Config::TransportMode::Monolith &&
MG_Remote::Client::AdoptTierIsEmulate()) {
return nullptr;
}
#endif
// NO SERIAL MOVES and NO DESCRIPTOR CHANGES: the donation re-mints the backend's own // NO SERIAL MOVES and NO DESCRIPTOR CHANGES: the donation re-mints the backend's own
// driver object, which is a server-local event that the backend's own id generation // driver object, which is a server-local event that the backend's own id generation
// already catches, and the client's view of the store's extent is untouched by it. // already catches, and the client's view of the store's extent is untouched by it.
+14 -3
View File
@@ -219,9 +219,20 @@ namespace MobileGL::MG_Pipe {
// sub-data). It is what replaces the frontend change serial the backend used to // sub-data). It is what replaces the frontend change serial the backend used to
// mirror, and no MGPipe call may require the client to provide or know one. // mirror, and no MGPipe call may require the client to provide or know one.
Uint64 Serial = 0; Uint64 Serial = 0;
// ALWAYS FALSE IN P3a, AND WRITTEN BY NOBODY. It exists so the phase that pushes // "DOES THIS RESOURCE HAVE A LIVE HOST WRITER RIGHT NOW?"
// persistent-mapped host writes can set it with zero new record kinds; a verify build //
// pins that it is false, so that phase cannot land a silent semantic change under it. // False and written by nobody through P3a and P4a; P5 (b1) is the phase it was waiting
// for and gives it its producer, with zero new record kinds exactly as planned: the
// bit rides MGPSubData::HasLiveHostWrites - a byte out of that payload's existing pad -
// and ApplyBufferWrite assigns it here. In a MONOLITH build nothing sets it and the
// MOBILEGL_PIPE_VERIFY wire (PinNoLiveHostWrites) still refuses a producer, so the pin
// survives the phase it was written for instead of being deleted by it.
//
// It is what IsBufferDrawCleanByHandle asks under split INSTEAD of the frontend
// object's IsMapped(), because a spawned server has no frontend object to ask. Getting
// that substitution wrong once already cost a silent regression - an emulated
// persistent map read draw-clean for ever - which MG_Test/SanityTest.cpp's
// DirectGLESBufferDrawProbe pair now pins from both sides.
Bool HasLiveHostWrites = false; Bool HasLiveHostWrites = false;
// ---- P4a. Only a record of kind Texture ever carries these; a buffer's stay at // ---- P4a. Only a record of kind Texture ever carries these; a buffer's stay at