[MGPipe] the Fatal texts no longer claim P5 does not chunk

R-10's "P5 does not chunk (R-10)" wording predates the stage chunk budget: buffer
content ranges are cut at MGPipeStageChunkBytes() (1e7c372e) and a texture level
at whole-width slabs (9469d48e), so the claim is false for the two rows that can
outgrow SEG_STAGE - and these messages are what a bring-up reads.

- PipeWireEncoder::StageAllocate's two Fatal{RingOverrun, "SEG_STAGE"} sites now
  say what reaching them means: a blob is staged whole, a row that cuts its
  content at the stage chunk budget never arrives, so this is a record type with
  no cut or a piece still larger than the arena.
- EncodeRecord's MaxRecordBytes arm says the bound is about the record's own
  bytes - the stage chunk budget cuts blobs, not a header, payload or inline
  tail - and keeps the integrator decision it names.
- Comments in PipeWireCodec.h, EmitTables.cpp's maxrecop line, Config.h's
  STAGE_MB knob, PipeStats.h's MaxRecordBytes gauge, SplitRuntimePeek.h and
  WireLedgerChecks.h said P5 does no chunking; they now say what is cut and what
  is not.
- PipeWireCodecTest.ARecordLargerThanHalfTheRingIsFatalRingOverrun asserts the
  new diagnostic text instead of "does not chunk (R-10)".
This commit is contained in:
2026-09-20 19:39:51 -04:00
parent e004269d96
commit a63577354d
8 changed files with 41 additions and 31 deletions
+3 -1
View File
@@ -476,7 +476,9 @@ namespace MobileGL::MG_Config {
// codec publish a max-record-bytes counter rather than assume that is enough.
Uint32 RingMb = 8;
// MOBILEGL_IPC_STAGE_MB: SEG_STAGE size. Every blob and every var-tail's bytes live
// here (R-10: no chunking in P5, so nothing may exceed it).
// here ONE BLOB AT A TIME: a row whose content can outgrow the segment cuts it at the
// stage chunk budget (MGPipeStageChunkBytes, a quarter of this), and a record type with
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
Uint32 StageMb = 32;
// MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction.
Uint32 SpinUs = 50;
@@ -87,8 +87,9 @@ namespace MGITest {
// ring and 0 wraps in a process whose ring never filled are the same number and
// completely different facts.
//
// maxRecordBytes / maxRecordBytesCap: R-10 says P5 does no chunking and must prove it
// needs none. The cap is RingProducer::MaxRecordBytes() == MOBILEGL_IPC_RING_MB / 2,
// maxRecordBytes / maxRecordBytesCap: R-10's bound on a record's OWN bytes - the
// content rows cut their blobs at the stage chunk budget, nothing cuts the record. The
// cap is RingProducer::MaxRecordBytes() == MOBILEGL_IPC_RING_MB / 2,
// read from the ring this process actually got rather than recomputed from the
// environment.
//
@@ -9,8 +9,10 @@
// THE TWO ASSERTIONS THE WIRE PRODUCER'S LEDGER MAKES POSSIBLE, in one place so the scenarios
// that carry them cannot drift apart on what the numbers mean.
//
// R-10 (ExpectMaxRecordBytesUnderCap). P5 does no chunking and must instead PROVE it never
// needs any: no record may exceed RingProducer::MaxRecordBytes() == MOBILEGL_IPC_RING_MB / 2.
// R-10 (ExpectMaxRecordBytesUnderCap). No record may exceed
// RingProducer::MaxRecordBytes() == MOBILEGL_IPC_RING_MB / 2: the content rows cut their BLOBS
// at the stage chunk budget, so what is left uncut is the record itself, and it has to be shown
// to stay under the bound.
// Half of that proof is already a Fatal - PipeWireCodec.cpp aborts Fatal{RingOverrun} on a
// record ABOVE the cap - and it is the loud half. The quiet half is the one this assertion
// covers: the phase has to publish the MAXIMUM ACTUALLY SEEN on a real workload, so that a
@@ -91,10 +93,11 @@ namespace MGITest::WireLedger {
<< where << ": R-10 - the largest record this session wrote is " << state.maxRecordBytes
<< " bytes and RingProducer::MaxRecordBytes() is " << state.maxRecordBytesCap
<< " (half of a " << (state.maxRecordBytesCap * 2)
<< " byte SEG_CMD, i.e. MOBILEGL_IPC_RING_MB). P5 does NOT chunk: a record at or "
"above the cap is Fatal{RingOverrun} at the encoder, and a maximum that has "
"climbed to it is the proof obligation failing. Report it to the integrator, who "
"decides between early chunking (P8) and a bigger default ring";
<< " byte SEG_CMD, i.e. MOBILEGL_IPC_RING_MB). The content rows cut their blobs at "
"the stage chunk budget, so nothing cuts this record: one at or above the cap is "
"Fatal{RingOverrun} at the encoder, and a maximum that has climbed to it is the "
"proof obligation failing. Report it to the integrator, who decides between a "
"cut for that row and a bigger default ring";
::testing::Test::RecordProperty("max_record_bytes",
static_cast<int>(state.maxRecordBytes));
::testing::Test::RecordProperty("max_record_bytes_cap",
+2 -2
View File
@@ -1058,7 +1058,7 @@ namespace MobileGL::MG_Remote::Client {
// AND THE ROW, WHENEVER THE MAXIMUM MOVES. The summary line can carry the
// number but not the name - MG_Util is below MG_Remote and has no WireOpName -
// and the name is the actionable half: R-10 makes the integrator choose between
// early chunking and a bigger ring, and that is a decision about a record
// a cut for that row and a bigger ring, and that is a decision about a record
// FAMILY. ClientSession::Stop prints the same pair at teardown, but a trace
// replay never reaches it (measured: the OpenRA lane's library log ends mid-run
// with no teardown line at all), so a stats-enabled run would otherwise publish
@@ -1067,7 +1067,7 @@ namespace MobileGL::MG_Remote::Client {
if (encoder.MaxRecordBytesSeen() > g_publishedMaxRecordBytes) {
g_publishedMaxRecordBytes = encoder.MaxRecordBytesSeen();
MGLOG_I("MGPipe: wire ledger: new maximum record - maxrec=%llu "
"maxrecop=%s cap=%llu (R-10's proof obligation; P5 does not chunk)",
"maxrecop=%s cap=%llu (R-10's proof obligation; blobs are cut, a record is not)",
static_cast<unsigned long long>(g_publishedMaxRecordBytes),
encoder.MaxRecordOpName(),
static_cast<unsigned long long>(encoder.MaxRecordBytesCap()));
+12 -10
View File
@@ -856,8 +856,10 @@ 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 "
"%llu byte staging segment at any occupancy; P5 does not chunk (R-10) - "
"raise MOBILEGL_IPC_STAGE_MB or report the record to the integrator",
"%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();
@@ -914,9 +916,9 @@ namespace MobileGL::MG_Remote::Wire {
ReclaimStagedBytes();
}
MGLOG_F("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); P5 "
"does not chunk (R-10) - raise MOBILEGL_IPC_STAGE_MB or report the record to "
"the integrator",
"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",
static_cast<unsigned long long>(size),
static_cast<unsigned long long>(m_stageCapacity),
static_cast<unsigned long long>(m_stageHead - m_stageTail),
@@ -1016,12 +1018,12 @@ namespace MobileGL::MG_Remote::Wire {
const Uint64 total = layout.TotalBytes;
if (total > m_cmd->MaxRecordBytes()) {
// R-10: P5 does no chunking and must instead PROVE it never needs any. This is
// where the proof fails loudly if it was wrong.
// 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 "
"RingProducer::MaxRecordBytes() == %llu (half of a %llu byte SEG_CMD); P5 "
"does not chunk (R-10) - report it to the integrator, who decides between "
"early chunking and a bigger default ring",
"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()));
+3 -3
View File
@@ -237,9 +237,9 @@ namespace MobileGL::MG_Remote::Wire {
// reply-slot id (R-3), or kInvalidSeq if the ring refused it.
//
// A record larger than RingProducer::MaxRecordBytes() is Fatal{RingOverrun}, NOT a
// wait: R-10 says P5 does no chunking and must instead PROVE it never needs any, so
// this is where the proof fails loudly if it is wrong. MaxRecordBytesSeen() is the
// counter that feeds that proof into MEASUREMENTS.
// wait: R-10's content rows cut their blobs at the stage chunk budget, so this bound is
// about a record's own bytes and this is where one that grew past it fails loudly.
// MaxRecordBytesSeen() is the counter that feeds that bound into MEASUREMENTS.
Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes,
const void* varTail = nullptr, Uint64 varTailBytes = 0);
+6 -5
View File
@@ -1774,7 +1774,7 @@ TEST_F(PipeWireCodecTest, TheDecoderWritesNoRingControlFieldOfItsOwn) {
}
TEST_F(PipeWireCodecTest, MaxRecordBytesSeenStaysFarBelowHalfTheRing) {
// R-10's proof obligation. P5 does no chunking and must instead show it never needed any.
// R-10's proof obligation, over a record's own bytes: the content rows cut their blobs.
//
// THE CAP IS ASKED FOR AT RUNTIME AND NEVER DERIVED FROM MOBILEGL_IPC_RING_MB, and this
// phase is why: the number moved twice in one afternoon. s1 first found that the control
@@ -2329,7 +2329,7 @@ TEST_F(PipeWireCodecTest, ANonZeroSizeWithNoSegmentIsFatal) {
TEST_F(PipeWireCodecTest, ARecordLargerThanHalfTheRingIsFatalRingOverrun) {
// R-10's PROOF OBLIGATION, FAILING ON PURPOSE - the red-once for everything the phase
// publishes as `maxrec=`. P5 does no chunking: a record above
// publishes as `maxrec=`. Nothing cuts a record's own bytes, so a record above
// RingProducer::MaxRecordBytes() == Capacity()/2 must abort by name at the ENCODER, on the
// producing side, rather than becoming a nullptr from Reserve that some caller reads as
// "the ring is full, wait" - which on an EMPTY ring would be a wait that never ends.
@@ -2351,9 +2351,10 @@ TEST_F(PipeWireCodecTest, ARecordLargerThanHalfTheRingIsFatalRingOverrun) {
ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log;
EXPECT_NE(r.Log.find("Fatal{RingOverrun,"), std::string::npos) << r.Log;
EXPECT_NE(r.Log.find("exceeds RingProducer::MaxRecordBytes()"), std::string::npos) << r.Log;
// The diagnostic has to name R-10 and the decision it forces, because the person reading it
// has to choose between early chunking and a bigger ring and neither is a local fix.
EXPECT_NE(r.Log.find("does not chunk (R-10)"), std::string::npos) << r.Log;
// The diagnostic has to name the decision it forces, because the person reading it
// has to choose between a cut for this row and a bigger ring and neither is a local fix.
EXPECT_NE(r.Log.find("budget cuts blobs, not a record's own bytes"), std::string::npos)
<< r.Log;
}
TEST_F(PipeWireCodecTest, ARunThatLeavesItsSegmentIsFatal) {
+3 -2
View File
@@ -224,8 +224,9 @@ namespace MobileGL::MG_Util::PipeStats {
enum class Gauge : Uint32 {
// R-10's PROOF OBLIGATION. The largest single record the wire encoder has written, in
// bytes, and the cap it must stay under - RingProducer::MaxRecordBytes() ==
// MOBILEGL_IPC_RING_MB / 2. P5 does no chunking and has to prove it needs none; before
// this pair existed the only consumers of PipeWireEncoder::MaxRecordBytesSeen() were
// MOBILEGL_IPC_RING_MB / 2. The content rows cut their blobs at the stage chunk
// budget, so what this measures is a record's own bytes. Until this pair existed the
// only consumers of PipeWireEncoder::MaxRecordBytesSeen() were
// codec unit tests, so BRIEF 8 item 3 had no measurement from any real workload
// (joint-v1.md 5, "Maximum record bytes: NO MEASUREMENT").
MaxRecordBytes = 0,