Merge branch 'codex/p5f-close' of /mnt/c/Users/geekerwan/AndroidStudioProjects/FoldCraftLauncher/.git/modules/MobileGL into codex/p5f-final

This commit is contained in:
2026-09-20 00:55:29 -04:00
6 changed files with 336 additions and 42 deletions
+19
View File
@@ -1392,6 +1392,10 @@ jobs:
echo "dual-block census run: rc=${rc}"
rows=$(awk '!/^[[:space:]]*(#|$)/ { n++ } END { print n+0 }' "${expected}")
if [ "${rows}" -eq 0 ] && [ "${rc}" -ne 0 ]; then
echo "::error::empty dual-block census requires ctest success, including infrastructure/execution status"
exit "${rc}"
fi
if [ "${rc}" -eq 0 ] && [ "${rows}" -gt 0 ]; then
echo "::error::the dual-block census lane went GREEN with ${rows} expected pair(s) outstanding - the knob stopped arming, or the fields retired and dualblock-expected-fatals.txt was not emptied in the same commit"
exit 1
@@ -1399,6 +1403,21 @@ jobs:
python3 "${helper}" expect-fatal "${RUNNER_TEMP}/dualblock-lane.json" \
"${RUNNER_TEMP}/dualblock.xml" "${expected}"
- name: P5f per-frame zero residual reads must execute on both backends
working-directory: build-split
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
MOBILEGL_IPC_ROLE_SPLIT_STATE: "1"
MOBILEGL_IPC_STRICT_ERRORS: "1"
run: |
ctest -L integration-p5f-rsp --show-only=json-v1 > "${RUNNER_TEMP}/rsp-lane.json"
ctest -L integration-p5f-rsp --no-tests=error --output-on-failure \
--output-junit "${RUNNER_TEMP}/rsp.xml"
python3 "${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" \
require-green "${RUNNER_TEMP}/rsp-lane.json" "${RUNNER_TEMP}/rsp.xml" \
DirectGLES.Split.P5fRsp.F1WireScenario.EachWireFrameHasZeroResidualPulls \
DirectVulkan.Split.P5fRsp.F1WireScenario.EachWireFrameHasZeroResidualPulls
# The mechanism's own negative control (P5F §6): the same named case must be green with
# the knob on and red - on its own assertion text - with it off.
- name: Dual-block negative control (P5f f1, P5F section 6)
@@ -2601,3 +2601,9 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
ENVIRONMENT "${p5f_rsp_environment}")
endforeach()
endif()
if (MOBILEGL_BUILD_DISAGGREGATED)
add_test(NAME SplitLogPaths.ResultAccountingControls
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/Harness/test_split_log_paths.py)
set_tests_properties(SplitLogPaths.ResultAccountingControls PROPERTIES LABELS "unit")
endif()
@@ -16,7 +16,7 @@ def paths(document):
props = {p["name"]: p["value"] for p in test.get("properties", [])}
values = [v.split("=", 1)[1] for v in props.get("ENVIRONMENT", [])
if v.startswith("MOBILEGL_LOG_FILE_PATH=")]
is_split = test["name"].startswith("DirectGLES.Split.")
is_split = test["name"].startswith(("DirectGLES.Split.", "DirectVulkan.Split."))
if is_split and (len(values) != 1 or not values[0]):
raise ValueError(f"{test['name']}: requires exactly one nonempty MOBILEGL_LOG_FILE_PATH")
for value in values:
@@ -113,6 +113,55 @@ def print_marker_table(title, table):
print(f" {len(table[pair]):4d} {pair}")
# These six skips predate P5f closure. Match both the exact entry and its reason;
# losing a GPU/preflight, a required lane marker, or either RSP probe is never allowed.
DUALBLOCK_ALLOWED_SKIPS = {
"DirectGLES.Split.TriangleScenario.TheServerStampedAVerbBoundaryOnThisDrawingFrame": "not the strict-arming lane:",
"DirectGLES.Split.SmallRing.TriangleScenario.TheServerStampedAVerbBoundaryOnThisDrawingFrame": "not the strict-arming lane:",
"DirectGLES.Split.PersistentCoherentMapScenario.TheMapLandsInTheArmItsLaneDeclares": "not the counting lane: MGITEST_PMAP_LANE",
"DirectGLES.Split.SmallRing.PersistentCoherentMapScenario.TheMapLandsInTheArmItsLaneDeclares": "not the counting lane: MGITEST_PMAP_LANE",
"DirectVulkan.Split.Fm.ClipDistanceScenario.ADisabledClipDistanceRemovesNothing": "clips by a DISABLED gl_ClipDistance",
"DirectVulkan.Split.Fm.ClipDistanceScenario.TheEnablesAreIndependentPerDistance": "clips by every declared gl_ClipDistance regardless of the enables",
}
def complete_results(document, junit_path, allowed_skips=None):
expected = [test["name"] for test in document.get("tests", [])]
if not expected or len(expected) != len(set(expected)):
raise ValueError("result accounting: discovery is empty or has duplicate names")
cases = ET.parse(junit_path).getroot().findall(".//testcase")
by_name = {}
for case in cases:
name = case.get("name")
if name in by_name:
raise ValueError(f"result accounting: duplicate JUnit entry {name}")
by_name[name] = case
missing, extra = set(expected) - set(by_name), set(by_name) - set(expected)
if missing or extra:
raise ValueError(f"result accounting: missing {sorted(missing)}, unexpected {sorted(extra)}")
allowed_skips = allowed_skips or {}
for name, case in by_name.items():
if case.find("skipped") is not None:
reason = allowed_skips.get(name)
text = " ".join(" ".join(case.itertext()).split())
if reason is None or reason not in text:
raise ValueError(f"result accounting: unexpected skip/reason for {name}")
elif case.get("status") in ("notrun", "disabled"):
raise ValueError(f"result accounting: {name} did not execute ({case.get('status')})")
return by_name
def require_green(document, junit_path, required):
names = {test["name"] for test in document.get("tests", [])}
if not required or names != set(required):
raise ValueError(f"required green entries differ: expected {sorted(required)}, discovered {sorted(names)}")
by_name = complete_results(document, junit_path)
for name, case in by_name.items():
if case.find("failure") is not None or case.find("error") is not None or case.get("status") == "fail":
raise ValueError(f"required green entry failed: {name}")
print(f"SplitLogPaths require-green: {len(by_name)} PASS, 0 skip, 0 failed, complete discovery")
def expect_fatal(document, junit_path, expected_path):
"""THE DUAL-BLOCK LANE'S CENSUS AND TWO-SIDED RATCHET (P5f f1, P5F §4/§6).
@@ -137,41 +186,30 @@ def expect_fatal(document, junit_path, expected_path):
empty expected file IS the P5f exit state, which this mode then verifies rather than
obstructs."""
logs = marker_log_paths(document)
cases = ET.parse(junit_path).getroot().findall(".//testcase")
by_name = {}
for case in cases:
by_name.setdefault(case.get("name"), []).append(case)
by_name = complete_results(document, junit_path, DUALBLOCK_ALLOWED_SKIPS)
problems = []
fatal, admitted, escalated = {}, {}, {}
scanned = 0
skipped = 0
green = 0
red = 0
for name, entries in sorted(by_name.items()):
if any(c.find("skipped") is not None for c in entries):
scanned = skipped = green = red = 0
for name, case in sorted(by_name.items()):
is_skipped = case.find("skipped") is not None
failed = case.find("failure") is not None or case.find("error") is not None or case.get("status") == "fail"
if is_skipped:
skipped += 1
continue
failed = any(c.find("failure") is not None or c.get("status") == "fail"
for c in entries)
if not failed:
elif failed:
red += 1
else:
green += 1
continue
red += 1
path = logs.get(name)
if path is None:
problems.append(f"{name} is RED and declares no MOBILEGL_LOG_FILE_PATH, so the "
"red cannot be shown to be a named Fatal - an unnamed crash is "
"exactly what this lane exists to forbid")
continue
if failed:
problems.append(f"{name} is RED and declares no private log")
continue # metadata/monolith-arm entries have no marker channel
if not Path(path).is_file():
problems.append(f"{name} is RED and its private log {path} does not exist - the "
"process died before it could write one, which is an unnamed crash")
problems.append(f"{name}: declared private log {path} does not exist")
continue
scanned += 1
text = Path(path).read_text(errors="replace")
entry_fatal = set()
for tag, field, verb, why in MARKER_RE.findall(text):
for tag, field, verb, why in MARKER_RE.findall(Path(path).read_text(errors="replace")):
pair = f"{field}@{verb}"
if tag == "Fatal":
entry_fatal.add(pair)
@@ -180,13 +218,13 @@ def expect_fatal(document, junit_path, expected_path):
escalated.setdefault(pair, set()).add(name)
else:
admitted.setdefault(pair, set()).add(name)
if not entry_fatal:
problems.append(f"{name} is RED but its private log carries no "
"Fatal{UnmigratedPipeInput} marker - an unnamed failure, not a "
"census entry")
if failed and not entry_fatal:
problems.append(f"{name} is RED but its private log carries no Fatal{{UnmigratedPipeInput}} marker")
if entry_fatal and not failed:
problems.append(f"{name} did not fail but its private log carries Fatal markers")
print(f"SplitLogPaths expect-fatal: {len(by_name)} entrie(s) in the run - "
f"{green} green, {skipped} skipped, {red} red ({scanned} with a private log scanned)")
f"{green} green, {skipped} skipped, {red} red ({scanned} private logs scanned)")
print_marker_table("Fatal{UnmigratedPipeInput", fatal)
if admitted or escalated:
problems.append("%d Admitted marker(s) under the dual-block knob - the unconditional "
@@ -294,6 +332,9 @@ def main():
if mode == "expect-fatal":
expect_fatal(json.loads(Path(sys.argv[2]).read_text()), sys.argv[3], sys.argv[4])
return
if mode == "require-green":
require_green(json.loads(Path(sys.argv[2]).read_text()), sys.argv[3], sys.argv[4:])
return
selected = paths(json.loads(Path(sys.argv[2]).read_text()))
selected = {name: path for name, path in selected.items() if re.search(sys.argv[3], name)}
# The SAME exclusion run_control hands ctest -E. Without it this helper and ctest disagree
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""Regression controls for complete, non-vacuous P5f result accounting."""
import contextlib
import importlib.util
import io
import os
from pathlib import Path
import tempfile
import unittest
import xml.etree.ElementTree as ET
module_path = Path(os.environ.get("P5F_TEST_HELPER", Path(__file__).with_name("split_log_paths.py")))
spec = importlib.util.spec_from_file_location("tested_split_logs", module_path)
helper = importlib.util.module_from_spec(spec)
spec.loader.exec_module(helper)
A = "DirectGLES.Split.P5fRsp.F1WireScenario.EachWireFrameHasZeroResidualPulls"
B = "DirectVulkan.Split.P5fRsp.F1WireScenario.EachWireFrameHasZeroResidualPulls"
class ResultAccountingTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.expected = self.root / "expected.txt"
self.expected.write_text("# empty P5f census\n")
self.xml = self.root / "run.xml"
self.document = self.discovery([A, B])
self.results([(A, "run", None), (B, "run", None)])
def tearDown(self):
self.temp.cleanup()
def discovery(self, names):
tests = []
for i, name in enumerate(names):
log = self.root / (str(i) + ".log")
log.write_text("")
tests.append({"name": name, "properties": [{"name": "ENVIRONMENT", "value": [
"MOBILEGL_LOG_FILE_PATH=" + str(log)]}]})
return {"tests": tests}
def results(self, rows):
suite = ET.Element("testsuite")
for name, status, kind in rows:
case = ET.SubElement(suite, "testcase", name=name, status=status)
if kind:
ET.SubElement(case, kind)
ET.ElementTree(suite).write(self.xml)
def census(self):
with contextlib.redirect_stdout(io.StringIO()):
helper.expect_fatal(self.document, self.xml, self.expected)
def test_complete_green_census(self):
self.census()
def test_missing_junit_entry(self):
self.results([(A, "run", None)])
with self.assertRaisesRegex(ValueError, "missing"):
self.census()
def test_empty_discovery(self):
self.document = {"tests": []}
self.results([])
with self.assertRaisesRegex(ValueError, "empty"):
self.census()
def test_extra_and_duplicate_results(self):
for rows in [[(A, "run", None), (B, "run", None), ("extra", "run", None)],
[(A, "run", None), (B, "run", None), (B, "run", None)]]:
self.results(rows)
with self.assertRaises(ValueError):
self.census()
def test_notrun_or_disabled_is_not_pass(self):
for status in ["notrun", "disabled"]:
self.results([(A, "run", None), (B, status, None)])
with self.assertRaisesRegex(ValueError, "did not execute"):
self.census()
def test_rsp_skip_is_forbidden(self):
self.results([(A, "run", None), (B, "notrun", "skipped")])
with self.assertRaisesRegex(ValueError, "unexpected skip"):
self.census()
def test_known_skip_requires_its_exact_reason(self):
name, reason = next(iter(helper.DUALBLOCK_ALLOWED_SKIPS.items()))
self.document = self.discovery([name])
self.results([(name, "notrun", "skipped")])
with self.assertRaisesRegex(ValueError, "unexpected skip"):
self.census()
tree = ET.parse(self.xml)
ET.SubElement(tree.getroot().find("testcase"), "system-out").text = reason
tree.write(self.xml)
self.census()
def test_admitted_or_fatal_on_passed_case_is_not_hidden(self):
for marker in ['Admitted{UnmigratedPipeInput, "GetProgramObject@Clear"}',
'Fatal{UnmigratedPipeInput, "GetProgramObject@Clear"}']:
(self.root / "0.log").write_text(marker)
with self.assertRaises(ValueError):
self.census()
def test_required_pair_must_be_exact_and_both_pass(self):
with contextlib.redirect_stdout(io.StringIO()):
helper.require_green(self.document, self.xml, [A, B])
for row in [(B, "notrun", "skipped"), (B, "fail", "failure")]:
self.results([(A, "run", None), row])
with self.assertRaises(ValueError):
helper.require_green(self.document, self.xml, [A, B])
self.document = self.discovery([A])
self.results([(A, "run", None)])
with self.assertRaisesRegex(ValueError, "entries differ"):
helper.require_green(self.document, self.xml, [A, B])
if __name__ == "__main__":
unittest.main()
+16 -9
View File
@@ -262,8 +262,21 @@ namespace MobileGL::MG_Remote::Server {
std::abort();
}
// Accept owns the single process callback table. Its owner need not be the
// convenience singleton; every reverse event must use the accepted instance's ring.
ServerSession& ReverseCallbackOwner(const char* callback) {
auto* session = ServerSession::Active();
if (session == nullptr) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"%s.session-missing\"} - "
"a reverse callback has no accepted session owner", callback);
std::abort();
}
return *session;
}
void ServerOnBufferWriteback(MG_Pipe::MGPipeHandle res, Uint64 offset,
MG_Pipe::MGPBlobRef bytes) {
ServerSession& session = ReverseCallbackOwner("OnBufferWriteback");
if (bytes.Seg != MG_Pipe::kMGHostSpanSegNone) {
// 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
@@ -275,7 +288,6 @@ namespace MobileGL::MG_Remote::Server {
bytes.Seg);
std::abort();
}
ServerSession& session = ServerSessionInstance();
const Uint64 payloadBytes = sizeof(Transport::EventBufferWritebackHead) + bytes.Size;
void* slot = ReserveEventOrBlock(session, Transport::kEventBufferWriteback,
"kEventBufferWriteback", payloadBytes);
@@ -294,7 +306,7 @@ namespace MobileGL::MG_Remote::Server {
void ServerOnGpuWritten(MG_Pipe::MGPipeHandle res, Uint rangeCount,
const MG_Pipe::MGPRange* ranges) {
ServerSession& session = ServerSessionInstance();
ServerSession& session = ReverseCallbackOwner("OnGpuWritten");
const Uint64 tailBytes = static_cast<Uint64>(rangeCount) * sizeof(Transport::EventRange);
const Uint64 payloadBytes = sizeof(Transport::EventGpuWrittenHead) + tailBytes;
void* slot = ReserveEventOrBlock(session, Transport::kEventGpuWritten,
@@ -313,7 +325,7 @@ namespace MobileGL::MG_Remote::Server {
}
void ServerOnSurfaceChanged(const MG_Pipe::MGPSurfaceInfo* info) {
ServerSession& session = ServerSessionInstance();
ServerSession& session = ReverseCallbackOwner("OnSurfaceChanged");
constexpr Uint64 payloadBytes = sizeof(Transport::EventSurfaceChangedHead);
void* slot = ReserveEventOrBlock(session, Transport::kEventSurfaceChanged,
"kEventSurfaceChanged", payloadBytes);
@@ -333,12 +345,7 @@ namespace MobileGL::MG_Remote::Server {
// P5f fv: OnGlError now has the same owner and lifetime as the other producers.
// PostGlError copies message bytes synchronously into the existing FIFO event ring.
void ServerOnGlError(Uint32 code, const char* message) {
auto* session = ServerSession::Active();
if (session == nullptr) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"OnGlError.session-missing\"}");
std::abort();
}
session->PostGlError(code, message);
ReverseCallbackOwner("OnGlError").PostGlError(code, message);
}
// One installer for both roles' tables, so the check exists in exactly one spelling:
+105 -2
View File
@@ -144,7 +144,7 @@ namespace {
Codec::PipeWireEncoder encoder;
Server::ServerSession* session = nullptr;
bool Handshake() {
bool Handshake(Server::ServerSession* owner = nullptr) {
Transport::InProcessTransport::CreatePair(clientTransport, serverTransport);
{
::flatbuffers::FlatBufferBuilder builder(512);
@@ -160,7 +160,7 @@ namespace {
return false;
}
}
session = &Server::ServerSessionInstance();
session = owner != nullptr ? owner : &Server::ServerSessionInstance();
session->SetSegmentSizes(TestSizes());
// The two halves v1 owns. Neither has a default and CallMask() Fatals on an unset
// one, which is s1's BLOCKER fix and the reason this is stated rather than derived.
@@ -2586,6 +2586,109 @@ TEST(P5fReverseChannel, GlErrorMessagesShareFifoWithOtherReverseEvents) {
events.Drained();
}
TEST(P5fReverseChannel, ANonSingletonSessionOwnsAllFourReverseCallbacks) {
FvGlobals restore;
ASSERT_EQ(Server::ServerSession::Active(), nullptr);
ASSERT_FALSE(Server::ServerSessionInstance().Accepted());
Server::ServerSession owner;
FvSession fixture;
ASSERT_TRUE(fixture.Handshake(&owner));
ASSERT_EQ(Server::ServerSession::Active(), &owner);
ASSERT_NE(fixture.session, &Server::ServerSessionInstance());
ASSERT_TRUE(fixture.StartLoop());
ASSERT_EQ(Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting(
+[](void*) -> MobileGLResult {
MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast<Uint32>(ErrorCode::InvalidOperation),
"non-singleton owner");
const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer};
MG_Pipe::gMGPipeCallbacks.OnGpuWritten({41, 7}, 1, &whole);
const Uint8 bytes[] = {17, 34, 51, 68};
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback({41, 7}, 20,
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(bytes), sizeof(bytes),
MG_Pipe::kMGHostSpanSegNone, 0});
MG_Pipe::MGPSurfaceInfo surface{};
surface.Width = 321;
surface.Height = 123;
surface.InternalFormat = static_cast<Uint32>(TextureInternalFormat::RGBA8);
surface.Samples = 4;
surface.Layers = 2;
surface.IsDefault = 1;
MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged(&surface);
return MOBILEGL_OK;
}, nullptr), MOBILEGL_OK);
Transport::EventRingConsumer events(fixture.clientSegments.EventControl(),
fixture.clientSegments.CmdControl(), fixture.clientSegments.EventRingBase(),
fixture.clientSegments.EventRingCapacity(), fixture.clientSegments.EventSegmentBase());
ASSERT_TRUE(events.Valid());
Transport::RingRecordView event;
ASSERT_TRUE(events.Pop(event));
ASSERT_EQ(event.kind, Transport::kEventGlError);
const auto* error = static_cast<const Transport::EventGlErrorHead*>(event.payload);
EXPECT_EQ(error->Code, static_cast<Uint32>(ErrorCode::InvalidOperation));
EXPECT_STREQ(reinterpret_cast<const char*>(error + 1), "non-singleton owner");
ASSERT_TRUE(events.Pop(event));
ASSERT_EQ(event.kind, Transport::kEventGpuWritten);
const auto* written = static_cast<const Transport::EventGpuWrittenHead*>(event.payload);
EXPECT_EQ(written->Resource.Slot, 41u);
EXPECT_EQ(written->Resource.Gen, 7u);
ASSERT_EQ(written->RangeCount, 1u);
const auto* range = reinterpret_cast<const Transport::EventRange*>(written + 1);
EXPECT_EQ(range->Offset, 0u);
EXPECT_EQ(range->Size, MG_Pipe::kMGPipeWholeBuffer);
ASSERT_TRUE(events.Pop(event));
ASSERT_EQ(event.kind, Transport::kEventBufferWriteback);
const auto* writeback = static_cast<const Transport::EventBufferWritebackHead*>(event.payload);
EXPECT_EQ(writeback->Resource.Slot, 41u);
EXPECT_EQ(writeback->Resource.Gen, 7u);
EXPECT_EQ(writeback->Offset, 20u);
ASSERT_EQ(writeback->Size, 4u);
const Uint8 expected[] = {17, 34, 51, 68};
EXPECT_EQ(std::memcmp(writeback + 1, expected, sizeof(expected)), 0);
ASSERT_TRUE(events.Pop(event));
ASSERT_EQ(event.kind, Transport::kEventSurfaceChanged);
const auto* surface = static_cast<const Transport::EventSurfaceChangedHead*>(event.payload);
EXPECT_EQ(surface->Width, 321u);
EXPECT_EQ(surface->Height, 123u);
EXPECT_EQ(surface->InternalFormat, static_cast<Uint32>(TextureInternalFormat::RGBA8));
EXPECT_EQ(surface->Samples, 4u);
EXPECT_EQ(surface->Layers, 2u);
EXPECT_EQ(surface->IsDefault, 1u);
EXPECT_FALSE(events.Pop(event));
events.Drained();
EXPECT_FALSE(Server::ServerSessionInstance().Accepted());
}
#if !defined(_WIN32)
TEST(P5fReverseChannel, CachedReverseCallbacksRejectAMissingSessionOwner) {
FvGlobals restore;
Server::ServerSession owner;
FvSession fixture;
ASSERT_TRUE(fixture.Handshake(&owner));
const auto callbacks = MG_Pipe::gMGPipeCallbacks;
fixture.Stop();
ASSERT_EQ(Server::ServerSession::Active(), nullptr);
const char* names[] = {"OnGlError", "OnGpuWritten", "OnBufferWriteback", "OnSurfaceChanged"};
for (Uint32 callback = 0; callback < 4; ++callback) {
const auto logSize = ReadLog().size();
EXPECT_EXIT({
switch (callback) {
case 0: callbacks.OnGlError(0, "retired"); break;
case 1: callbacks.OnGpuWritten(MG_Pipe::kMGPipeNullHandle, 0, nullptr); break;
case 2: callbacks.OnBufferWriteback(MG_Pipe::kMGPipeNullHandle, 0, MG_Pipe::MGPBlobRef{}); break;
case 3: callbacks.OnSurfaceChanged(nullptr); break;
}
std::_Exit(9);
}, ::testing::KilledBySignal(SIGABRT), "");
const auto marker = String(names[callback]) + ".session-missing";
EXPECT_NE(ReadLog().substr(logSize).find(marker), String::npos) << marker;
}
}
#endif
int main(int argc, char** argv) {
// Before anything logs: MG_Util::Debug::InitFile() reads the variable once, on the first
// write, and caches the FILE*. The name carries this process's pid, because