diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 27807e22..0329ee05 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -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; diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h index 874e2739..8e206b2f 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h +++ b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h @@ -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. // diff --git a/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h b/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h index b14d11af..4b45560f 100644 --- a/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h +++ b/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h @@ -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(state.maxRecordBytes)); ::testing::Test::RecordProperty("max_record_bytes_cap", diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index e5da241a..76a418c1 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -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(g_publishedMaxRecordBytes), encoder.MaxRecordOpName(), static_cast(encoder.MaxRecordBytesCap())); diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index 8653eac3..3a6a8343 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -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(size), static_cast(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(size), static_cast(m_stageCapacity), static_cast(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(total), static_cast(m_cmd->MaxRecordBytes()), static_cast(m_cmd->Capacity())); diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.h b/MobileGL/MG_Remote/Wire/PipeWireCodec.h index 53aafabf..a824eb27 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.h +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.h @@ -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); diff --git a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp index ecd1399a..e67d9277 100644 --- a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp +++ b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp @@ -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) { diff --git a/MobileGL/MG_Util/Metrics/PipeStats.h b/MobileGL/MG_Util/Metrics/PipeStats.h index 9c333eee..061e3ff5 100644 --- a/MobileGL/MG_Util/Metrics/PipeStats.h +++ b/MobileGL/MG_Util/Metrics/PipeStats.h @@ -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,