mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()})
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user