mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[MG_Remote] (P6/dl): every session death goes through one funnel
Step two of CONTRACT-P6 5.2. The ~90 scattered `MGLOG_F("...Fatal{...}"); std::abort();`
pairs under MG_Remote/ become one call each to SessionFail(MGFatalFamily, fmt, ...),
which logs, echoes to stderr, counts, and aborts in one place. a6's census
found no single chokepoint existed; now there is one, and step three hangs the
SessionFault-to-peer publish off it.
THE MESSAGE STRING IS PASSED VERBATIM. The contract requires the log lines, the
CI greps and every red-once byte-identical, so SessionFail does not compose the
line from the family - it takes the SAME string the site handed MGLOG_F and
forwards it unchanged. The family enum rides alongside, redundantly, so the
funnel has the coarse wire FatalCode without parsing the string. The census now
checks that redundancy the way it checks WireLogFatal: every SessionFail call's
string must carry a Fatal{ word, so the enum and the string cannot silently
disagree.
The conversion was mechanical - 89 sites by a transform that injects the family
as MGLOG_F's new first argument and drops the trailing abort - with three sites
fixed by hand that the transform would have broken: a stub macro, and two
if/else-then-shared-abort blocks (PersistentMapTracker's adopt-tier and
SurfaceOpCodec's three surface-error branches) where a naive rewrite stole the
abort from the earlier branches and would have let them fall through. The
death tests are the safety net for byte-identity, and every one stayed green.
Four std::abort() remain under MG_Remote/, all inside the two funnels
(WireLog.cpp, FatalFunnel.cpp); the census exempts both files because their
callers carry the family word, and its baseline drops from 93 aborts to 4.
SessionFaultCount() is the one telemetry point S8 will assert zero over a good
run.
G1 re-measured: 0 symbol diff, .text unchanged at 0xa52203 (FatalFunnel and
FatalFamily live under MG_Remote/, compiled only in the disaggregated build).
Unit 2358 (one pre-existing environmental failure), integration-split 186/186,
integration-spawn 102/102, retrace 6/6, census 0 unmarked.
This commit is contained in:
@@ -621,6 +621,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
|
||||
# emitter that returns quietly is how a split lane runs monolith and
|
||||
# goes green (ARCHITECTURE.md 10.3).
|
||||
MobileGL/MG_Remote/CapsCodec.cpp
|
||||
# P6 dl: the one place a session dies - Session::Fail's funnel (Fatal-family aware,
|
||||
# publishes SessionFault, one telemetry point).
|
||||
MobileGL/MG_Remote/FatalFunnel.cpp
|
||||
MobileGL/MG_Remote/Wire/PipeWireCodec.cpp
|
||||
MobileGL/MG_Remote/Client/ClientSession.cpp
|
||||
MobileGL/MG_Remote/Client/EmitTables.cpp
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// reply read that sits inside it are package c1's (EmitAndWait below is still c0's stub).
|
||||
|
||||
#include "ClientSession.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "../CapsCodec.h"
|
||||
#include "../Protocol/generated/protocol_generated.h"
|
||||
@@ -60,12 +61,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
} // namespace
|
||||
|
||||
#define MGP5_C0_STUB(what) \
|
||||
do { \
|
||||
MGLOG_F("MGPipe: Fatal{UnimplementedClientSession, \"%s\"} - P5 packages s1/c1 have not " \
|
||||
SessionFail(MGFatalFamily::UnimplementedClientSession, \
|
||||
"MGPipe: Fatal{UnimplementedClientSession, \"%s\"} - P5 packages s1/c1 have not " \
|
||||
"landed this yet; c0 shipped the signature only", \
|
||||
what); \
|
||||
std::abort(); \
|
||||
} while (0)
|
||||
what)
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -124,25 +123,23 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// to be reachable, which is how the gap survived review.
|
||||
[[noreturn]] void FatalProtocolVersionMismatch(unsigned theirMajor, unsigned theirMinor,
|
||||
const char* theirStamp) {
|
||||
MGLOG_F("MGPipe: Fatal{AbiMismatch, \"protocol version\"} ours=%u.%u theirs=%u.%u "
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"protocol version\"} ours=%u.%u theirs=%u.%u "
|
||||
"ourBuild=%s theirBuild=%s - the peer speaks a different MGPipe protocol. "
|
||||
"Under spawn this is most often a stale MOBILEGL_IPC_SERVER_PATH pointing at "
|
||||
"a libMobileGLServer.so built from another tree.",
|
||||
static_cast<unsigned>(MOBILEGL_PROTOCOL_ABI_MAJOR),
|
||||
static_cast<unsigned>(MOBILEGL_PROTOCOL_ABI_MINOR), theirMajor, theirMinor,
|
||||
GIT_COMMIT_HASH_SHORT, theirStamp == nullptr ? "?" : theirStamp);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
[[noreturn]] void FatalAbiMismatch(const char* what, Uint64 ours, Uint64 theirs,
|
||||
const char* theirStamp) {
|
||||
MGLOG_F("MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
"theirBuild=%s - never a downgrade: the caps block's size is ABI-dependent "
|
||||
"and every field past the first difference would be read at the wrong offset",
|
||||
what, static_cast<unsigned long long>(ours),
|
||||
static_cast<unsigned long long>(theirs), GIT_COMMIT_HASH_SHORT,
|
||||
theirStamp == nullptr ? "?" : theirStamp);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// Guarded the same way ServerSession's helpers are: MG_Config::Ipc only exists
|
||||
@@ -326,9 +323,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Bool corrupt = false;
|
||||
while (events.Pop(view, &corrupt)) {
|
||||
if (corrupt) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"event-ring\"} - the reverse "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"event-ring\"} - the reverse "
|
||||
"channel's record stream is corrupt");
|
||||
std::abort();
|
||||
}
|
||||
switch (view.kind) {
|
||||
case Transport::kEventBufferWriteback: {
|
||||
@@ -337,11 +333,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
static_cast<const Transport::EventBufferWritebackHead*>(view.payload);
|
||||
const void* bytes = static_cast<const Uint8*>(view.payload) + sizeof(*head);
|
||||
if (view.payloadSize - sizeof(*head) < head->Size) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"buffer-writeback\"} - the "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"buffer-writeback\"} - the "
|
||||
"head declares %llu inline bytes and the record carries %llu",
|
||||
static_cast<unsigned long long>(head->Size),
|
||||
static_cast<unsigned long long>(view.payloadSize - sizeof(*head)));
|
||||
std::abort();
|
||||
}
|
||||
// The blobref names SEG_EVENT and the IN-SEGMENT offset of those inline
|
||||
// bytes - never a host address (R-2's rule B), which is the whole reason
|
||||
@@ -363,11 +358,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
static_cast<const Transport::EventGpuWrittenHead*>(view.payload);
|
||||
const Uint64 tail = view.payloadSize - sizeof(*head);
|
||||
if (tail / sizeof(Transport::EventRange) < head->RangeCount) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"gpu-written\"} - RangeCount "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"gpu-written\"} - RangeCount "
|
||||
"%u does not fit the record's %llu tail bytes",
|
||||
static_cast<unsigned>(head->RangeCount),
|
||||
static_cast<unsigned long long>(tail));
|
||||
std::abort();
|
||||
}
|
||||
// EventRange and MGPRange are the same two Uint64s (EventRing.h:61-66
|
||||
// asserts it), so the tail is handed over as-is rather than copied into
|
||||
@@ -408,12 +402,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// least one byte and that byte terminates the string.
|
||||
if (head->MessageBytes == 0 || tail < head->MessageBytes ||
|
||||
message[head->MessageBytes - 1] != '\0') {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"kEventGlError\"} - "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"kEventGlError\"} - "
|
||||
"MessageBytes %u against a %llu-byte tail, or the terminating "
|
||||
"NUL is missing",
|
||||
static_cast<unsigned>(head->MessageBytes),
|
||||
static_cast<unsigned long long>(tail));
|
||||
std::abort();
|
||||
}
|
||||
// The error queue is CLIENT state, written here on the GL thread. The
|
||||
// observation point is the next drain after the post - P9 owns the
|
||||
@@ -1381,11 +1374,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
if (statusOut != nullptr) *statusOut = Wire::ReplySink::kStatusError;
|
||||
if (replySizeOut != nullptr) *replySizeOut = 0;
|
||||
if (!m_started) {
|
||||
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - EmitAndWait on a session that has "
|
||||
SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - EmitAndWait on a session that has "
|
||||
"not started. There is no fall-through: a record that could not be emitted "
|
||||
"is a verb that did not happen",
|
||||
Wire::WireOpName(op));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// R-1's INVARIANT, AS A RUNTIME CHECK RATHER THAN A SENTENCE - but only while the
|
||||
@@ -1399,11 +1391,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// the fill quiescent during that apply). With the batch off, the original check
|
||||
// stands as written.
|
||||
if (MG_Config::Ipc.BatchWaits == 0 && ApplyThreadIsInsideApplier()) {
|
||||
MGLOG_F("MGPipe: Fatal{BarrierViolation, \"%s\"} - the apply thread is inside the "
|
||||
SessionFail(MGFatalFamily::BarrierViolation, "MGPipe: Fatal{BarrierViolation, \"%s\"} - the apply thread is inside the "
|
||||
"applier while the GL thread is emitting. R-1 makes at most one of them "
|
||||
"runnable, which is what keeps one process-wide gPipeInputs legal",
|
||||
Wire::WireOpName(op));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Uint64 seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount);
|
||||
@@ -1418,11 +1409,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
const BarrierWaitScope waiting;
|
||||
const auto wait = m_producer.WaitForCmdSpace(needed, kBarrierTimeoutMs);
|
||||
if (wait != Transport::SessionWait::Reached) {
|
||||
MGLOG_F("MGPipe: Fatal{RetirementWaitFailed, \"SEG_CMD\"} - %s needs %llu "
|
||||
SessionFail(MGFatalFamily::RetirementWaitFailed, "MGPipe: Fatal{RetirementWaitFailed, \"SEG_CMD\"} - %s needs %llu "
|
||||
"reclaimable bytes; the retirement wait ended on %s",
|
||||
Wire::WireOpName(op), static_cast<unsigned long long>(needed),
|
||||
wait == Transport::SessionWait::ShutDown ? "shutdown" : "timeout");
|
||||
std::abort();
|
||||
}
|
||||
// P5e (ra, §2.6): EVERY WAIT EXIT DRAINS. The cmd-space wait is one of the two
|
||||
// safety nets under run-ahead (the stage bell is the other), and a client parked
|
||||
@@ -1432,9 +1422,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
DrainEventRing(m_events);
|
||||
seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount);
|
||||
if (seq == Wire::kInvalidSeq) {
|
||||
MGLOG_F("MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s refused after sufficient "
|
||||
SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s refused after sufficient "
|
||||
"space retired", Wire::WireOpName(op));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1588,12 +1577,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return seq;
|
||||
}
|
||||
if (wait != Transport::SessionWait::Reached) {
|
||||
MGLOG_F("MGPipe: Fatal{BarrierTimeout, \"%s\"} - appliedSeq did not reach %llu within "
|
||||
SessionFail(MGFatalFamily::BarrierTimeout, "MGPipe: Fatal{BarrierTimeout, \"%s\"} - appliedSeq did not reach %llu within "
|
||||
"%llu ms. A bounded wait is deliberate: a wedged CI job and a lost record look "
|
||||
"identical from outside, and only one of them is a bug worth finding",
|
||||
Wire::WireOpName(op), static_cast<unsigned long long>(seq),
|
||||
static_cast<unsigned long long>(waitBudgetMs));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// THE REVERSE CHANNEL IS DRAINED HERE, and here is the only place it can be: under the
|
||||
@@ -1620,11 +1608,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Uint64 replySize = 0;
|
||||
Int32 status = Wire::ReplySink::kStatusError;
|
||||
if (!ReadReply(seq, replyOut, replyBytes, &status, &replySize)) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyMissing, \"%s\"} - seq %llu carries kReplySlot and the "
|
||||
SessionFail(MGFatalFamily::ReplyMissing, "MGPipe: Fatal{ReplyMissing, \"%s\"} - seq %llu carries kReplySlot and the "
|
||||
"server applied it, but its slot does not stamp that seq. The stamp is what "
|
||||
"makes a wrong-slot read detectable rather than plausible (R-3)",
|
||||
Wire::WireOpName(op), static_cast<unsigned long long>(seq));
|
||||
std::abort();
|
||||
}
|
||||
if (statusOut != nullptr) *statusOut = status;
|
||||
if (replySizeOut != nullptr) *replySizeOut = replySize;
|
||||
@@ -1637,11 +1624,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// (R-5). A client that treated it as a failure would re-create ID-39 from the other
|
||||
// side.
|
||||
if (replyOut != nullptr && replySize > replyBytes) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyTooLarge, \"%s\"} - the answer is %llu bytes and the "
|
||||
SessionFail(MGFatalFamily::ReplyTooLarge, "MGPipe: Fatal{ReplyTooLarge, \"%s\"} - the answer is %llu bytes and the "
|
||||
"caller offered %llu. P5 does not chunk a reply",
|
||||
Wire::WireOpName(op), static_cast<unsigned long long>(replySize),
|
||||
static_cast<unsigned long long>(replyBytes));
|
||||
std::abort();
|
||||
}
|
||||
return seq;
|
||||
}
|
||||
@@ -1730,12 +1716,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
return;
|
||||
}
|
||||
if (wait != Transport::SessionWait::Reached) {
|
||||
MGLOG_F("MGPipe: Fatal{BarrierTimeout, \"%s\"} - a forced run-ahead wait did not "
|
||||
SessionFail(MGFatalFamily::BarrierTimeout, "MGPipe: Fatal{BarrierTimeout, \"%s\"} - a forced run-ahead wait did not "
|
||||
"reach appliedSeq %llu within %u ms. The forced waits of CONTRACT-P5E §2.5 "
|
||||
"are the points at which the client must be in step again, so a wedge here "
|
||||
"is a wedge in the queue and not a slow frame",
|
||||
why, static_cast<unsigned long long>(target), kBarrierTimeoutMs);
|
||||
std::abort();
|
||||
}
|
||||
// EVERY WAIT EXIT DRAINS (§2.6). It is what the flow-control deadlock argument rests
|
||||
// on: the server blocks only on a full event ring, the client blocks only on
|
||||
@@ -1794,13 +1779,12 @@ namespace MobileGL::MG_Remote::Client {
|
||||
DrainEventRing(m_events);
|
||||
}
|
||||
if (wait == Transport::SessionWait::TimedOut) {
|
||||
MGLOG_F("MGPipe: Fatal{PresentCreditTimeout} - present %llu waited %u ms for "
|
||||
SessionFail(MGFatalFamily::PresentCreditTimeout, "MGPipe: Fatal{PresentCreditTimeout} - present %llu waited %u ms for "
|
||||
"swap %llu to come back with a credit of %u. The credit, never the "
|
||||
"ring's bytes, is what paces a run-ahead client (CONTRACT-P5E §2.4), "
|
||||
"so a credit that never returns is a server that stopped swapping",
|
||||
static_cast<unsigned long long>(serial), kBarrierTimeoutMs,
|
||||
static_cast<unsigned long long>(awaited), credit);
|
||||
std::abort();
|
||||
}
|
||||
// ShutDown is teardown and returns: the present did not happen, the same answer
|
||||
// the barrier gives on a dead doorbell.
|
||||
@@ -1880,7 +1864,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// is exactly "no record is being applied" and not merely "not decoding".
|
||||
if (isBarrieredFill && !ApplyThreadIsInsideApplier()) return;
|
||||
if (Server::ServerLoop::OnApplyThread()) return; // the applier owns the block
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched "
|
||||
"gPipeInputs (%s) on a RUN-AHEAD session %s. With the client running ahead "
|
||||
"the block is the server's to read for as long as ANY record is in flight "
|
||||
"(CONTRACT-P5E §3), so the only legal GL-thread write is a barriered fill "
|
||||
@@ -1889,7 +1873,6 @@ namespace MobileGL::MG_Remote::Client {
|
||||
isBarrieredFill ? "in a barriered fill that did not first wait for the "
|
||||
"applier to catch up"
|
||||
: "outside a barriered fill");
|
||||
std::abort();
|
||||
}
|
||||
// MOBILEGL_IPC_BATCH_WAITS=1 makes "the fill runs while the apply thread applies an
|
||||
// earlier value record" the INTENDED shape: the fill writes only fields no record
|
||||
@@ -1909,13 +1892,12 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// access in a shared library, and it must stay thread-local (see InBarrierWait's note
|
||||
// above - a second GL thread would otherwise read the first one's wait as its own).
|
||||
if (InBarrierWait()) return;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched gPipeInputs "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched gPipeInputs "
|
||||
"(%s) while the apply thread was inside the applier and this thread was not in a "
|
||||
"barrier wait. R-1's barrier is the only thing that makes one process-wide "
|
||||
"gPipeInputs legal (CONTRACT-P5 table 3); a touch in this window races the "
|
||||
"applier's own reads of it",
|
||||
surface);
|
||||
std::abort();
|
||||
}
|
||||
void ClientSession::NoteApplyThreadEnteredApplier() {
|
||||
g_roleInsideApplier = true;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
// regression re-committed at the transport layer.
|
||||
|
||||
#include "EmitTables.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "ClientSession.h"
|
||||
#include "GpuWritePending.h"
|
||||
@@ -87,8 +88,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// The same shape as MGPipeInputPoisonFatal (generated/PipeFilled.inc:407-413): names the
|
||||
// slot, live at every log level, aborts. Deliberately NOT MOBILEGL_ASSERT, which is
|
||||
// inert in an INFO build - and INFO is what every device lane runs.
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"}", slot);
|
||||
std::abort();
|
||||
SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"}", slot);
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -209,11 +209,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
RequireClientTablesInstalled(slot);
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is "
|
||||
SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is "
|
||||
"installed but no ClientSession is active. A slot may not fall through "
|
||||
"to a driver this role does not have",
|
||||
slot);
|
||||
std::abort();
|
||||
}
|
||||
return *session;
|
||||
}
|
||||
@@ -836,11 +835,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// NOT a guess and not a zero-length reply. A format this build cannot size is a
|
||||
// readback whose answer would be silently truncated, which is the one failure a
|
||||
// picture comparison cannot see.
|
||||
MGLOG_F("MGPipe: Fatal{UnsizedReadback, \"read_pixels\"} format=0x%04x type=0x%04x "
|
||||
SessionFail(MGFatalFamily::UnsizedReadback, "MGPipe: Fatal{UnsizedReadback, \"read_pixels\"} format=0x%04x type=0x%04x "
|
||||
"- the client must declare MGPReadbackInfo::DstSize and cannot size this "
|
||||
"pair; P5's reduced path reads RGBA/UNSIGNED_BYTE",
|
||||
static_cast<unsigned>(format), static_cast<unsigned>(type));
|
||||
std::abort();
|
||||
}
|
||||
return static_cast<Uint64>(bytesPerPixel);
|
||||
}
|
||||
@@ -882,29 +880,25 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// an oversize reply, so the only failures left are a wrong status or a SHORT one.
|
||||
void RequireReadbackReplyComplete(Int32 status, Uint64 replySize, Uint64 expected) {
|
||||
if (status == Wire::ReplySink::kStatusError) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyError, \"ReadPixels\"} - the readback answered ERROR; "
|
||||
SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"ReadPixels\"} - the readback answered ERROR; "
|
||||
"the destination is left untouched rather than filled with stale bytes");
|
||||
std::abort();
|
||||
}
|
||||
if (status == Wire::ReplySink::kStatusDeclined) {
|
||||
MGLOG_F("MGPipe: Fatal{ReadbackDeclined, \"ReadPixels\"} - the server has no "
|
||||
SessionFail(MGFatalFamily::ReadbackDeclined, "MGPipe: Fatal{ReadbackDeclined, \"ReadPixels\"} - the server has no "
|
||||
"GL.ReadPixels and DECLINED; a decline is a real answer for an acceptance "
|
||||
"row (R-5) but a blocking readback has no pixels to return, so it is a "
|
||||
"Fatal here rather than a buffer of stale bytes");
|
||||
std::abort();
|
||||
}
|
||||
if (status != Wire::ReplySink::kStatusOk) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyStatusInvalid, \"ReadPixels\"} - unknown reply status %d", status);
|
||||
std::abort();
|
||||
SessionFail(MGFatalFamily::ReplyStatusInvalid, "MGPipe: Fatal{ReplyStatusInvalid, \"ReadPixels\"} - unknown reply status %d", status);
|
||||
}
|
||||
if (replySize != expected) {
|
||||
MGLOG_F("MGPipe: Fatal{ReadbackReplyShort, \"ReadPixels %llu < %llu\"} - the OK "
|
||||
SessionFail(MGFatalFamily::ReadbackReplyShort, "MGPipe: Fatal{ReadbackReplyShort, \"ReadPixels %llu < %llu\"} - the OK "
|
||||
"reply carried fewer bytes than the read's own DstSize (CONTRACT-P5 row "
|
||||
"23's exact extent); the missing rows would otherwise be scattered as "
|
||||
"whatever the destination held",
|
||||
static_cast<unsigned long long>(replySize),
|
||||
static_cast<unsigned long long>(expected));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "GpuWritePending.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "ClientSession.h"
|
||||
// RunsAsTheServerRole: declared beside the wire emitters that need the same predicate, so this
|
||||
@@ -252,12 +253,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// UnmigratedVerbFatal), and it is what gives the hole a red spelling before the
|
||||
// transport arrives.
|
||||
if (!buffer.HasOutstandingGpuWrite()) return;
|
||||
MGLOG_F("MGPipe: Fatal{UnimplementedWritebackWait} - a ClientSession is active and buffer %u "
|
||||
SessionFail(MGFatalFamily::UnimplementedWritebackWait, "MGPipe: Fatal{UnimplementedWritebackWait} - a ClientSession is active and buffer %u "
|
||||
"still has an outstanding GPU write after its readback was emitted. The wait is "
|
||||
"ClientSession::EmitAndWait's (R-3: the reply slot id IS the record seq); P5 package "
|
||||
"b1 landed the third state and s1/c1 own the wait itself.",
|
||||
buffer.GetExternalIndex());
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Client
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "PersistentMapTracker.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include <MG_Pipe/MGPipeTypes.h>
|
||||
#include <MG_State/GLState/BufferState/BufferObject.h>
|
||||
@@ -640,9 +641,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void PersistentMapTracker::PushBlocksFor(BufferObject& buffer) {
|
||||
if (!PushIsArmed()) return;
|
||||
if (OnServerRole()) {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map "
|
||||
"producer belongs to the client; the server consumes transported bytes");
|
||||
std::abort();
|
||||
}
|
||||
PushBlocksForChecked(buffer);
|
||||
}
|
||||
@@ -856,9 +856,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void PersistentMapTracker::PushAllMembers() {
|
||||
if (!PushIsArmed()) return;
|
||||
if (OnServerRole()) {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map "
|
||||
"producer belongs to the client; the server consumes transported bytes");
|
||||
std::abort();
|
||||
}
|
||||
if (m_livePersistentMaps.empty()) return;
|
||||
// Copied out first: PushBlocksFor can erase its own entry (a member that stopped
|
||||
@@ -874,9 +873,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
void PersistentMapTracker::PushDrawConsumers() {
|
||||
if (!PushIsArmed()) return;
|
||||
if (OnServerRole()) {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map "
|
||||
"producer belongs to the client; the server consumes transported bytes");
|
||||
std::abort();
|
||||
}
|
||||
if (m_livePersistentMaps.empty()) return;
|
||||
// THE EPOCH SKIP, AND WHAT IT RESTS ON. A page of a tracked map is marked by
|
||||
@@ -1052,17 +1050,18 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// of them and no family grep could see them - this is one. (The other, WireLog.cpp's, is
|
||||
// the sanctioned funnel: every one of its callers passes a Fatal{ string of its own.)
|
||||
if (tier <= 1) {
|
||||
MGLOG_F("MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u "
|
||||
SessionFail(MGFatalFamily::UnimplementedAdoptTier,
|
||||
"MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u "
|
||||
"names an adoption tier P11 implements and P5 does not; P5 runs at T2 "
|
||||
"(emulate) only.",
|
||||
static_cast<unsigned>(tier), static_cast<unsigned>(tier));
|
||||
} else {
|
||||
MGLOG_F("MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is "
|
||||
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));
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote::Client
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// before every `set_dynamic_state` - hundreds of times a frame, and each one a real record.
|
||||
|
||||
#include "WireTables.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
|
||||
@@ -113,11 +114,10 @@ namespace MobileGL::MG_Remote::Client {
|
||||
RequireClientTablesInstalled(row);
|
||||
ClientSession* session = ClientSession::Active();
|
||||
if (session == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are "
|
||||
SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are "
|
||||
"installed but no ClientSession is active. A row may not fall through to "
|
||||
"a driver this role does not have",
|
||||
row);
|
||||
std::abort();
|
||||
}
|
||||
return *session;
|
||||
}
|
||||
@@ -129,12 +129,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
MG_Pipe::MGPBlobRef StageRequired(ClientSession& session, const char* row,
|
||||
const void* bytes, Uint64 count) {
|
||||
if (bytes == nullptr || count == 0) {
|
||||
MGLOG_F("MGPipe: Fatal{BlobMissing, \"%s\"} - the row's decoder requires a "
|
||||
SessionFail(MGFatalFamily::BlobMissing, "MGPipe: Fatal{BlobMissing, \"%s\"} - the row's decoder requires a "
|
||||
"declared blob and the call site handed over %llu bytes at %p. Under "
|
||||
"monolith the companion pointer carries them; under split they have to "
|
||||
"be staged, and there is nothing to stage",
|
||||
row, static_cast<unsigned long long>(count), bytes);
|
||||
std::abort();
|
||||
}
|
||||
return session.Encoder().StageBytes(bytes, count);
|
||||
}
|
||||
@@ -409,12 +408,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
ClientSession& session = RequireSession("ResourceRespecify");
|
||||
if (initialBytes != nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{UncarriedInitialBytes, \"resource_respecify\"} - a call "
|
||||
SessionFail(MGFatalFamily::UncarriedInitialBytes, "MGPipe: Fatal{UncarriedInitialBytes, \"resource_respecify\"} - a call "
|
||||
"site handed initial content to a split respecify. R-13.3 rules that "
|
||||
"initialBytes never crosses and that the content follows as "
|
||||
"resource_subdata; a caller that still passes it has bytes nothing will "
|
||||
"carry");
|
||||
std::abort();
|
||||
}
|
||||
// The scope rides in the descriptor's own pads (CONTRACT-P5 table 1 row 19b, LANDED)
|
||||
// and is written only through MGPipeSetRespecifiedLevel - three fields are one
|
||||
@@ -438,10 +436,9 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// - the generated acceptance rows through MGPipeTakeReplyBool, and now these two
|
||||
// escapes - answers ERROR with the same named Fatal.
|
||||
if (status == 2) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyError, \"resource_respecify\"} - the row answered "
|
||||
SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"resource_respecify\"} - the row answered "
|
||||
"ERROR, which is not an acceptance answer; folding it into accepted or "
|
||||
"refused would make a transport fault look like a resource decision");
|
||||
std::abort();
|
||||
}
|
||||
if (status == 1) ++g_declined;
|
||||
return status == 0;
|
||||
@@ -485,22 +482,20 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// the generated acceptance rows obey: status 2 is a transport fault, and returning
|
||||
// nullptr for it would make it indistinguishable from R-6's legitimate decline.
|
||||
if (status == 2) {
|
||||
MGLOG_F("MGPipe: Fatal{ReplyError, \"map_persistent\"} - the row answered ERROR, "
|
||||
SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"map_persistent\"} - the row answered ERROR, "
|
||||
"which is not an acceptance answer; a transport fault is not a resource "
|
||||
"decision and may not be folded into the decline R-6 predicts");
|
||||
std::abort();
|
||||
}
|
||||
if (status == 1) ++g_declined;
|
||||
// NOT "always nullptr": the answer is READ. R-6 says the server declines, and the
|
||||
// day it stops declining this returns what it actually said rather than what the
|
||||
// ruling predicted.
|
||||
if (status == 0) {
|
||||
MGLOG_F("MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server "
|
||||
SessionFail(MGFatalFamily::UnexpectedMapAccept, "MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server "
|
||||
"accepted a persistent map under split. R-6 makes the split answer a "
|
||||
"constant decline because there is no way to hand a host pointer across "
|
||||
"a process boundary in P5; a pointer arriving here is one this client "
|
||||
"cannot dereference");
|
||||
std::abort();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -522,12 +517,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
ClientSession& session = RequireSession("CreateShaderState");
|
||||
if (link == nullptr || spirv == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ArtefactsMissing, \"create_shader_state\"} - the record's "
|
||||
SessionFail(MGFatalFamily::ArtefactsMissing, "MGPipe: Fatal{ArtefactsMissing, \"create_shader_state\"} - the record's "
|
||||
"two typed companions are null. Under monolith the applier reads the "
|
||||
"modules out of spirv->generatedSpirv; under split there is nothing to "
|
||||
"serialise, and emitting the record anyway would create a CSO with no "
|
||||
"code");
|
||||
std::abort();
|
||||
}
|
||||
// EncodeProgramArchive APPENDS and never fails - everything it walks is owned
|
||||
// plain data - so an empty archive means the two structs themselves were empty,
|
||||
@@ -547,9 +541,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
static_cast<Uint64>(archive.size()));
|
||||
}
|
||||
if (archive.empty()) {
|
||||
MGLOG_F("MGPipe: Fatal{ArchiveEmpty, \"create_shader_state\"} - the program's "
|
||||
SessionFail(MGFatalFamily::ArchiveEmpty, "MGPipe: Fatal{ArchiveEmpty, \"create_shader_state\"} - the program's "
|
||||
"artefacts serialised to nothing");
|
||||
std::abort();
|
||||
}
|
||||
MG_Pipe::MGPProgramDesc record = *desc;
|
||||
for (Uint32 i = 0; i < 6; ++i) record.Spirv[i] = MG_Pipe::MGPBlobRef{};
|
||||
@@ -714,9 +707,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
|
||||
void RequireClientTablesInstalled(const char* row) {
|
||||
if (g_clientTablesUninstalled.load(std::memory_order_acquire)) {
|
||||
MGLOG_F("MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - the client tables are "
|
||||
SessionFail(MGFatalFamily::ClientTablesUninstalled, "MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - the client tables are "
|
||||
"being torn down; refusing before session or ring access", row);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// MobileGL - MobileGL/MG_Remote/FatalFunnel.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 <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "../Includes.h" // MGLOG_F and the umbrella; a .cpp may pull it, a Transport/ header may not
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
|
||||
namespace MobileGL::MG_Remote {
|
||||
|
||||
namespace {
|
||||
// One telemetry point (5.2). Bumped exactly once per death, before the abort, so a test
|
||||
// that forks a child and reads it back sees the increment even though the child is gone -
|
||||
// it reads its OWN process's counter for the funnel it drove, not the child's.
|
||||
std::atomic<std::uint64_t> g_sessionFaultCount{0};
|
||||
} // namespace
|
||||
|
||||
std::uint64_t SessionFaultCount() {
|
||||
return g_sessionFaultCount.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void SessionFail(MGFatalFamily family, const char* fmt, ...) {
|
||||
// The line, formatted exactly as the site's old MGLOG_F would have. Same buffer size and
|
||||
// same fallback as WireLogFatal, so a death that used either path reads identically.
|
||||
char line[512];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
const int written = std::vsnprintf(line, sizeof(line), fmt, args);
|
||||
va_end(args);
|
||||
if (written < 0) {
|
||||
std::snprintf(line, sizeof(line),
|
||||
"MG_Remote: unformattable Fatal diagnostic (format=%s)", fmt);
|
||||
}
|
||||
|
||||
g_sessionFaultCount.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
MGLOG_F("%s", line);
|
||||
// The stderr echo is the half a death test can see: Defines.h builds the logger with the
|
||||
// console sink off, so an MGLOG line reaches only the file - and a gtest death matcher
|
||||
// reads the child's stderr. Flushed because abort() does not flush stdio.
|
||||
std::fputs(line, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
|
||||
// `dl` step three publishes a SessionFault{family, detail, seq, op} frame to the peer
|
||||
// here, so the guest's log names which record ended the session instead of reading a bare
|
||||
// EOF. The family's wire projection is FatalCodeForFamily(family); until the frame lands
|
||||
// the family is still carried in the line above and counted here.
|
||||
(void)family;
|
||||
|
||||
std::abort();
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote
|
||||
@@ -0,0 +1,50 @@
|
||||
// MobileGL - MobileGL/MG_Remote/FatalFunnel.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
|
||||
|
||||
// P6 `dl` (CONTRACT-P6 5.2). THE ONE PLACE A SESSION DIES.
|
||||
//
|
||||
// Every `Fatal{...}` under MG_Remote/ used to be an MGLOG_F followed by its own std::abort().
|
||||
// a6 counted ~92 of them. Scattered, they gave the funnel's three benefits to nobody: there was
|
||||
// no single place to publish a SessionFault to the peer before dying, no single telemetry point,
|
||||
// and no way for a census to be sure a new death was named. This is that place.
|
||||
//
|
||||
// THE MESSAGE STRING IS PASSED VERBATIM, family word and all. The contract requires the log
|
||||
// lines, the CI greps and every red-once to be byte-identical, so SessionFail does not compose
|
||||
// the line from the family - it takes the SAME string the site used to hand MGLOG_F and forwards
|
||||
// it unchanged. The family enum rides ALONGSIDE, redundantly, so the funnel knows the coarse wire
|
||||
// FatalCode without parsing the string; a test asserts the enum's name appears in the string, so
|
||||
// the redundancy cannot rot into disagreement.
|
||||
//
|
||||
// NOT in WireLog.h: that header is deliberately dependency-light (ITransport.h's rule, the
|
||||
// include-graph purity gate), and this needs FatalFamily.h, which pulls the protocol header. The
|
||||
// header-layer sites that must stay pure keep using WireLogFatal, whose own abort routes through
|
||||
// the same publisher in WireLog.cpp.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <MG_Remote/FatalFamily.h>
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace MobileGL::MG_Remote {
|
||||
|
||||
// Logs the line (byte-identical to the old MGLOG_F), echoes it to stderr so a death test can
|
||||
// see it, publishes a SessionFault{family, ...} to the peer if one is connected, bumps the
|
||||
// telemetry counter, and aborts. `fmt` is the FULL original message, e.g.
|
||||
// "MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s ...".
|
||||
[[noreturn]]
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
#endif
|
||||
void SessionFail(MGFatalFamily family, const char* fmt, ...);
|
||||
|
||||
// How many times a session has ended through the funnel this process. Exit gate S8 asserts
|
||||
// this is zero over a whole good run; a test reads it to prove the funnel was reached.
|
||||
::std::uint64_t SessionFaultCount();
|
||||
|
||||
} // namespace MobileGL::MG_Remote
|
||||
@@ -7,6 +7,7 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "SurfaceOpCodec.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Remote/Protocol/generated/protocol_generated.h>
|
||||
@@ -207,22 +208,24 @@ namespace MobileGL::MG_Remote {
|
||||
const SurfaceWireError error = DecodeWireSurfaceOp(op, &frame);
|
||||
if (error != SurfaceWireError::None) {
|
||||
if (error == SurfaceWireError::AndroidNativeWindowArrived) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedSurface, \"AndroidNativeWindow@P12\"} - a wire "
|
||||
SessionFail(MGFatalFamily::UnmigratedSurface,
|
||||
"MGPipe: Fatal{UnmigratedSurface, \"AndroidNativeWindow@P12\"} - a wire "
|
||||
"SurfaceOp (%s) named an ANativeWindow*, which is a pointer into the "
|
||||
"CLIENT's process and means nothing here. Real window arrival is P12; "
|
||||
"until then the spawn surface path is pbuffer/surfaceless only",
|
||||
::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind()));
|
||||
} else if (error == SurfaceWireError::MetalLayerArrived) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedSurface, \"MetalLayer@P12\"} - a wire "
|
||||
SessionFail(MGFatalFamily::UnmigratedSurface,
|
||||
"MGPipe: Fatal{UnmigratedSurface, \"MetalLayer@P12\"} - a wire "
|
||||
"SurfaceOp named a CAMetalLayer* in the CLIENT's process. "
|
||||
"Real window arrival is P12");
|
||||
} else {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp\"} - a wire surface op "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption,
|
||||
"MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp\"} - a wire surface op "
|
||||
"failed validation: %s (wire kind %u, window kind %u)",
|
||||
SurfaceWireErrorName(error), static_cast<unsigned>(op.kind()),
|
||||
static_cast<unsigned>(op.windowKind()));
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
const MobileGLResult rc = Server::ServerLoopInstance().RunSurfaceControlFrame(frame);
|
||||
if (replyOut != nullptr) *replyOut = frame;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// P5 package v1: the applier bridge, and the consumer for contract 7's five class-B verbs.
|
||||
|
||||
#include "PipeApplier.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "ServerSession.h"
|
||||
#include "../Transport/ReplySlot.h"
|
||||
@@ -326,11 +327,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// is a protocol fault, because such a client is pacing on this answer and a 0 would
|
||||
// acknowledge a frame nobody asked about.
|
||||
if (present.FrameSerial == 0 && MGPipeServerPublishesRunAhead()) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"Present.FrameSerial\"} - a run-ahead "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"Present.FrameSerial\"} - a run-ahead "
|
||||
"server was handed present serial 0. The client mints this 1-based and "
|
||||
"waits on it for its credit (CONTRACT-P5E §2.4); returning a credit for "
|
||||
"serial 0 would release a wait that is asking about frame N");
|
||||
std::abort();
|
||||
}
|
||||
m_lastPresentSerial = present.FrameSerial != 0 ? present.FrameSerial : m_presents;
|
||||
// §2.4's other half, and the reason ServerSession::ReturnPresentCredit has had no
|
||||
@@ -385,11 +385,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The decoder always passes its ReplySink; a null one means the applier was built
|
||||
// without a reply pool, and answering nothing would leave the client's barrier
|
||||
// waiting for a slot that never gets stamped - a hang, not a wrong picture.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"read_pixels without a reply sink\"} - "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"read_pixels without a reply sink\"} - "
|
||||
"the pixels' only destination in P5 is SEG_REPLY (contract table 1 row 23) "
|
||||
"and a client blocked on seq %llu would never be answered",
|
||||
static_cast<unsigned long long>(seq));
|
||||
std::abort();
|
||||
}
|
||||
const MG_Backend::GlobalBackendFunctionsTable* table = Table("read_pixels");
|
||||
if (table == nullptr || table->GL.ReadPixels == nullptr) {
|
||||
@@ -482,10 +481,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// rather than rendering nothing. Named "(server sink)" in the message so a log reader can
|
||||
// tell which half is missing.
|
||||
[[noreturn]] static void ServerUnmigratedVerbFatal(const char* slot) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"%s\"} (server sink: the record crossed and "
|
||||
SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"} (server sink: the record crossed and "
|
||||
"ServerVerbSink has no body for it yet - CONTRACT-P5B.md names the package)",
|
||||
slot);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// P5b d1 (MG_Remote/CONTRACT-P5B.md §2 d1): draw_vbo's whole cross product. The record
|
||||
@@ -553,22 +551,20 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (maxTouched < 0) return;
|
||||
const Uint32 required = static_cast<Uint32>(maxTouched) + 1u;
|
||||
if (st.SamplerViewStart != 0 || st.SamplerViewCount < required) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - %s applies with "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - %s applies with "
|
||||
"units 0..%d touched, so set_sampler_views must carry Start=0 and Count >= %u "
|
||||
"(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which drops "
|
||||
"the sync of at least one texture this draw samples",
|
||||
verb, static_cast<int>(maxTouched), required, st.SamplerViewStart,
|
||||
st.SamplerViewCount);
|
||||
std::abort();
|
||||
}
|
||||
if (st.SamplerStateStart != 0 || st.SamplerStateCount < required) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - %s applies with "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - %s applies with "
|
||||
"units 0..%d touched, so bind_sampler_states must carry Start=0 and Count >= %u "
|
||||
"(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which leaves "
|
||||
"an earlier draw's sampler object on at least one unit",
|
||||
verb, static_cast<int>(maxTouched), required, st.SamplerStateStart,
|
||||
st.SamplerStateCount);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
@@ -1147,13 +1143,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// accepted resets IS the expected value; anything else means the two ends disagree
|
||||
// about how many make-current edges have crossed, which no backend answer can fix.
|
||||
if (reset.ContextSerial != m_applierResetSerial) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"} - the "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"} - the "
|
||||
"record carries %llu and this session has accepted %llu reset(s); the "
|
||||
"serial is asserted against the session's own count, not dispatched on "
|
||||
"(one context per session in P5c)",
|
||||
static_cast<unsigned long long>(reset.ContextSerial),
|
||||
static_cast<unsigned long long>(m_applierResetSerial));
|
||||
std::abort();
|
||||
}
|
||||
++m_applierResetSerial;
|
||||
// THE WHOLE POINT OF THE RECORD: the reset runs HERE, on the apply thread, against
|
||||
@@ -1170,16 +1165,14 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// emits NOTHING in that case (§5.2) - so a null handle arriving here is corruption,
|
||||
// not a no-op.
|
||||
if (MG_Pipe::MGPipeHandleIsNull(death.Handle)) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"} - a null "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"} - a null "
|
||||
"handle never crosses: the client emits nothing for an object its own "
|
||||
"allocator cannot resolve (CONTRACT-P5C.md §5.2)");
|
||||
std::abort();
|
||||
}
|
||||
if (death.Kind >= static_cast<Uint32>(MG_Pipe::MGPipeKind::KindCount)) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Kind\"} - %u is not an "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Kind\"} - %u is not an "
|
||||
"MGPipeKind",
|
||||
static_cast<unsigned>(death.Kind));
|
||||
std::abort();
|
||||
}
|
||||
// The per-kind release, keyed by the handle the record carried. A false answer is
|
||||
// NOT a decline: the kind's own delete opcode may already have released the twin
|
||||
@@ -1201,10 +1194,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
void PipeApplier::Attach(Transport::RingControl* control, MG_Backend::BackendObject* backend) {
|
||||
if (control == nullptr || m_segments == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::Attach\"} - no control "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::Attach\"} - no control "
|
||||
"page or no segment table; ServerSession::Accept builds both before the "
|
||||
"apply thread starts");
|
||||
std::abort();
|
||||
}
|
||||
m_verbs.SetBackend(backend);
|
||||
m_decoder = Wire::PipeWireDecoder(control, m_segments, m_replies);
|
||||
@@ -1228,10 +1220,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
|
||||
Bool PipeApplier::ApplyOne(const Transport::RingRecordView& record) {
|
||||
if (!m_attached) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::ApplyOne before Attach\"} "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::ApplyOne before Attach\"} "
|
||||
"- a record reached the applier with no decoder; the apply thread calls "
|
||||
"Attach once before its first pop");
|
||||
std::abort();
|
||||
}
|
||||
// R-1's INVARIANT, THE SERVER'S HALF (table 3's gPipeInputs row, c1-v1 8.1). The flag
|
||||
// is raised for the WHOLE of this function and not only around DecodeAndApply: the
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// P5 package v1: the apply thread, its affinity, its parking, and the EGL ownership move.
|
||||
|
||||
#include "ServerLoop.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
#include <MG_Backend/MGPipe/PipeInputs.h>
|
||||
|
||||
#include <Config.h>
|
||||
@@ -622,10 +623,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// Ring.h's own rule: a header the producer could not have written is
|
||||
// Fatal{ProtocolCorruption}, never a retry. Retrying re-reads the same bytes
|
||||
// for ever; skipping desynchronises seq, and seq IS the reply-slot id.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SEG_CMD record header\"} - the "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SEG_CMD record header\"} - the "
|
||||
"consumer refused a record header at applied seq %llu",
|
||||
static_cast<unsigned long long>(consumer.AppliedSeq()));
|
||||
std::abort();
|
||||
}
|
||||
if (!popped) break;
|
||||
++applied;
|
||||
@@ -636,13 +636,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// and PipeWireDecoder keeps its own count; a batched publish would move one and not
|
||||
// the other, which a single counter could not have told apart (w1-v1 5).
|
||||
if (applier.DecoderAppliedSeq() != consumer.AppliedSeq()) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"appliedSeq batched\"} - the "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"appliedSeq batched\"} - the "
|
||||
"session's watermark is %llu and the decoder applied %llu records. P5 "
|
||||
"forbids batching appliedSeq (R-9): the verb barrier's waiter reads it, "
|
||||
"and a watermark ahead of the decoder promises work that has not run",
|
||||
static_cast<unsigned long long>(consumer.AppliedSeq()),
|
||||
static_cast<unsigned long long>(applier.DecoderAppliedSeq()));
|
||||
std::abort();
|
||||
}
|
||||
// RETIRE. MANDATORY, not optional: RingProducer::FreeBytes() reclaims against
|
||||
// retiredTail only, and w1's SEG_STAGE linear allocator reclaims on retiredSeq - so
|
||||
@@ -705,13 +704,12 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// render correctly with the context on the wrong thread - the one outcome R-1
|
||||
// exists to make impossible. Fatal by name, never a fallback; compare
|
||||
// CreateBackend's default: arm, which also refuses rather than substitutes.
|
||||
MGLOG_F("MGPipe: Fatal{ApplyThreadNotRunning, \"EGL on the app thread\"} - a "
|
||||
SessionFail(MGFatalFamily::ApplyThreadNotRunning, "MGPipe: Fatal{ApplyThreadNotRunning, \"EGL on the app thread\"} - a "
|
||||
"control frame reached RunSurfaceControlFrame with the server's backend "
|
||||
"built but no mgl-srv-apply thread running (ClientSession::Start failed "
|
||||
"after ServerLoop::CreateBackend). Dispatching it inline would make the "
|
||||
"context current on the APP thread - the split lane's whole premise. "
|
||||
"Refusing by name rather than falling back to monolith");
|
||||
std::abort();
|
||||
}
|
||||
// Backend already gone (post-Stop teardown, or the pre-Init window): the dispatch's
|
||||
// null-backend arm would answer NOT_INITIALIZED anyway, so that is the honest result
|
||||
@@ -805,7 +803,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// run on into the client's teardown, reading rings the client is about to unmap -
|
||||
// a use-after-free whose only symptom is an intermittent crash somewhere else.
|
||||
// Aborting here is red, immediate, and names the cause.
|
||||
MGLOG_F("MGPipe: Fatal{ApplyThreadJoinTimeout} - mgl-srv-apply did not exit within "
|
||||
SessionFail(MGFatalFamily::ApplyThreadJoinTimeout, "MGPipe: Fatal{ApplyThreadJoinTimeout} - mgl-srv-apply did not exit within "
|
||||
"%u ms of Stop(). Stop() published m_stopRequested (in the park predicate) "
|
||||
"BEFORE it rang, so a plain Notify should already have un-parked the thread; "
|
||||
"Doorbell::Kill() (Doorbell.h, CondVarDoorbell::Kill; table 3 step 2) is "
|
||||
@@ -814,7 +812,6 @@ namespace MobileGL::MG_Remote::Server {
|
||||
"lost, not slow. Aborting rather than detaching: a detached apply thread still "
|
||||
"owns the context and would read rings the client is about to unmap",
|
||||
kJoinTimeoutMs);
|
||||
std::abort();
|
||||
}
|
||||
m_thread.join();
|
||||
// The identity is NOT cleared here any more: the thread clears it itself, under
|
||||
@@ -882,10 +879,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
MG_Backend::WindowBackend WindowBackendFromFrameValue(Int value) {
|
||||
if (value < static_cast<Int>(MG_Backend::WindowBackend::Unknown) ||
|
||||
value >= static_cast<Int>(MG_Backend::WindowBackend::WindowBackendCount)) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.windowBackend\"} - a "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.windowBackend\"} - a "
|
||||
"control frame carried window backend tag %d, which names no "
|
||||
"WindowBackend", value);
|
||||
std::abort();
|
||||
}
|
||||
return static_cast<MG_Backend::WindowBackend>(value);
|
||||
}
|
||||
@@ -1069,9 +1065,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// A kind the switch does not know never legitimately posts (RunSurfaceControlFrame
|
||||
// refuses None), so reaching this line means the slot's bytes were not written by the
|
||||
// poster at all - the ring's Fatal{ProtocolCorruption} discipline, one channel over.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.kind\"} - the control slot held "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.kind\"} - the control slot held "
|
||||
"kind %d, which names no dispatchable op", static_cast<int>(frame.kind));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// P5 package s1: the server half of a session.
|
||||
|
||||
#include "ServerSession.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include "../CapsCodec.h"
|
||||
#include "../Protocol/generated/protocol_generated.h"
|
||||
@@ -115,7 +116,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// DynamicBackendParameters still carries SizeT, so a peer built from a different
|
||||
// tree hands over a caps block whose members are at different offsets and whose
|
||||
// bytes are all individually plausible.
|
||||
MGLOG_F("MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
SessionFail(MGFatalFamily::AbiMismatch, "MGPipe: Fatal{AbiMismatch, \"%s\"} ours=%llu theirs=%llu ourBuild=%s "
|
||||
"theirBuild=%s - the two peers were not built from the same struct shapes, "
|
||||
"and there is no downgrade path: the caps block's size is ABI-dependent "
|
||||
"(MGPipeTypes.h:145-146) and every field past the first difference would be "
|
||||
@@ -123,7 +124,6 @@ namespace MobileGL::MG_Remote::Server {
|
||||
what, static_cast<unsigned long long>(ours),
|
||||
static_cast<unsigned long long>(theirs),
|
||||
ourStamp == nullptr ? "?" : ourStamp, theirStamp == nullptr ? "?" : theirStamp);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// MOBILEGL_IPC_RING_MB / MOBILEGL_IPC_STAGE_MB, actually applied.
|
||||
@@ -164,7 +164,7 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// SetCapabilityBits / SetConsumedSubsystems for why the one that used to be here was
|
||||
// the phase's marquee defect committed from the server's side.
|
||||
[[noreturn]] void FatalUnsetCallMask(Bool capBitsSet, Bool consumedSet) {
|
||||
MGLOG_F("MGPipe: Fatal{UnsetCallMask} - %s%s%s was never set on this ServerSession, "
|
||||
SessionFail(MGFatalFamily::UnsetCallMask, "MGPipe: Fatal{UnsetCallMask} - %s%s%s was never set on this ServerSession, "
|
||||
"and there is no default: a guessed consumer mask makes the client's R-8 "
|
||||
"liveness gates answer from a server-side fact the server never stated. The "
|
||||
"client would stop emitting whole record families, clear its dirty flags on "
|
||||
@@ -174,7 +174,6 @@ namespace MobileGL::MG_Remote::Server {
|
||||
capBitsSet ? "" : "SetCapabilityBits",
|
||||
(!capBitsSet && !consumedSet) ? " and " : "",
|
||||
consumedSet ? "" : "SetConsumedSubsystems");
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---- P5c ev: the reverse channel's PRODUCER callbacks (CONTRACT-P5C §4.1) --------
|
||||
@@ -192,12 +191,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// a Reserve that returns nullptr is Fatal{EventRingOverflow}. CountDrop and the
|
||||
// eventRingFull latch stay built and stay unused-by-policy; P9 owns the policy.
|
||||
[[noreturn]] void FatalEventRingOverflow(const char* eventName, Uint64 payloadBytes) {
|
||||
MGLOG_F("MGPipe: Fatal{EventRingOverflow} - the producer of %s could not reserve "
|
||||
SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - the producer of %s could not reserve "
|
||||
"%llu bytes on SEG_EVENT. P5c's events are lossless and a full ring under "
|
||||
"lockstep is a producer burst no measured workload has, so this is a "
|
||||
"defect, not a drop (CONTRACT-P5C §4.4; the drop policy is P9's)",
|
||||
eventName, static_cast<unsigned long long>(payloadBytes));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---- P5e (ra), CONTRACT-P5E §2.6: the ring stops being a Fatal and becomes a queue --
|
||||
@@ -239,14 +237,13 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// be a hang with a comment on it.
|
||||
const Uint64 cap = session.Events().Ring().MaxRecordBytes();
|
||||
if (payloadBytes + sizeof(Transport::RingRecordHeader) > cap) {
|
||||
MGLOG_F("MGPipe: Fatal{EventRingOverflow} - %s needs %llu bytes and SEG_EVENT "
|
||||
SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - %s needs %llu bytes and SEG_EVENT "
|
||||
"caps ONE record at %llu (half its capacity). Flow control cannot help: "
|
||||
"no amount of draining makes a record fit a ring that is too small for "
|
||||
"it. Raise MOBILEGL_IPC_EVENT_KB or slice the event at its producer, as "
|
||||
"the writeback path already does",
|
||||
eventName, static_cast<unsigned long long>(payloadBytes),
|
||||
static_cast<unsigned long long>(cap));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Transport::RingControl& control = session.Control();
|
||||
@@ -261,12 +258,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
session.PublishEvents();
|
||||
if (!drained() && !bell.Wait(control.consumerParked, drained, /*spinUs=*/0,
|
||||
kEventBacklogWaitMs)) {
|
||||
MGLOG_F("MGPipe: Fatal{EventRingOverflow} - %s waited %u ms for the client "
|
||||
SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - %s waited %u ms for the client "
|
||||
"to drain SEG_EVENT and it never did. Under run-ahead the apply "
|
||||
"thread parks on this ring (CONTRACT-P5E §2.6); a client that does "
|
||||
"not drain is one that is not running",
|
||||
eventName, kEventBacklogWaitMs);
|
||||
std::abort();
|
||||
}
|
||||
slot = session.Events().Reserve(kind, payloadBytes);
|
||||
if (slot != nullptr) return slot;
|
||||
@@ -274,11 +270,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// Two full rounds against a ring the record provably fits: something else is
|
||||
// producing into it, which on this side is impossible (one producer, the apply
|
||||
// thread, by construction).
|
||||
MGLOG_F("MGPipe: Fatal{EventRingOverflow} - %s could not reserve %llu bytes on an "
|
||||
SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - %s could not reserve %llu bytes on an "
|
||||
"emptied SEG_EVENT. The apply thread is SEG_EVENT's only producer, so a "
|
||||
"drained ring that still refuses a record it fits is a corrupt cursor set",
|
||||
eventName, static_cast<unsigned long long>(payloadBytes));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// Accept owns the single process callback table. Its owner need not be the
|
||||
@@ -286,9 +281,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
ServerSession& ReverseCallbackOwner(const char* callback) {
|
||||
auto* session = ServerSession::Active();
|
||||
if (session == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"%s.session-missing\"} - "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"%s.session-missing\"} - "
|
||||
"a reverse callback has no accepted session owner", callback);
|
||||
std::abort();
|
||||
}
|
||||
return *session;
|
||||
}
|
||||
@@ -300,12 +294,11 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// The backend handed over a segment-tagged blobref. The ONLY legal shape at
|
||||
// this boundary is the monolith one - Offset is the mapped address, valid
|
||||
// for this call - because the segment copy is THIS function's own job.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Seg\"} - the "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Seg\"} - the "
|
||||
"writeback producer was handed Seg %u; at the backend boundary the "
|
||||
"blobref names the backend's own mapped bytes (Seg = "
|
||||
"kMGHostSpanSegNone) and the copy into SEG_EVENT is the producer's",
|
||||
bytes.Seg);
|
||||
std::abort();
|
||||
}
|
||||
const Uint64 payloadBytes = sizeof(Transport::EventBufferWritebackHead) + bytes.Size;
|
||||
void* slot = ReserveEventOrBlock(session, Transport::kEventBufferWriteback,
|
||||
@@ -375,11 +368,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
template <typename Fn>
|
||||
void InstallReverseCallback(Fn& entry, Fn producer) {
|
||||
if (entry != nullptr && entry != producer) {
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - a reverse "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - a reverse "
|
||||
"MGPipeCallbacks entry is already claimed by a different function. With "
|
||||
"an active transport the four reverse entries are the server "
|
||||
"session's producers; the client installs them under monolith only");
|
||||
std::abort();
|
||||
}
|
||||
entry = producer;
|
||||
}
|
||||
@@ -442,9 +434,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
if (!g_sessionOwner.compare_exchange_strong(expectedOwner, this,
|
||||
std::memory_order_acq_rel, std::memory_order_acquire)) {
|
||||
if (expectedOwner == this) return MOBILEGL_ERR_INVALID_ARGUMENT;
|
||||
MGLOG_F("MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - another "
|
||||
SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - another "
|
||||
"ServerSession already owns or is accepting the process reverse channel");
|
||||
std::abort();
|
||||
}
|
||||
struct ReleaseFailedClaim {
|
||||
ServerSession* self;
|
||||
@@ -720,9 +711,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Transport::RingControl& ServerSession::Control() {
|
||||
Transport::RingControl* control = m_shm.CmdControl();
|
||||
if (control == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ServerSession::Control\"} - the control "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::Control\"} - the control "
|
||||
"page does not exist until Accept() has mapped SEG_CMD");
|
||||
std::abort();
|
||||
}
|
||||
return *control;
|
||||
}
|
||||
@@ -746,9 +736,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return *m_externalConsumerBell;
|
||||
}
|
||||
if (m_transport == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ConsumerDoorbell\"} - no "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ConsumerDoorbell\"} - no "
|
||||
"transport; Accept() has not run");
|
||||
std::abort();
|
||||
}
|
||||
if (m_transport->Role() == Transport::TransportRole::InProcess) {
|
||||
return static_cast<Transport::InProcessTransport*>(m_transport)->SelfDoorbell();
|
||||
@@ -756,10 +745,9 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// P6: SocketTransport's pair. The accessors stay off ITransport by ruling (contract
|
||||
// §3.9) precisely so that this stays one switch in one file rather than two virtuals
|
||||
// every transport has to invent a home for.
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ConsumerDoorbell\"} - transport "
|
||||
SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ConsumerDoorbell\"} - transport "
|
||||
"role %u has no doorbell pair yet; that is P6's SocketTransport",
|
||||
static_cast<unsigned>(m_transport->Role()));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Transport::Doorbell& ServerSession::ProducerDoorbell() {
|
||||
@@ -767,17 +755,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return *m_externalProducerBell;
|
||||
}
|
||||
if (m_transport == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ProducerDoorbell\"} - no "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ProducerDoorbell\"} - no "
|
||||
"transport; Accept() has not run");
|
||||
std::abort();
|
||||
}
|
||||
if (m_transport->Role() == Transport::TransportRole::InProcess) {
|
||||
return static_cast<Transport::InProcessTransport*>(m_transport)->PeerDoorbell();
|
||||
}
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ProducerDoorbell\"} - transport "
|
||||
SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ProducerDoorbell\"} - transport "
|
||||
"role %u has no doorbell pair yet; that is P6's SocketTransport",
|
||||
static_cast<unsigned>(m_transport->Role()));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
Transport::SessionSegments& ServerSession::Shm() { return m_shm; }
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
// someone who reads the comment and not this file.
|
||||
|
||||
#include "PipeWireCodec.h"
|
||||
#include <MG_Remote/FatalFunnel.h>
|
||||
|
||||
#include <Config.h>
|
||||
#include <MG_Pipe/MGPipeRenderStateSpans.h>
|
||||
@@ -375,14 +376,12 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
void WireProtocolFatal(const char* what, const char* detail) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what, detail != nullptr ? detail : "");
|
||||
std::abort();
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what, detail != nullptr ? detail : "");
|
||||
}
|
||||
|
||||
void WireProtocolFatalAt(const char* what, Uint64 got, Uint64 expected) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what,
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what,
|
||||
static_cast<unsigned long long>(got), static_cast<unsigned long long>(expected));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
@@ -458,20 +457,18 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// The monolith shape: Seg = None, Offset = a host address, Size = 0. Legal
|
||||
// there, and precisely what must not cross (rule A). Reading it as "absent"
|
||||
// would silently drop the bytes of every record an unconverted emitter sent.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} a blob with Size 0 "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} a blob with Size 0 "
|
||||
"declares Seg=%u Offset=%llu; under split a blobref is either fully "
|
||||
"declared or all three fields zero",
|
||||
WireOpName(op), static_cast<unsigned>(blob.Seg),
|
||||
static_cast<unsigned long long>(blob.Offset));
|
||||
std::abort();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (blob.Seg == kSegNone) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} Size=%llu with no segment "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} Size=%llu with no segment "
|
||||
"(R-2.3): a non-zero Size must name a real SEG_*",
|
||||
WireOpName(op), static_cast<unsigned long long>(blob.Size));
|
||||
std::abort();
|
||||
}
|
||||
// R-2.3's SECOND HALF: "inside SOME segment" IS NOT THE RULE. Contract table 1 gives
|
||||
// every client->server content blob - groups A, B and C, all nineteen rows - the ONE
|
||||
@@ -493,7 +490,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// seventh name would be a token nothing else in the tree recognises. The SEGMENT is in
|
||||
// the message, which is what has to be greppable.
|
||||
if (blob.Seg != kSegStage) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu "
|
||||
"size=%llu is not SEG_STAGE(%u); every client->server content blob is "
|
||||
"staged whole in SEG_STAGE (contract table 1 groups A/B/C, R-10) and no "
|
||||
"other segment may carry one",
|
||||
@@ -501,24 +498,21 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
static_cast<unsigned long long>(blob.Offset),
|
||||
static_cast<unsigned long long>(blob.Size),
|
||||
static_cast<unsigned>(kSegStage));
|
||||
std::abort();
|
||||
}
|
||||
if (segments.Resolve(blob.Seg, blob.Offset, blob.Size) == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu size=%llu "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu size=%llu "
|
||||
"does not lie inside that segment (R-2.3)",
|
||||
WireOpName(op), static_cast<unsigned>(blob.Seg),
|
||||
static_cast<unsigned long long>(blob.Offset),
|
||||
static_cast<unsigned long long>(blob.Size));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
void RequireDeclaredBlob(MGPWireOp op, const MGPBlobRef& blob, const SegmentTable& segments) {
|
||||
if (blob.Size == 0) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} the record's own fields say "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} the record's own fields say "
|
||||
"it carries content and its blob declares none (R-2.2 / rule A)",
|
||||
WireOpName(op));
|
||||
std::abort();
|
||||
}
|
||||
CheckBlobIsHonest(op, blob, segments);
|
||||
}
|
||||
@@ -554,12 +548,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// resolver, reads outside the segment. P5 emits no spans, so this was latent; P8 arms
|
||||
// it, which is exactly when nobody will be reading this code.
|
||||
if (segments.Resolve(span.Seg, span.Offset, span.Size) == nullptr) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"host-span\"} seg=%u offset=%llu "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"host-span\"} seg=%u offset=%llu "
|
||||
"size=%llu does not lie inside that segment (R-2.3 arm 4)",
|
||||
static_cast<unsigned>(span.Seg),
|
||||
static_cast<unsigned long long>(span.Offset),
|
||||
static_cast<unsigned long long>(span.Size));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,14 +848,13 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
|
||||
const Uint64 need = Align8(size);
|
||||
if (need > m_stageCapacity) {
|
||||
MGLOG_F("MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob cannot fit a "
|
||||
SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob cannot fit a "
|
||||
"%llu byte staging segment at any occupancy; a blob is staged whole, so a "
|
||||
"row that cuts its content at MGPipeStageChunkBytes() never reaches this - "
|
||||
"raise MOBILEGL_IPC_STAGE_MB or report the record type with no cut to the "
|
||||
"integrator",
|
||||
static_cast<unsigned long long>(size),
|
||||
static_cast<unsigned long long>(m_stageCapacity));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
bool reclaimed = false;
|
||||
@@ -907,15 +899,14 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// therefore no reverse channel to drain.
|
||||
if (m_stageWaitHook != nullptr) m_stageWaitHook(m_stageWaitSelf);
|
||||
if (!ready() && !m_stageRetirementBell->Wait(m_control->producerParked, ready, 0, 5000)) {
|
||||
MGLOG_F("MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} producer wait "
|
||||
SessionFail(MGFatalFamily::RetirementWaitFailed, "MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} producer wait "
|
||||
"ended before the pending allocation retired (shutdown or timeout)");
|
||||
std::abort();
|
||||
}
|
||||
if (m_stageWaitHook != nullptr) m_stageWaitHook(m_stageWaitSelf);
|
||||
}
|
||||
ReclaimStagedBytes();
|
||||
}
|
||||
MGLOG_F("MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu "
|
||||
SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu "
|
||||
"byte staging segment with %llu bytes still in flight (retiredSeq=%llu); a "
|
||||
"blob is staged whole and only the rows that cut at the stage chunk budget "
|
||||
"stay small - raise MOBILEGL_IPC_STAGE_MB or report that row to the integrator",
|
||||
@@ -924,7 +915,6 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
static_cast<unsigned long long>(m_stageHead - m_stageTail),
|
||||
static_cast<unsigned long long>(
|
||||
m_control != nullptr ? m_control->Progress.retiredSeq.load(std::memory_order_acquire) : 0));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
MGPBlobRef PipeWireEncoder::StageBytes(const void* bytes, Uint64 size) {
|
||||
@@ -1020,14 +1010,13 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
if (total > m_cmd->MaxRecordBytes()) {
|
||||
// R-10's record bound. The content rows cut their BLOBS at the stage chunk budget
|
||||
// (MGPipeStageChunkBytes); a record's own bytes have no such budget and die here.
|
||||
MGLOG_F("MGPipe: Fatal{RingOverrun, \"%s\"} a %llu byte record exceeds "
|
||||
SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"%s\"} a %llu byte record exceeds "
|
||||
"RingProducer::MaxRecordBytes() == %llu (half of a %llu byte SEG_CMD); the stage chunk "
|
||||
"budget cuts blobs, not a record's own bytes - report the row to the "
|
||||
"integrator",
|
||||
WireOpName(op), static_cast<unsigned long long>(total),
|
||||
static_cast<unsigned long long>(m_cmd->MaxRecordBytes()),
|
||||
static_cast<unsigned long long>(m_cmd->Capacity()));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// MGPipeCallFlags -> RingRecordFlags. See the file header: these are two flag spaces
|
||||
@@ -1131,13 +1120,12 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// here and a Fatal on a peer - exactly the asymmetry EncodeRecord's own
|
||||
// comment says it exists to prevent.
|
||||
if (op == MGPWireOp::CreateShaderState && i < 6 && blob.Size != 0) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} "
|
||||
"declares %llu bytes at the ENCODER; under split the modules travel "
|
||||
"inside the Reflection archive and the six per-stage runs stay "
|
||||
"undeclared",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long long>(blob.Size));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1347,11 +1335,10 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// then fails for ever: the barrier HANGS rather than returning something wrong, which
|
||||
// is the harder failure to diagnose of the two.
|
||||
if ((MGPipeCallFlagsFor(op) & static_cast<Uint32>(kReplySlot)) == 0) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} the decoder tried to answer into "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the decoder tried to answer into "
|
||||
"a reply slot for a call the catalogue gives no kReplySlot; s1 sizes "
|
||||
"ReplyPool from MGPipeCallFlagsFor and reserved none",
|
||||
WireOpName(op));
|
||||
std::abort();
|
||||
}
|
||||
if (m_replies != nullptr) {
|
||||
m_replies->PostReply(seq, status, bytes, size);
|
||||
@@ -1376,23 +1363,21 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// the audit has stopped covering the carrier, which is the same failure as no audit at
|
||||
// all - the reason the run-count overflow just below is a Fatal too.
|
||||
if (blob.Size == 0 || blob.Seg != kSegStage) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} the audit was asked to record a "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the audit was asked to record a "
|
||||
"resolved run with seg=%u size=%llu; only declared SEG_STAGE(%u) runs "
|
||||
"reach the poison fill (R-2.5)",
|
||||
WireOpName(op), static_cast<unsigned>(blob.Seg),
|
||||
static_cast<unsigned long long>(blob.Size),
|
||||
static_cast<unsigned>(kSegStage));
|
||||
std::abort();
|
||||
}
|
||||
if (m_resolvedCount >= sizeof(m_resolved) / sizeof(m_resolved[0])) {
|
||||
// LOUD, NOT A SILENT DROP. This array is what the 0xDD fill covers, and a poison
|
||||
// that quietly stopped covering a run is the same failure as no poison at all -
|
||||
// rule C's only mechanical control going dark without a line in the log.
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} more than %llu SEG_STAGE runs in "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} more than %llu SEG_STAGE runs in "
|
||||
"one record; the audit fill would stop covering them",
|
||||
WireOpName(op),
|
||||
static_cast<unsigned long long>(sizeof(m_resolved) / sizeof(m_resolved[0])));
|
||||
std::abort();
|
||||
}
|
||||
m_resolved[m_resolvedCount++] = blob;
|
||||
}
|
||||
@@ -1508,7 +1493,7 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
// record type)`; it CANNOT SEE THE TAIL, so this is the first thing that holds a
|
||||
// record declaring Count = 4000 while carrying 8 bytes to its own arithmetic.
|
||||
if (size != layout.TotalBytes) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"%s\"} the record declares Size=%llu and "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the record declares Size=%llu and "
|
||||
"its own count fields describe %llu bytes (fixed payload %llu + tails "
|
||||
"%llu/%llu/%llu)",
|
||||
WireOpName(op), static_cast<unsigned long long>(size),
|
||||
@@ -1517,7 +1502,6 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
static_cast<unsigned long long>(layout.TailBytes[0]),
|
||||
static_cast<unsigned long long>(layout.TailBytes[1]),
|
||||
static_cast<unsigned long long>(layout.TailBytes[2]));
|
||||
std::abort();
|
||||
}
|
||||
|
||||
// The record's own ordinal, which is its reply-slot id (R-3). 1-based, and m_applySeq
|
||||
@@ -1791,12 +1775,11 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
for (Uint32 i = 0; i < 6; ++i) {
|
||||
CheckBlobIsHonest(op, desc.Spirv[i], *m_segments);
|
||||
if (desc.Spirv[i].Size != 0) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} "
|
||||
"declares %llu bytes; under split the modules travel inside the "
|
||||
"Reflection archive and the six per-stage runs stay undeclared",
|
||||
static_cast<unsigned>(i),
|
||||
static_cast<unsigned long long>(desc.Spirv[i].Size));
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
const void* archiveBytes = ResolveOrFatal(op, desc.Reflection);
|
||||
@@ -2067,11 +2050,10 @@ namespace MobileGL::MG_Remote::Wire {
|
||||
const Bool boxIsWhole =
|
||||
rec.UnionBox.W != 0 && rec.UnionBox.H != 0 && rec.UnionBox.D != 0;
|
||||
if (!namesABuffer && !boxIsEmpty && !boxIsWhole) {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"ResourceSubData\"} the union box "
|
||||
SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ResourceSubData\"} the union box "
|
||||
"%ux%ux%u has a zero extent on some axes and not others; a box is "
|
||||
"either empty or whole",
|
||||
rec.UnionBox.W, rec.UnionBox.H, rec.UnionBox.D);
|
||||
std::abort();
|
||||
}
|
||||
const Bool carriesContent = namesABuffer ? MGPipeSubDataBufferSize(rec) != 0
|
||||
: (boxIsWhole || rec.RegionCount != 0);
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""The Fatal census gate (CONTRACT-P6 5.2, exit gate S7).
|
||||
|
||||
WHAT IT REFUSES: a `std::abort()` under MG_Remote/ that carries no family word.
|
||||
WHAT IT REFUSES: a `std::abort()` under MG_Remote/ that carries no family word, and a
|
||||
SessionFail / WireLogFatal call whose string forgot its family.
|
||||
|
||||
S7 says "a bare abort() added outside Session::Fail -> the census gate goes red". Session::Fail
|
||||
does not exist yet - it is the 94-site funnel 5.2 describes, and this file is its precondition
|
||||
rather than its replacement. What CAN be checked today is the property that funnel exists to
|
||||
guarantee and that every consumer already depends on: every death names a FAMILY, in a
|
||||
`Fatal{Word...}` marker, on a line a grep can find.
|
||||
S7: "a bare abort() added outside Session::Fail -> the census gate goes red". Session::Fail now
|
||||
EXISTS - it is SessionFail() in FatalFunnel.cpp, and the ~90 scattered aborts route through it,
|
||||
so a bare std::abort() under MG_Remote/ is now the exception this gate refuses rather than the
|
||||
rule. Four aborts remain, all inside the two funnels (WireLog.cpp, FatalFunnel.cpp), and both
|
||||
files are exempt because their callers carry the family word. The property this enforces is the
|
||||
one every downstream consumer depends on: every death names a FAMILY, in a `Fatal{Word...}`
|
||||
marker, on a line a grep can find.
|
||||
|
||||
That is not as weak as it sounds. a6's census found the vocabulary had drifted to 30 family
|
||||
words against a 7-value wire FatalCode, and found two aborts carrying no marker at all - so "the
|
||||
@@ -44,6 +47,11 @@ BASELINE = Path(__file__).with_name("fatal_census_baseline.json")
|
||||
FUNNEL_FILES = {
|
||||
"MobileGL/MG_Remote/Transport/WireLog.cpp",
|
||||
"MobileGL/MG_Remote/Transport/WireLog.h",
|
||||
# P6 dl: Session::Fail's funnel. Its abort is the sanctioned one every SessionFail reaches,
|
||||
# exactly as WireLog.cpp's is for WireLogFatal; its own file also defines the SessionFail
|
||||
# call, so it is exempt from both rules for the same reason WireLog is.
|
||||
"MobileGL/MG_Remote/FatalFunnel.cpp",
|
||||
"MobileGL/MG_Remote/FatalFunnel.h",
|
||||
}
|
||||
|
||||
# Twelve lines: long enough for a wrapped MGLOG_F argument list (the longest in the tree runs to
|
||||
@@ -54,6 +62,10 @@ kMarkerWindow = 12
|
||||
MARKER = re.compile(r"Fatal\{([A-Za-z][A-Za-z0-9]*)")
|
||||
ABORT = re.compile(r"\bstd::abort\(\)")
|
||||
WIRE_LOG_FATAL_CALL = re.compile(r"WireLogFatal\s*\(")
|
||||
# P6 dl: a SessionFail call must carry a Fatal{ word in its string, the same rule
|
||||
# WireLogFatal has - the enum and the string both name the family, and a call whose string
|
||||
# forgot it would let the two disagree. Checked everywhere SessionFail is called.
|
||||
SESSION_FAIL_CALL = re.compile(r"SessionFail\s*\(")
|
||||
|
||||
|
||||
def strip_comments(text):
|
||||
@@ -136,10 +148,10 @@ def census():
|
||||
families.add(word)
|
||||
|
||||
if rel not in FUNNEL_FILES:
|
||||
# Rule 2: every WireLogFatal call carries a family word. The call may wrap, so the
|
||||
# Rule 2: every WireLogFatal / SessionFail call carries a family word in its string.
|
||||
# window covers its continuation lines.
|
||||
for index, line in enumerate(lines):
|
||||
if not WIRE_LOG_FATAL_CALL.search(line):
|
||||
if not (WIRE_LOG_FATAL_CALL.search(line) or SESSION_FAIL_CALL.search(line)):
|
||||
continue
|
||||
window = "\n".join(lines[index:index + kMarkerWindow])
|
||||
if not MARKER.search(window):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"abort_sites": 93,
|
||||
"abort_sites": 4,
|
||||
"families": [
|
||||
"AbiMismatch",
|
||||
"ApplyThreadJoinTimeout",
|
||||
|
||||
Reference in New Issue
Block a user