diff --git a/MobileGL/MG_Pipe/PipeRoute.h b/MobileGL/MG_Pipe/PipeRoute.h index 1eceb59e..42d186e8 100644 --- a/MobileGL/MG_Pipe/PipeRoute.h +++ b/MobileGL/MG_Pipe/PipeRoute.h @@ -195,7 +195,7 @@ namespace MobileGL::MG_Pipe { // reaches the very same `MG_Impl/Pipe` emitters the client does, and a wire emitter there // publishes a record and then waits for the apply thread to apply it. That thread IS the // apply thread, so it waits for itself: `Fatal{BarrierTimeout, "ResourceRespecify"}`, - // logged by `mgl-srv-apply`, thirty seconds after bring-up starts. + // logged by `mgl-srv-apply` one barrier budget after bring-up starts. // // So the client emitters ask "am I the server role right now?" and, if so, run the // monolith adapter - which is exactly what `PipeWireCodec` already does on the decode side diff --git a/MobileGL/MG_Remote/CONTRACT-P5E.md b/MobileGL/MG_Remote/CONTRACT-P5E.md index 406f7f24..cfb8f4a9 100644 --- a/MobileGL/MG_Remote/CONTRACT-P5E.md +++ b/MobileGL/MG_Remote/CONTRACT-P5E.md @@ -164,7 +164,7 @@ keeps its lockstep meaning (nothing but reply rows waits); under run-ahead it re ### 2.4 Present pacing `EmitPresent` (`EmitTables.cpp:977-1053`): `BeforeReadOnlyVerb(); if (m_presentsSent >= PresentCredit) -WaitForPresentAck(m_presentsSent + 1 - PresentCredit, 30 s)` — `ShutDown` returns, `TimedOut` is +WaitForPresentAck(m_presentsSent + 1 - PresentCredit, kBarrierTimeoutMs)` — `ShutDown` returns, `TimedOut` is `Fatal{PresentCreditTimeout}`; then `DrainEventRing`, encode, publish, `PumpControlPlane`. Credit 1 = the client publishes frame N+1 while the server applies and swaps frame N: one frame of overlap, at most one frame of added latency. Budget at ~850 draws + ~0.36 MB pmap/frame: SEG_CMD ~70 KB + diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index b029c854..243d24be 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -129,11 +129,20 @@ namespace MobileGL::MG_Remote::Client { BarrierWaitScope& operator=(const BarrierWaitScope&) = delete; }; - // BOUNDED, AND THE BOUND IS GENEROUS RATHER THAN TIGHT. The barrier is a correctness - // device, not a watchdog: a slow readback on a software rasterizer is a legitimate - // second-scale wait, while a lost record never completes at all. 30 s separates the two - // without turning a loaded CI machine into a red lane, and the Fatal names the seq. - constexpr Uint32 kBarrierTimeoutMs = 30000; + // A DEADLOCK DETECTOR, NOT A PERFORMANCE GATE. The barrier exists so that a record the + // applier never retires is reported instead of hanging: a lost record never completes at + // all, while a slow apply completes late. The bound only has to sit far above the slowest + // legitimate apply. + // + // lavapipe/llvmpipe have legitimate apply-thread stalls that reach the tens of seconds: + // texture-handle registration (vkCreateImageView -> lvp_CreateImageView -> + // llvmpipe_register_texture) blocks the apply thread on a JIT/futex path, measured at + // 29.3-39.7 s in a single stall, ~44.6 s cumulatively over one inproc retrace run. A 30 s + // bound therefore red-lanes those runs intermittently while the same stall on a monolith + // run is simply slow - the monolith has no equivalent cap on this path. 120 s is >3x the + // largest observed stall and keeps the watchdog's job: a record that is truly lost still + // terminates the run, and the Fatal names the seq. + constexpr Uint32 kBarrierTimeoutMs = 120000; Uint64 AppliedWaitBudgetMs(MG_Pipe::MGPWireOp op, const void* payload) { if (op != MG_Pipe::MGPWireOp::FenceWait) return kBarrierTimeoutMs; @@ -711,7 +720,7 @@ namespace MobileGL::MG_Remote::Client { // would go to a server this client has not been told consumes that family; // - after ServerLoop::Start (step 8), because EmitAndWait BLOCKS on appliedSeq and // with no apply thread nothing advances it: the first resource_create would spend - // 30 seconds in the barrier and then Fatal{BarrierTimeout}; + // the whole kBarrierTimeoutMs in the barrier and then Fatal{BarrierTimeout}; // - on THIS thread, the one that called MG_Backend::Init(), because it is the GL // thread and table 3 makes gPipeInputs its to touch while the barrier holds. // The publication is safe without a fence because the apply thread never reads these diff --git a/MobileGL/MG_Remote/Client/WireTables.cpp b/MobileGL/MG_Remote/Client/WireTables.cpp index 452378d9..f94c7f8f 100644 --- a/MobileGL/MG_Remote/Client/WireTables.cpp +++ b/MobileGL/MG_Remote/Client/WireTables.cpp @@ -61,7 +61,7 @@ namespace MobileGL::MG_Remote::Client { // apply thread running the server's own backend - the EGL bring-up, InitCapabilities, // the applier - reaches these very emitters. A record published there would be waited // for by the thread that is supposed to apply it: `Fatal{BarrierTimeout, - // "ResourceRespecify"}` from `mgl-srv-apply`, thirty seconds into bring-up, which is + // "ResourceRespecify"}` from `mgl-srv-apply` one barrier budget into bring-up, which is // exactly how this was found. // // THE ANSWER IS NOT "SUPPRESS THE RECORD" - it is "run the server's own code", because diff --git a/MobileGL/MG_Remote/Transport/Doorbell.h b/MobileGL/MG_Remote/Transport/Doorbell.h index 11992c07..1f31552b 100644 --- a/MobileGL/MG_Remote/Transport/Doorbell.h +++ b/MobileGL/MG_Remote/Transport/Doorbell.h @@ -249,7 +249,7 @@ namespace MobileGL::MG_Remote::Transport { // caller asked for is no longer charged against its timeout, so the // total is bounded by `timeoutMs + the spin budget` rather than by // `timeoutMs`. spinUs is 50 by default and the timeouts that reach - // here are milliseconds (the verb barrier's 30 s, the drain's bound), + // here are milliseconds (the verb barrier's budget, the drain's bound), // so the drift is under a part in a thousand - and erring LONG is the // safe direction for a deadline whose expiry is reported as a timeout. const auto deadline = timeoutMs == kWaitForever diff --git a/MobileGL/MG_Test/Wire/RemoteClientControls.inc b/MobileGL/MG_Test/Wire/RemoteClientControls.inc index ad6eade0..9dd747bb 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientControls.inc +++ b/MobileGL/MG_Test/Wire/RemoteClientControls.inc @@ -218,13 +218,16 @@ TEST(RemoteClientControls, UnknownReadPixelsReplyStatusRefusesTightAndBounce) { } TEST(RemoteClientControls, FenceWaitBudgetHonorsGlTimeoutAndFiniteTransportChunks) { + // 120000 below is ClientSession.cpp's kBarrierTimeoutMs, the transport grace every CPU bound + // wait adds on top of the GL timeout. It was 30 s; lavapipe's texture-handle registration + // (vkCreateImageView -> llvmpipe_register_texture) exceeds that in a single apply stall. struct WaitCase { Uint64 timeoutNs; MGPWireOp op; Uint32 expectedParkMs; }; const WaitCase cases[] = { - {0, MGPWireOp::FenceWait, 30000}, - {1, MGPWireOp::FenceWait, 30001}, - {60000000000ull, MGPWireOp::FenceWait, 90000}, + {0, MGPWireOp::FenceWait, 120000}, + {1, MGPWireOp::FenceWait, 120001}, + {60000000000ull, MGPWireOp::FenceWait, 180000}, {~Uint64(0), MGPWireOp::FenceWait, Transport::kWaitForever - 1}, - {~Uint64(0), MGPWireOp::FenceWaitServer, 30000}, + {~Uint64(0), MGPWireOp::FenceWaitServer, 120000}, }; for (const auto test : cases) { const auto child = RunInChild([test] { @@ -245,7 +248,7 @@ TEST(RemoteClientControls, FenceWaitBudgetHonorsGlTimeoutAndFiniteTransportChunk producer.*PeerMember(ProducerBellTag{}) = bell.original; const auto observed = bell.firstMs.load(); // Doorbell converts the remaining deadline to whole milliseconds. Allow - // scheduler delay, but neither a 30s clamp nor an unbounded sentinel. + // scheduler delay, but neither the barrier clamp nor an unbounded sentinel. if (!bell.entered.load() || observed > test.expectedParkMs || observed < test.expectedParkMs - 100 || observed == Transport::kWaitForever) ::_exit(93); if (test.op == MGPWireOp::FenceWait && diff --git a/docs/Disaggregated/ARCHITECTURE.md b/docs/Disaggregated/ARCHITECTURE.md index 2f8e4ac7..1ab732f1 100644 --- a/docs/Disaggregated/ARCHITECTURE.md +++ b/docs/Disaggregated/ARCHITECTURE.md @@ -539,7 +539,7 @@ P5 的 `inproc` 是真第二线程,但还是 **lockstep**:每条 class-B ver 同一地址空间不得成为旁路(R-2):encoder 把 `MGHostSpan::Ptr` 恒写成 `nullptr`,内容 blob 必须带真实 `SEG_STAGE` offset / 非零 size;decoder 对四种形状分别 `Fatal{ProtocolCorruption}`。`MapPersistent` 在 split 恒 decline;`MOBILEGL_IPC_AUDIT=1` 在 retire 后把 staging 填 `0xDD`,让跨 applier 返回持针的实现下一次读取时可见地失败(R-11:任何 widened read 先过 `RequireStagedCoverageForPendingRanges`,否则 `Fatal{StageSnapshotTooNarrow}`)。apply 角色不得进入 client 的 persistent-map producer(r1)。 -reply mailbox 以**记录序号作为 slot id**(R-3):slot header 是 `{Seq, Status, Size}`,acceptance 的 Bool 答案与 `MapPersistent` decline 都在既有 verb wait 内读取;`Status=ERROR` 一律 `Fatal{ReplyError}`,未知 status 是 `Fatal{ReplyStatusInvalid}`;读到 reply 之前先由 `appliedSeq` 证明该记录已离开 applier(R-5)。P9 才把这套同步 mailbox 推广成异步池。等待预算:普通 verb / 容量等待 30 s(`Fatal{BarrierTimeout}`);`ClientWaitSync` 的 applied/reply 预算 = ceil(timeout ns / 1e6) + 30 000 ms,有限 chunk 避开溢出。 +reply mailbox 以**记录序号作为 slot id**(R-3):slot header 是 `{Seq, Status, Size}`,acceptance 的 Bool 答案与 `MapPersistent` decline 都在既有 verb wait 内读取;`Status=ERROR` 一律 `Fatal{ReplyError}`,未知 status 是 `Fatal{ReplyStatusInvalid}`;读到 reply 之前先由 `appliedSeq` 证明该记录已离开 applier(R-5)。P9 才把这套同步 mailbox 推广成异步池。等待预算:普通 verb / 容量等待 120 s(`Fatal{BarrierTimeout}`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`);`ClientWaitSync` 的 applied/reply 预算 = ceil(timeout ns / 1e6) + 120 000 ms,有限 chunk 避开溢出。这条看门狗只判**死锁**(记录永不退役),不是性能门:lavapipe/llvmpipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,故预算从原 30 s 提到 120 s——monolith 走同一路径本就没有这个上限。 ### 17.2 71 槽的三类、caps 与 tight readback diff --git a/docs/Disaggregated/MEASUREMENTS.md b/docs/Disaggregated/MEASUREMENTS.md index 6cc5f2e1..c693318c 100644 --- a/docs/Disaggregated/MEASUREMENTS.md +++ b/docs/Disaggregated/MEASUREMENTS.md @@ -403,11 +403,13 @@ G1 27,814 符号 0/0/0/0、`.text` 10,806,611 → 10,806,611;G5 两族 + pin | 项 | 问题 | 处置 | |---|---|---| | Major 1 | user-index span 在段内但 Size 可能短于 Count × IndexSize | `a021e3cc`:encoder / decoder / sink 共用 shape/extent gate(单 range、宽度 1/2/4、`Uint64(Count) × IndexSize ≤ Size`),三端短 span 均拒绝、段末 exact-fit 通过;定向 9/9 | -| Major 2 | `ClientWaitSync` 保留 64-bit timeout 但 applied/reply 等待一律 30 s | `82683d4a`:预算 = ceil(timeout ns / 1e6) + 30,000 ms,有限 chunk;普通容量等待 / `FenceWaitServer` 仍 30 s,shutdown 可唤醒 | +| Major 2 | `ClientWaitSync` 保留 64-bit timeout 但 applied/reply 等待一律 30 s | `82683d4a`:预算 = ceil(timeout ns / 1e6) + 30,000 ms,有限 chunk;普通容量等待 / `FenceWaitServer` 仍 30 s,shutdown 可唤醒。**该 30 s 常量后来在 CI 修复轮改成 120 s(`68b55ea3`),原因见本表下注** | | Minor 3 | ReadPixels exact-size reply 的未知 status 穿过 production helper | `82683d4a`:tight 与 bounce 路径拒绝 status=3,`Fatal{ReplyStatusInvalid}` | 定向 `RemoteClientTest` 7/7(0 ns、1 ns、60 s、UINT64_MAX 预算,`FenceWaitServer`、shutdown、未知状态)。合并后 quickgate 里 split 单元车道仅有的 2 个失败(`PipeWireCodecTest.UserIndexSpan*`)是测试日志捕获缺陷(库按进程截断日志,fork 子进程增量读读空),`7cb29d46` 修复后 3/3。本阶段不再做第二轮全门或审查;原始全门源头仍为 `348d22a4`,三项修复只以定向证据补齐。 +> 上表 Major 2 的 30 s 是 `82683d4a` 当时的值,保留为历史记录。**当前值是 120 s**(`68b55ea3`,`ClientSession.cpp` 的 `kBarrierTimeoutMs`),因为 lavapipe 在 texture-handle 注册(`vkCreateImageView` → `llvmpipe_register_texture`)上可合法阻塞 apply 线程 29.3–39.7 s,30 s 的看门狗会把合法慢路径间歇判红;详因见 `ARCHITECTURE.md` §17.1。`RemoteClientControls.FenceWaitBudgetHonorsGlTimeoutAndFiniteTransportChunks` 的期望值(30000/30001/90000)已同步改为 120000/120001/180000。 + ### 7.4 Redmi 出口与四臂 A/B(`82683d4a`,APK `p5bcodex2`) Redmi `2f7cbe2e`,证据根 `MobileGL/.trace-work/p5b-redmi/p5bcodex2/2f7cbe2e/`;全部组合显式 `MOBILEGL_IPC_STAGE_MB=256`。**钉频口径变更**:2026-09-11 的厂商 GPU 上限(1050 MHz)已消失,本次 deterministic pin 为 **1100 MHz**;与 1050 MHz 时代的活动不可比钟频,只有同场四臂配对可比。