[MG_Pipe, MG_Backend, MG_Remote, MG_Test, scripts] (P7): the Magma wire fatals and the two Staged aborts go through Session::Fail

Five deaths in the split server image raised their own std::abort(). Three of them are
funnels - MagmaWireFatal (Renderer/WireFramebuffer.inc), WireDescriptorFatal
(Renderer/UniformManager.cpp) and WireBufferLegacyFatal (Renderer/WireDraw.inc) - and the
fifteen `@P7` refusals on the tree (8 in UniformManager.cpp, 4 in WireFramebuffer.inc, 2 in
WireDraw.inc, 1 in VulkanRenderer.cpp; plan section 1.3's count confirmed) all die through
two of them. Every one of those deaths published NO SessionFault frame, so the peer read a
bare EOF; bumped NO SessionFaultCount(), so exit gate S8 could neither confirm nor falsify
it; and carried a hand-typed family word with no path to FatalFamilies.def's projection.

MG_Backend may not name an MG_Remote symbol on its own account, so the route is a seam in
MG_Pipe with MGP_TRIP_WIRE_REPORT's shape (PipeApply.cpp:66, and the new code sits beside
it): MG_Pipe/PipeSessionFail.h declares MGPipeSessionFail, PipeApply.cpp defines it inside
`#if MOBILEGL_BUILD_DISAGGREGATED`, the DEFAULT behaviour is byte-identical to what the
three funnels did on their own, and MG_Remote::InstallPipeSessionFailHook() points it at
SessionFail. The install is in InitServerRoleCommon (MG_Backend/Init.cpp), step 0, because
that is the one function BOTH server roles run - the inproc role and the spawn/TCP session
child - so there is no per-transport install to get wrong.

MGPipeFatalFamily is a two-value boundary enum, NOT MGFatalFamily cast to an integer: the
.def's enumerators are positional and a row inserted in its middle would silently re-point
every hook call at a different family. FatalFunnel.cpp's adapter is the one place the two
vocabularies meet, as a switch with a named default.

The family word stays UnmigratedVerb rather than becoming a new MagmaWireRefusal. Three
reasons: the message strings are required to stay byte-identical (5.2), and the retrace
refusal censuses recorded since P5b count these deaths by that word - a rename invalidates
every `@P7` measurement taken against them; `Magma:` in the detail already separates the
Magma arm from the GLES UnmigratedVerb deaths, so a new word adds no discrimination a grep
does not have; and its wire projection would be ProtocolCorruption, exactly UnmigratedVerb's.
Recorded for the integrator.

Ph slice (2) in the same commit: Server/StagedShadow.h's RequireCovered and
Server/StagedTextureStore.h's RequireLevelBytes were MGLOG_F + bare abort. Both are inside
MG_Remote already, so they call SessionFail directly, message verbatim;
check_include_closure.py's four probes do not reach either header and the gate is green.

Census: 83 abort sites -> 79 over 20 files (five sites retired, one gained for the seam's
no-hook default, which is argued into FUNNEL_SITES), baseline rewritten in this commit - the
downward ratchet's notice is what asked for it. Two gate bugs the widening exposed and this
commit fixes: the SessionFail call regex also matched `MGPipeSessionFail(` (a lookbehind
now), and a funnel's anchor needed a wider window than a marker's (kFunnelAnchorWindow).

Red-once, three ways. Pointing MagmaWireFatal back at its own abort reds the census naming
WireFramebuffer.inc 0 -> 1. Making the seam skip the hook reds both new death cases -
`TheMagmaWireVerbDeathReachesSessionFailAndBumpsTheFaultCount` on the exit code, because it
reads SessionFaultCount() out of a SIGABRT handler and gets 0 instead of 1, and
`TheSeamHandsTheInstalledHookItsFamilyAndItsLineVerbatim` on the line the hook echoed.
This commit is contained in:
2026-09-22 14:01:06 -04:00
parent f141918aa1
commit e1ec69341a
14 changed files with 333 additions and 21 deletions
@@ -31,6 +31,8 @@
#include <Config.h>
#if MOBILEGL_BUILD_DISAGGREGATED
#include <MG_Pipe/PipeApply.h>
// P7 wave 0: the seam WireDescriptorFatal dies through. See MG_Pipe/PipeSessionFail.h.
#include <MG_Pipe/PipeSessionFail.h>
#endif
#include <vulkan/utility/vk_format_utils.h>
#include <algorithm>
@@ -65,9 +67,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return hash;
}
// P7 wave 0, the descriptor half of WireFramebuffer.inc's MagmaWireFatal - same message,
// same seam, same reason. Four of the fifteen `@P7` refusals die here.
[[noreturn]] static void WireDescriptorFatal(const char* detail) {
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
std::abort();
MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::UnmigratedVerb,
"MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
}
static Bool ResolveWireRange(Uint64 offset, Uint64 declaredSize, VkDeviceSize bufferSize,
@@ -41,6 +41,9 @@
#include "MG_Util/Texture/PixelStoreProcessor.h"
#include <Config.h>
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 0: the seam WireFramebuffer.inc's MagmaWireFatal and WireDraw.inc's
// WireBufferLegacyFatal die through. Declared by MG_Pipe on purpose - see PipeSessionFail.h.
#include <MG_Pipe/PipeSessionFail.h>
// P5c (T5 / tx): the server's staged-texture shadow GenerateMipmap defines its chain on.
#include <MG_Remote/Server/StagedTextureStore.h>
#include <MG_Remote/Server/ServerLoop.h>
@@ -11,9 +11,13 @@
return MagmaProgramSource(handle, records[index]);
}
// Through the same seam as MagmaWireFatal, and for the same reason (P7 wave 0). The family
// stays RoleViolation: reaching the legacy buffer arm inside a Magma server is a role
// invariant the two halves must hold, not a verb the wire failed to migrate.
[[noreturn]] static void WireBufferLegacyFatal() {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
std::abort();
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeFatalFamily::RoleViolation,
"MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
}
static const MG_Pipe::MGPStreamOutputBegin& WireXfbSpan() {
@@ -1,9 +1,16 @@
// P5f fm: included by VulkanRenderer.cpp inside the backend namespace. All inputs here
// belong to the applier; no frontend object, lifetime-id lookup or allocator is used.
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 0. THE MESSAGE IS UNCHANGED; where it goes is not. This funnel is how eleven of
// the fifteen `@P7` refusals die (the other four are WireDescriptorFatal's), and until now
// every one of them raised its own abort: no SessionFault frame for the peer, no
// SessionFaultCount() for exit gate S8, and nothing tying the hand-typed word to
// FatalFamilies.def. MGPipeSessionFail hands the line to the hook MG_Remote installs at
// server-role init, whose arm for UnmigratedVerb is SessionFail(MGFatalFamily::
// UnmigratedVerb, ...) - and with no hook installed it logs and aborts exactly as this did.
[[noreturn]] static void MagmaWireFatal(const char* detail) {
MGLOG_F("MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
std::abort();
MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::UnmigratedVerb,
"MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
}
VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage(
+9
View File
@@ -15,6 +15,7 @@
#include <MG_Pipe/MGPipe.h>
#include <MG_Pipe/PipeApply.h>
#include <MG_Remote/Client/ClientSession.h>
#include <MG_Remote/FatalFunnel.h> // InstallPipeSessionFailHook, installed at step 0 below
#include <MG_Remote/Server/ServerLoop.h>
#include <MG_Remote/Server/ServerSession.h>
#endif
@@ -159,6 +160,14 @@ namespace MobileGL::MG_Backend {
Bool InitServerRoleCommon() {
using namespace MobileGL::MG_Remote;
// 0. the Magma wire funnels' route to Session::Fail (P7 wave 0). FIRST, before
// CreateBackend can reach a wire arm: a hook installed after the first death it
// was meant to catch is a hook that does nothing on the only run that mattered.
// Here rather than in ServerMain because BOTH server roles pass through this
// function - the inproc role and the spawn/TCP session child - and a per-transport
// install is how one of the two ends up without it.
InstallPipeSessionFailHook();
// 1. the SERVER role's private backend object, on the app thread, with no GL and no
// EGL. The context is created and made current later, on mgl-srv-apply, when the
// client's first eglMakeCurrent crosses as a blocking control request.
+48
View File
@@ -33,6 +33,9 @@
#endif
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 0: the seam the Magma wire funnels in MG_Backend die through. Declared in MG_Pipe and
// DEFINED below, so MG_Backend names no MG_Remote symbol of its own to reach Session::Fail.
#include <MG_Pipe/PipeSessionFail.h>
// 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.
@@ -51,6 +54,8 @@
#include <algorithm>
#include <cmath>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <utility>
@@ -108,6 +113,49 @@
X(ProgramPointSize)
namespace MobileGL::MG_Pipe {
#if MOBILEGL_BUILD_DISAGGREGATED
// ----------------------------------------------------------------------------
// THE BACKEND-SIDE SESSION-FAIL SEAM (P7 wave 0). PipeSessionFail.h holds the argument;
// this is the whole implementation.
//
// It lives in this file for the reason MGP_TRIP_WIRE_REPORT does, forty lines up: this is
// where MG_Pipe already states "the verdict of every trip wire, in one place", and a seam
// whose default is an abort belongs next to the other one rather than in a file of its own
// that a reader has to be told about. Split-only, so a pull build gains nothing (G1) and a
// push-monolith build - where the three Magma wire funnels are not compiled either - gains
// nothing to call.
namespace {
MGPipeSessionFailHook g_sessionFailHook = nullptr;
} // namespace
void MGPipeInstallSessionFailHook(MGPipeSessionFailHook hook) { g_sessionFailHook = hook; }
MGPipeSessionFailHook MGPipeSessionFailHookInstalled() { return g_sessionFailHook; }
void MGPipeSessionFail(MGPipeFatalFamily family, const char* fmt, ...) {
// The same 512-byte buffer and the same fallback SessionFail and WireLogFatal use, so a
// death that travels this seam reads identically to one that did not.
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),
"MGPipe: unformattable Fatal diagnostic (format=%s)", fmt);
}
if (g_sessionFailHook != nullptr) g_sessionFailHook(family, line);
// NO HOOK: exactly what the three funnels did before this seam existed - the line, then
// the abort. Reached in a server image whose role init has not run (a unit case that
// drives the applier headless) and in the client process, where there is no session to
// fail. The abort carries no `Fatal{` of its own because `line` already does;
// fatal_census.py's FUNNEL_SITES names this function for that reason.
MGLOG_F("%s", line);
std::abort();
}
#endif
namespace {
// ----------------------------------------------------------------------------
// WHICH CHUNKS EACH DERIVATION READS.
+81
View File
@@ -0,0 +1,81 @@
// MobileGL - MobileGL/MG_Pipe/PipeSessionFail.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
// P7 wave 0 (plan section 1.3 row `@P7`, item (0); Ph slice (2)). HOW A BACKEND DEATH REACHES
// Session::Fail WITHOUT THE BACKEND KNOWING MG_Remote EXISTS.
//
// The three Magma wire funnels - MagmaWireFatal (Renderer/WireFramebuffer.inc),
// WireDescriptorFatal (Renderer/UniformManager.cpp) and WireBufferLegacyFatal
// (Renderer/WireDraw.inc) - each logged their line and raised their OWN std::abort(). Fifteen
// `@P7` refusals and two more sites die through them, and every one of those deaths:
//
// * published NO SessionFault frame, so the peer read a bare EOF and could not name what
// ended the session (CONTRACT-P6 5.2's whole point);
// * bumped NO SessionFaultCount(), so exit gate S8's "zero faults over a good run" could
// neither confirm nor falsify them;
// * carried a HAND-TYPED family word with no path to FatalFamilies.def's projection, so
// nothing checked that `UnmigratedVerb` in the string still meant UnmigratedVerb.
//
// Calling SessionFail() from the .inc files directly would be the obvious fix and is the wrong
// one: MG_Backend's renderer would then name an MG_Remote symbol on its own account rather than
// through the two staging headers it already borrows under `#if MOBILEGL_BUILD_DISAGGREGATED`,
// and the layering that P13's module boundaries have to establish would be one more edge worse.
//
// So the shape is MGP_TRIP_WIRE_REPORT's (PipeApply.cpp:66): MG_Pipe owns the entry point, the
// DEFAULT behaviour is exactly what the site did before (same log line, same abort), and the
// richer behaviour is a function pointer that a layer above installs. MG_Remote installs
// InstallPipeSessionFailHook() at server-role init (MG_Backend/Init.cpp's InitServerRoleCommon,
// which both the inproc server role and the spawn child run), and from then on these deaths go
// through SessionFail like every other one.
//
// THE MESSAGE STRING IS PASSED VERBATIM, family word and all - the same rule FatalFunnel.h
// states. The lines these three funnels write are already counted by name in the retrace refusal
// census and in every recorded `@P7` measurement, so they are byte-identical before and after.
#pragma once
namespace MobileGL::MG_Pipe {
// THE FAMILIES THAT CROSS THIS BOUNDARY, AND ONLY THOSE. Not `MGFatalFamily` cast to an
// integer: the .def's enumerators are positional, and a row inserted in the middle of it
// would silently re-point every hook call at a different family. Two words is what the three
// funnels use today, and a third one costs a row here and an arm in the adapter - which is
// the point, because that arm is where the choice gets argued.
enum class MGPipeFatalFamily : unsigned {
// The Magma wire arms' refusal to honour a verb/shape the split path does not implement.
// Deliberately the SAME word the sites already print: `Magma:` in the detail is what
// separates them from the GLES `UnmigratedVerb` deaths, and renaming the family would
// invalidate every census number recorded against it since P5b.
UnmigratedVerb,
// A call that reached an arm this build did not compile, or ran on the wrong role.
RoleViolation,
};
// What the layer above installs. `line` is the FULLY FORMATTED message, so the hook forwards
// it rather than re-formatting it - a second vsnprintf is a second chance to disagree about
// what the death said. A hook is expected not to return; MGPipeSessionFail aborts anyway if
// one does, because a death that keeps running is the one outcome nothing downstream handles.
using MGPipeSessionFailHook = void (*)(MGPipeFatalFamily family, const char* line);
// Idempotent and last-writer-wins. Installed once per process at server-role init; a unit
// case installs its own and restores nullptr afterwards.
void MGPipeInstallSessionFailHook(MGPipeSessionFailHook hook);
// The installed hook, or nullptr. A test reads it to prove installation happened without
// having to die to find out.
MGPipeSessionFailHook MGPipeSessionFailHookInstalled();
// Formats `fmt`, hands the line to the hook if one is installed, and otherwise does exactly
// what the three funnels used to do on their own: MGLOG_F the line and std::abort().
[[noreturn]]
#if defined(__GNUC__) || defined(__clang__)
__attribute__((format(printf, 2, 3)))
#endif
void MGPipeSessionFail(MGPipeFatalFamily family, const char* fmt, ...);
} // namespace MobileGL::MG_Pipe
+29
View File
@@ -9,6 +9,7 @@
#include <MG_Remote/FatalFunnel.h>
#include "../Includes.h" // MGLOG_F and the umbrella; a .cpp may pull it, a Transport/ header may not
#include <MG_Pipe/PipeSessionFail.h> // the seam MG_Backend's renderer dies through
#include "Client/ClientSession.h"
#include "Protocol/generated/protocol_generated.h"
#include "Server/ServerSession.h"
@@ -33,6 +34,34 @@ namespace MobileGL::MG_Remote {
return g_sessionFaultCount.load(std::memory_order_relaxed);
}
namespace {
// THE PROJECTION FROM MG_Pipe's TWO-WORD BOUNDARY ENUM ONTO THE .def's VOCABULARY, as a
// switch with a named default rather than a cast. MGPipeFatalFamily is deliberately not
// MGFatalFamily's ordinals (PipeSessionFail.h says why), so this arm is the only place
// the two vocabularies meet - and adding a third word there means arguing for a row here.
//
// The line is forwarded through "%s" rather than re-used as a format: it has already been
// vsnprintf'd once, and a detail string that happened to contain a `%` would otherwise be
// read as a conversion the second time round.
[[noreturn]] void PipeSessionFailAdapter(MG_Pipe::MGPipeFatalFamily family,
const char* line) {
MGFatalFamily mapped = MGFatalFamily::ProtocolCorruption;
switch (family) {
case MG_Pipe::MGPipeFatalFamily::UnmigratedVerb:
mapped = MGFatalFamily::UnmigratedVerb;
break;
case MG_Pipe::MGPipeFatalFamily::RoleViolation:
mapped = MGFatalFamily::RoleViolation;
break;
}
SessionFail(mapped, "%s", line);
}
} // namespace
void InstallPipeSessionFailHook() {
MG_Pipe::MGPipeInstallSessionFailHook(&PipeSessionFailAdapter);
}
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.
+12
View File
@@ -47,4 +47,16 @@ namespace MobileGL::MG_Remote {
// this is zero over a whole good run; a test reads it to prove the funnel was reached.
::std::uint64_t SessionFaultCount();
// P7 wave 0. Points MG_Pipe's MGPipeSessionFail seam (MG_Pipe/PipeSessionFail.h) at
// SessionFail, which is what makes the three Magma wire funnels in MG_Backend's renderer
// publish a SessionFault, bump SessionFaultCount() and appear in the census like every other
// death - without MG_Backend naming a single MG_Remote symbol.
//
// Called from MG_Backend/Init.cpp's InitServerRoleCommon, which BOTH the inproc server role
// and the spawn/TCP session child run, so there is one install point rather than one per
// transport. Installing it in the client process would be wrong and is not done: the hook's
// families are the SERVER's renderer refusing a wire verb, and a client that installed it
// would publish a SessionFault for a death the server never had.
void InstallPipeSessionFailHook();
} // namespace MobileGL::MG_Remote
+7 -2
View File
@@ -37,6 +37,10 @@
#pragma once
#include <Includes.h>
// P7 wave 0 / Ph slice (2): RequireCovered's death goes through Session::Fail like every other
// one. Not an out-of-line helper - this header is already inside MG_Remote, so it may name
// MG_Remote's own funnel, and check_include_closure.py's four probes do not reach it.
#include <MG_Remote/FatalFunnel.h>
#include <MG_Util/Debug/Log.h>
#include <MG_Util/Math/VectorTypes.h>
@@ -103,7 +107,9 @@ namespace MobileGL::MG_Remote::Server {
const auto it = m_shadows.find(key);
if (it == m_shadows.end() || it->second.Bytes.data() != hostBase) return;
if (CoverageHas(it->second.Covered, start, end)) return;
MGLOG_F("MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the server's ladder wants "
// Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0).
SessionFail(MGFatalFamily::StageSnapshotTooNarrow,
"MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the server's ladder wants "
"[%zu, %zu) of a buffer whose staged coverage does not include it. Under "
"split the authoritative shadow is SERVER-OWNED (rule C) and "
"resource_subdata is the only way bytes reach it, so bytes outside a staged "
@@ -112,7 +118,6 @@ namespace MobileGL::MG_Remote::Server {
"GPU-written bytes dead (Managers.cpp:1126-1129). This is a missing record, "
"not a missing widening",
site, start, end);
std::abort();
}
// Diagnostics the unit cases read, so that a check can assert WHAT HAPPENED rather than
@@ -101,6 +101,9 @@
// StagedShadowStore::CoverageAdd / CoverageHas: ONE spelling of the covered set, so the texture
// half's runs and the buffer half's ranges cannot drift apart on what "covered" means.
#include <MG_Remote/Server/StagedShadow.h>
// P7 wave 0 / Ph slice (2): RequireLevelBytes' death goes through Session::Fail. Named here
// rather than left to StagedShadow.h's copy of the same include.
#include <MG_Remote/FatalFunnel.h>
#include <MG_Util/Debug/Log.h>
#include <MG_Util/Math/VectorTypes.h>
@@ -331,7 +334,9 @@ namespace MobileGL::MG_Remote::Server {
StagedShadowStore::CoverageHas(shadow->Covered, 0, shadow->Bytes.size())) {
return shadow->Bytes.data();
}
MGLOG_F("MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the "
// Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0).
SessionFail(MGFatalFamily::StageSnapshotTooNarrow,
"MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the "
"bytes of (uploadTarget=%u, level=%u) and the server's staged shadow has no "
"COMPLETE covered run for it. Under split the authoritative shadow is "
"SERVER-OWNED (rule C) and resource_subdata is the only way bytes reach it, "
@@ -341,7 +346,6 @@ namespace MobileGL::MG_Remote::Server {
"byte answer exists at all. Re-reading the client's shadow would be the "
"cross-role access this store exists to end",
site, static_cast<Uint32>(uploadTarget), static_cast<Uint32>(level));
std::abort();
}
// Diagnostics the sync path and the unit cases read, so that a check can assert WHAT
+95
View File
@@ -8,12 +8,17 @@
// P6 `dl` (CONTRACT-P6 5.2). The Fatal-family table and its projection onto the wire FatalCode.
#include <MG_Pipe/PipeSessionFail.h>
#include <MG_Remote/FatalFamily.h>
#include <MG_Remote/FatalFunnel.h>
#include <MG_Remote/Protocol/generated/protocol_generated.h>
#include <flatbuffers/flatbuffers.h>
#include <gtest/gtest.h>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <set>
#include <string>
@@ -29,6 +34,27 @@ namespace {
#undef X
};
#if !defined(_WIN32)
// P7 wave 0. SessionFaultCount() is the only thing the funnel leaves behind that a test can
// read, and SessionFail aborts before returning - so the counter has to be read from INSIDE
// the dying process. A SIGABRT handler turns it into the child's exit code: kFaultExitBase +
// n. "The funnel was reached exactly once" is then a value the parent asserts rather than a
// log line it has to parse, and a seam that bypassed the funnel would exit 0x60, not 0x61.
constexpr int kFaultExitBase = 0x60;
void ReportFaultCountAndExit(int) {
std::_Exit(kFaultExitBase + static_cast<int>(MobileGL::MG_Remote::SessionFaultCount()));
}
// A hook of the TEST's own, to prove what the seam hands across independently of what
// SessionFail then does with it. It echoes and returns; MGPipeSessionFail's contract is that
// it aborts anyway, which is also under test here.
void EchoingHook(MobileGL::MG_Pipe::MGPipeFatalFamily family, const char* line) {
std::fprintf(stderr, "seam-saw family=%u line=%s\n", static_cast<unsigned>(family), line);
std::fflush(stderr);
}
#endif
} // namespace
TEST(FatalFamily, TheTableAndTheEnumAgreeOnCount) {
@@ -84,6 +110,75 @@ TEST(FatalFamily, TheAnchorFamiliesProjectWhereTheirMeaningSays) {
EXPECT_EQ(FatalCodeForFamily(MGFatalFamily::UnmigratedVerb), FatalCode::ProtocolCorruption);
}
// ---------------------------------------------------------------------------------------------
// P7 wave 0: the seam MG_Backend's three Magma wire funnels die through (MG_Pipe/
// PipeSessionFail.h). Until this package they logged and raised their own std::abort(), so
// fifteen `@P7` refusals plus WireBufferLegacyFatal published no SessionFault to the peer, bumped
// no SessionFaultCount() and were invisible to the census gate. These three cases are the
// red-once for that: delete InstallPipeSessionFailHook()'s call in InitServerRoleCommon and the
// first two go red on the exit code, point a funnel back at MGLOG_F + abort and all three do.
TEST(FatalFunnelSeam, InstallingTheHookPointsTheSeamSomewhereOtherThanItsDefault) {
using namespace MobileGL;
ASSERT_EQ(MG_Pipe::MGPipeSessionFailHookInstalled(), nullptr)
<< "the seam was already installed before this case ran; the binary's default is the "
"no-hook one and only a server-role init should change it";
MG_Remote::InstallPipeSessionFailHook();
EXPECT_NE(MG_Pipe::MGPipeSessionFailHookInstalled(), nullptr);
// Restored, so the two cases below start from the same state whichever order they run in.
MG_Pipe::MGPipeInstallSessionFailHook(nullptr);
EXPECT_EQ(MG_Pipe::MGPipeSessionFailHookInstalled(), nullptr);
}
TEST(FatalFunnelSeam, TheMagmaWireVerbDeathReachesSessionFailAndBumpsTheFaultCount) {
#if defined(_WIN32)
GTEST_SKIP() << "the seam's proof forks and reads a signal, which is POSIX only - the same "
"reason MG_Test/Wire's spawn cases are POSIX only";
#else
using namespace MobileGL;
// WireFramebuffer.inc's MagmaWireFatal, character for character - the funnel eleven of the
// fifteen `@P7` refusals die through.
EXPECT_EXIT(
{
std::signal(SIGABRT, &ReportFaultCountAndExit);
MG_Remote::InstallPipeSessionFailHook();
MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::UnmigratedVerb,
"MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}",
"unit-probe");
},
::testing::ExitedWithCode(kFaultExitBase + 1),
"Fatal\\{UnmigratedVerb, \"Magma:unit-probe\"\\}")
<< "the Magma wire funnel did not end through SessionFail: either the line is not the "
"site's own any more, or SessionFaultCount() did not move - which is what exit gate "
"S8 reads and what every telemetry consumer of the funnel depends on";
#endif
}
TEST(FatalFunnelSeam, TheSeamHandsTheInstalledHookItsFamilyAndItsLineVerbatim) {
#if defined(_WIN32)
GTEST_SKIP() << "POSIX only, as above";
#else
using namespace MobileGL;
// WireDraw.inc's WireBufferLegacyFatal, character for character. Through a hook of the test's
// own rather than MG_Remote's, so what is under test is the SEAM - that the family enum and
// the fully formatted line cross intact - and not what SessionFail then does with them. The
// hook returns; MGPipeSessionFail must still abort, which is why this is an exit assertion.
EXPECT_EXIT(
{
MG_Pipe::MGPipeInstallSessionFailHook(&EchoingHook);
MG_Pipe::MGPipeSessionFail(
MG_Pipe::MGPipeFatalFamily::RoleViolation,
"MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)");
},
::testing::KilledBySignal(SIGABRT),
"seam-saw family=1 line=MGPipe: Fatal\\{RoleViolation, \"buffer-legacy-arm\"\\} "
"\\(Magma P7 buffer consumer\\)")
<< "the seam changed what it hands across: either the family ordinal moved (which would "
"re-point FatalFunnel.cpp's adapter arm at the wrong .def row) or the line is no "
"longer the site's own string, which CONTRACT-P6 5.2 requires to stay byte-identical";
#endif
}
TEST(FatalFamily, TheSessionFaultFrameCarriesTheFamilyAndItsCode) {
// `dl` step three (5.2): the Fatal frame a dying session publishes carries the full family
// WORD beside the coarse code, so a peer names which family ended the session instead of
+20 -4
View File
@@ -124,6 +124,10 @@ FUNNEL_SITES = {
("#define MGP_TRIP_WIRE_REPORT",
"the poison/verify trip-wire funnel: its 30-odd callers each pass MGP_TRIP_WIRE_TAG(name), "
"which IS the `Fatal{name}` marker, so the death is named by the caller"),
("void MGPipeSessionFail(",
"the no-hook default of the backend session-fail seam (MG_Pipe/PipeSessionFail.h): the "
"line it logs is the CALLER's, family word and all, and with MG_Remote's hook installed "
"this abort is never reached at all"),
),
"MobileGL/MG_Pipe/generated/PipeWire.inc": (
("MGPipeWireProtocolFatal",
@@ -138,6 +142,13 @@ FUNNEL_SITES = {
# abort it has nothing to do with.
kMarkerWindow = 12
# A FUNNEL'S ANCHOR GETS A WIDER WINDOW THAN A MARKER DOES, and for the opposite reason. A marker
# window has to be tight because any `Fatal{` in it silences the abort below; an anchor window can
# be loose because the anchor is a specific line of code, argued for by name in the table above,
# and the only thing a wider window can reach is the same funnel's own body. Forty covers a funnel
# whose signature and abort are separated by its formatting and its explanation.
kFunnelAnchorWindow = 40
MARKER = re.compile(r"Fatal\{([A-Za-z][A-Za-z0-9]*)")
REFUSAL = re.compile(r"Refuse\{([A-Za-z][A-Za-z0-9]*)")
ABORT = re.compile(r"\bstd::abort\(\)")
@@ -145,7 +156,11 @@ 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*\(")
#
# The lookbehind is not decoration: without it this pattern also matches `MGPipeSessionFail(`,
# and it reported MG_Pipe's own seam - whose callers carry the word exactly as SessionFail's do -
# for being a SessionFail call with no word in its DECLARATION.
SESSION_FAIL_CALL = re.compile(r"(?<![A-Za-z0-9_])SessionFail\s*\(")
FAMILY_ROW = re.compile(r"^\s*X\(([A-Za-z][A-Za-z0-9]*)\s*,", re.MULTILINE)
REFUSE_ENUM = re.compile(r"enum\s+RefuseCode\s*:[^{]*\{(.*?)\}", re.DOTALL)
ENUM_VALUE = re.compile(r"^\s*([A-Za-z][A-Za-z0-9]*)\s*=", re.MULTILINE)
@@ -245,11 +260,11 @@ def declared_refusals():
return set(ENUM_VALUE.findall(block.group(1)))
def sanctioned(rel, window):
def sanctioned(rel, anchor_window):
"""Is this unmarked abort inside a funnel the tables above sanction?"""
if rel in FUNNEL_FILES:
return True
return any(anchor in window for anchor, _reason in FUNNEL_SITES.get(rel, ()))
return any(anchor in anchor_window for anchor, _reason in FUNNEL_SITES.get(rel, ()))
def census():
@@ -287,7 +302,8 @@ def census():
continue
sites.append({"file": rel, "line": index + 1})
window = "\n".join(lines[max(0, index - kMarkerWindow):index + 1])
if sanctioned(rel, window):
anchors = "\n".join(lines[max(0, index - kFunnelAnchorWindow):index + 1])
if sanctioned(rel, anchors):
continue # rule 2, or an argued funnel entry, covers it
if not MARKER.search(window):
unmarked.append({"file": rel, "line": index + 1, "text": line.strip()})
+2 -7
View File
@@ -1,5 +1,5 @@
{
"abort_sites": 83,
"abort_sites": 79,
"abort_sites_by_file": {
"MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp": 15,
"MobileGL/MG_Backend/DirectGLES/Managers.cpp": 12,
@@ -7,22 +7,17 @@
"MobileGL/MG_Backend/DirectGLES/Utils.cpp": 1,
"MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp": 2,
"MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h": 1,
"MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp": 1,
"MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp": 11,
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc": 1,
"MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc": 1,
"MobileGL/MG_Backend/Init.cpp": 2,
"MobileGL/MG_Backend/MGPipe/PipeInputs.cpp": 3,
"MobileGL/MG_Impl/Pipe/PipeFill.cpp": 5,
"MobileGL/MG_Impl/Pipe/ResourceTracker.h": 5,
"MobileGL/MG_Impl/Pipe/SlotAllocator.cpp": 2,
"MobileGL/MG_Pipe/PipeApply.cpp": 3,
"MobileGL/MG_Pipe/PipeApply.cpp": 4,
"MobileGL/MG_Pipe/PipeRoute.cpp": 7,
"MobileGL/MG_Pipe/generated/PipeFilled.inc": 1,
"MobileGL/MG_Pipe/generated/PipeWire.inc": 1,
"MobileGL/MG_Remote/FatalFunnel.cpp": 1,
"MobileGL/MG_Remote/Server/StagedShadow.h": 1,
"MobileGL/MG_Remote/Server/StagedTextureStore.h": 1,
"MobileGL/MG_Remote/Transport/WireLog.cpp": 1,
"MobileGL/MG_State/GLState/BufferState/BufferObject.cpp": 2,
"MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp": 1