[Docs] (P12): the device-lost reply tolerance and the texture-upload wait granularity get their own record - what landed, the gates that can go red, and what is still owed

- notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md: the two items, their root causes with file:line anchors,
  the five cases that pin them, the red each one produced when the fix was taken back out, and the
  four things this round did NOT do (device end-to-end, local G1, the ResourceCreate /
  SetTextureParams pair, and the sandbox's missing /dev/dri - which reds the two EventForfeitPeer
  lanes for reasons that have nothing to do with the change).
- p12/README.md: a line in the summary and the new file in this directory's table.
- MEASUREMENTS.md: the P12 conclusion and row now carry the number the gate pins - one reply wait
  per pure texture upload becomes zero - with the device wall clock named as the measurement still
  owed rather than left implied.
This commit is contained in:
BZLZHH
2026-09-25 19:44:51 +08:00
parent 33e02f4bcf
commit 3b9f536dd2
3 changed files with 117 additions and 1 deletions
+3 -1
View File
@@ -10,6 +10,8 @@
- **拆成两线程**:起初游戏里只有 7–13 fps;优化后 103–106 fps(P5d);client 发完就走以后,重负载(渲染距离 32)下与单线程持平(P5e)。
- **拆成两进程**:真机上与两线程成本持平;两者的 GL 线程 CPU 都比单线程少约 30%(后端工作挪到了另一线程 / 进程)(P6)。
- **跨机**:电脑经 TCP 连手机,102 个集成用例全过;server 被杀后 133 ms、Wi-Fi 断开后约 5 s 干净报"设备丢失"(P6.5)。
- **纹理上传的等待粒度(P12,handoff §4)**:wire 臂上纹理那半 `resource_subdata` 不再逐条买回包,等待点从「每条记录」落到「SEG_STAGE 窗口用尽」。
单条纯上传占用的回包等待 **1 → 0**(`wait_replies`,正是 §3.2 里"一帧 55,428"的那个计数器);**真机端到端墙钟仍待复测**,见 [`notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md`](notes/p12/DEVICELOST-AND-UPLOAD-WAITS.md)。
- **画面正确**:Vulkan 后端真机画面检查 36/36 通过,CTS 五块相对基线没有超过 0.5 个百分点的退步、没有新崩溃(P7);server 自有窗口上屏两后端 SSIM 1.0(P12)。
## 按阶段
@@ -30,4 +32,4 @@
| 13 | P3b/P4b | 纹理上传形状在四种拓扑下完全一致 | [`notes/p34b`](notes/p34b/README.md) |
| 14 | P6.5 | 跨机 TCP 102/102;断线约 5 s 检测到 | [`notes/p65`](notes/p65/README.md) |
| 15 | P7 | 真机画面 36/36;server 内存无界增长收住(主机 825 → 546 MiB) | [`notes/p7`](notes/p7/README.md) |
| 16 | P12 | 审查后真机复测:Espryt SSIM 1.0;Magma 1.0 / 0.999999511;P12 定向 CTest 46/46;FCL 与跨机 TCP 收官门仍待完成 | [`notes/p12`](notes/p12/README.md) |
| 16 | P12 | 审查后真机复测:Espryt SSIM 1.0;Magma 1.0 / 0.999999511;P12 定向 CTest 46/46;纹理上传回包等待 1→0(主机机制门,真机墙钟待测);FCL 与跨机 TCP 收官门仍待完成 | [`notes/p12`](notes/p12/README.md) |
@@ -0,0 +1,112 @@
# device-lost 的回复容忍与纹理上传的等待粒度(handoff §4 / §5.1 落地记录)
> 2026-09-25。分支 `feat/disaggregated` @ `c984d952` 加本轮工作树改动。对应会话语料
> [`MOBILEGL-CS-HANDOFF.md`](../../../../MOBILEGL-CS-HANDOFF.md) 的 §5.1(A:进世界后的崩溃)与 §4(B:纹理上传等待粒度)。
## 0. 一句话
- **A**:客户端不再把 **DECLINED 回包**当作协议损坏。那正是进世界后 `Fatal{ProtocolCorruption, "Fence.reply"}`
(rc=134)的来源:device-lost 闩锁之后每个 verb 按设计 DECLINED,而 fence/query 两个消费点把它和 ERROR 折进了同一个 abort。
- **B**:wire 臂上纹理那半 `resource_subdata` 不再逐条买回包,等待点从「每条记录」落到「SEG_STAGE 窗口用尽」。
一帧 55,428 次回包等待(handoff §3.2 的实测)在机制上归零;真机端到端墙钟仍待复测(§3)。
## 1. A —— 一个 DECLINED 是答案,不是损坏
### 1.1 根因(三处,都是同一个折叠)
`ReplySink` 的状态空间是三值的,且它自己的头文件就写着这句话:**「DECLINED IS A REAL ANSWER, not a failure」**
(`MG_Remote/Wire/PipeWireCodec.h:457-460`);escape 那几行把另一半也说死了:**「ERROR IS NOT A DECLINE … an escape may not fold 2 into `false`」**
(`Client/WireTables.cpp:446-450`)。下面三处把 1 和 2 折成了一个 abort:
| 位置 | 折叠写法 | 真实后果 |
|---|---|---|
| `Client/EmitTables.cpp` `ReadFenceReply` | `status != kStatusOk \|\| replyBytes != sizeof(result)` → `WireProtocolFatal("Fence.reply", …)` | 进世界后区块上传期的 `glClientWaitSync` 撞上 device-lost 闩锁 → **Minecraft 26.2 崩溃(§5.1)** |
| `Client/QueryEmit.inc` `ReadQueryReply` | 同上 → `WireProtocolFatal("Query.reply", …)` | 同类:QueryAvailable / QueryResult / QueryTimestamp 三条 row 都会被它打死 |
| `MG_Impl/Pipe/PipeFill.cpp:1115` 自检 | 只比客户端记录序号 | device-lost 后 `resource_subdata` 按设计不发 → 自检误判「字节没送达」→ `Fatal{UncarriedInitialBytes}`,把**干净的 device-lost** 变成尸体(P12 出口门 (a) 卡在这条) |
DECLINED 有三条**完全不含损坏**的产生路径,两条在服务端、一条在客户端自己:
- 服务端按设计拒绝:`Wire/PipeWireCodec.cpp:1820-1830`(fence 两条 row)、`:1841-1861`(query 三条 row)——`PostReply(ok ? kStatusOk : kStatusDeclined, ok ? &result : nullptr, ok ? sizeof(result) : 0)`,
即一个**零长度**的合法答案;
- 客户端自己:`Client/ClientSession.cpp:1837`(device-lost 闩锁后每个 verb DECLINED)与 `:2025`(teardown 中死掉的门铃,注释原文就写着「报 DECLINED 而不是 ERROR」)。
而设计文档对 device-lost 的要求恰好相反:**「GL 调用变空操作、交换缓冲返回上下文丢失,而不是崩溃或卡死」**
(`design/08-runtime-and-platform.md:33`、`guide/glossary.md:18`)。前端自己也早有这个空操作:
`MG_Impl/GLImpl/Sync/GL_Sync.cpp:103-106`、`MG_Backend/DirectGLES/DirectGLES.cpp:16489`、`Server/PipeApplier.cpp:175-177` 都对「等不到的 sync」答 `GL_ALREADY_SIGNALED`。
### 1.2 改法
- **fence**:DECLINED → `FenceWait` 答 `GL_ALREADY_SIGNALED`、`FenceStatus` 答 1(signaled)。ERROR 与「OK 但短」**仍然是**原来的具名 Fatal。
- **query**:DECLINED → 该 row 自己的「还没有答案」值(零值即三者皆是:availability=false、`Produced=0`→false、timestamp=0)。ERROR 与短 OK 仍然 Fatal。
- **自检**:只在**会话没闩 device-lost** 时保持全效力(`&& !ClientSession::DeviceLost()`)。活会话上一个「发了却什么都没发出去」的 follow-up 仍然是它命名的那个 R-13.3 缺口。
- **不动的**:`ReadPixels` 的 `Fatal{ReadbackDeclined}` 是**故意的**(阻塞回读没有像素可还),本轮没碰,`ClientSession.cpp:2023-2024` 早就把它点名钉住了。
### 1.3 门与 red-once(R-16,真跑过一次红)
`MG_Test/Wire/RemoteClientControls.inc` 四个用例,全部在**活会话 + 对抗性 peer**上驱动生产消费点:
| 用例 | 断言 | 去掉修复后的红(实测) |
|---|---|---|
| `DeclinedFenceReplyAnswersSignaledInsteadOfAborting` | DECLINED 的 fence 回包 → `GL_ALREADY_SIGNALED` / `GetSyncStatus()==true` | `Fatal{ProtocolCorruption, "Fence.reply"} missing or malformed sync result`(**就是 §5.1 那条崩溃**) |
| `DeclinedQueryRepliesAnswerNoAnswerYetInsteadOfAborting` | 三条 query row → false / 未写出的 out 参数 / 0 | `Fatal{ProtocolCorruption, "Query.reply"}` |
| `ErrorFenceReplyStillRefusesByName` | **宽容不是一刀切**:ERROR 仍然具名 Fatal | (反向控制,修复前后都红/绿相反) |
| `DeviceLostRespecifyDoesNotDieOfTheUncarriedBytesCheck` | 闩锁后 `glBufferData(size,data)` 不再自杀 | `Fatal{UncarriedInitialBytes, "resource_respecify"} … 64 bytes of initial content exist on no side of the wire` |
绿:`RemoteClientControls` 30/30、`RemoteClientTest` 全量 **166/166**。
## 2. B —— 纹理上传不再逐条等回包
### 2.1 机制:一条谓词,两个半边
`MG_Pipe/PipeRoute.h` 的 `MGPipeSubDataWantsItsReply` 是**唯一**一处决定「这条记录要不要等回包」的地方(`MG_Remote/Client/WireTables.cpp:370` 与 `PipeRoute.h:328` 都读它,注释明写「a second spelling of it is how the client comes to wait for an answer the emitter told the server not to bother with」)。
本轮给它加了 wire 臂的一支:**客户端在 wire 臂上不再为纹理那半等回包**。
答案在 wire 臂上**是可推导的**,理由不是「没人读它」,而是三条站着的事实:
1. **发射本来就被答案的内容闸住了**。纹理记录只在 `FamilyIsLive(kMGPipeSubsystemTextureResources, …)` 成立时才产生,
而在 split 下那个闸**就是** `CapsMirrorInstance().ServerConsumes(bit 10)`(`PipeFill.cpp:1719-1740`)——
和服务端自己的验收闸(`PipeApply.cpp:1408-1419` `NoP4aConsumer`)读的是同一个已发布事实。
于是验收当初存在的唯一理由(「服务端没有消费者」,ID-39 / D-D5 / ID-18 M3)**不可能是**这条记录的回答。
2. **剩下的是 bug 类,而且本来就响**:target 不指纹理、没声明 texel、没字节 → 服务端 `MGP_TRIP_WIRE_REPORT`(`PipeApply.cpp:1201-1218`、`:2245-2252`);
handle 不存在 → 计数并具名(`ResolveResourceIn`,`:721-728`),而客户端自己的 tracker 靠「先 create 再 subdata」从源头杜绝(`PipeFill.cpp:1045-1051`)。
3. **客户端还能免费拿到的答案,一条都没丢**:发射器照旧读这条路返回的 Bool,所以**被客户端自己取消**的记录(device-lost、stage 用尽)仍然答「未接受」,
level 保持 dirty 并重试——D-D5 的安全方向原样不动。
**等待没有消失,它换了地方**:一条 run 放不进 SEG_STAGE 时会回收已退休字节并 park 在 stage 门铃上(`Wire/PipeWireCodec.cpp` 的 `StageAllocate` / `ReclaimStagedBytes`),
那是 run-ahead 客户端唯一欠的等待,且它正比于**窗口**而不是调用次数。
### 2.2 门与 red-once
`RemoteClientControls.TextureUploadsStopOwningAReplyOnTheWireArm` 用的是 handoff §3.2 的**同一个计数器**:
`LinkMetrics.cpp:76-80` 的 `wait_replies`(逐帧 `P65LinkMetrics` 行里那个字段,也就是「一帧 55,428」那个数)。本轮为它加了同进程读回的 `LinkMetricsReplyWaits()`,因为「等待没了」如果只能靠计时观察,那就只是实测、不是门。
| 步骤 | 断言 |
|---|---|
| 控制行:`glTexImage2D`(`resource_create` 是 kWaitReply) | `wait_replies` **必须**增加——否则下面的 0 会在「什么都没计数」的会话上假绿 |
| 被测行:`glTexSubImage2D`(纯 `resource_subdata`) | 记录真的过了线(`ClientWireRecordsEmitted()` 增加)**且** `wait_replies` 增加 **0** |
红(实测):把 `kClientWire` 那一支拿掉 → 子进程 exit 142。
**monolith 不变**由既有的 `RemoteRunAhead.OnlyTheTextureHalfOfResourceSubDataWantsItsReply` 继续钉住(臂是 `kNone` 时仍答 true)。
### 2.3 G1
谓词里新增的那一支**在 `#if MOBILEGL_BUILD_DISAGGREGATED` 内**,fall-through 是原来那一行表达式;
其余改动全部落在 split-only 的翻译单元(`MG_Remote/**`)或 `PipeFill.cpp` 的 `#if MOBILEGL_BUILD_DISAGGREGATED` 块内。
因此 pull 臂预处理后与改动前**逐字符相同**;G1 的符号/`.text` 断言仍按惯例由 CI 的 pull 车道给出(本机未复核,见 §3)。
## 3. 本轮**没有**做的(下一轮的直接入口)
1. **真机端到端**:A 需要「进世界不再崩」、B 需要「那一帧墙钟 249 s → 约 20 s」的真机数字。需要重出 APK(`assembleTraceRelease` + 签名)、手机 `90cee93` 起 server、跨机 TCP 起 MC 26.2。
注意 handoff §1.3:**GL 别名软链(`/tmp/mgl` 五个)是必需的**,且本会话的 shell 沙箱给 `/tmp` 挂的是**每次命令全新的 tmpfs**,所以要在同一条命令里建链再跑。
2. **G1 本机复核**:pull 配置完整构建两次(带/不带本轮改动)比 `.text` 与符号集。CI 已断言,本机未跑。
3. **同类第二条**:`ResourceCreate` 与 `SetTextureParams` 也是逐条 kWaitReply(handoff §4.3 末条)。B 只处理了上传那半;这两条要单独分析(创建是按资源一次,参数设置是每次调用——收益与风险不同)。
4. **主机门的环境前提**:本机 shell 沙箱没有 `/dev/dri`,所以任何需要真 EGL pbuffer 的车道(`SpawnLane.EventForfeitPeer`、`TcpLane.EventForfeitPeer`)在本会话里必然红(子进程 `kEglSurface = 12`),与本轮改动无关;`EventForfeitPeerTest.cpp:134` 的注释本来就写明这种 runner 会「every case reds before the ring ever fills」。
## 4. 证据位置
| 位置 | 内容 |
|---|---|
| `MG_Test/Wire/RemoteClientControls.inc` | 本轮五个用例(四个 A + 一个 B),每个都写了它自己的红是什么 |
| `MG_Test/Wire/RemoteClientTest.cpp` | `RemoteRunAhead.OnlyTheTextureHalfOfResourceSubDataWantsItsReply`(monolith 侧的钉) |
| red-once 命令 | `./build-split/MobileGL/MG_Test/Wire/RemoteClientTest --gtest_filter='*Declined*:*DeviceLostRespecify*:*TextureUploadsStopOwning*'`,配合 `build-split/fix-a.patch`、`build-split/fix-b.patch` 两个临时补丁往返 |
| 全量 | `ctest --test-dir build-split -E "Scenario|EventForfeitPeer"`:2616 例,除上述沙箱 EGL 两条外全绿 |
+2
View File
@@ -7,6 +7,7 @@
- 当前阶段。状态摘要在 [`../../CURRENT_STAGE_PROGRESS.md`](../../CURRENT_STAGE_PROGRESS.md);计划与交接 [`PLAN-P12.md`](PLAN-P12.md);任务书 [`BRIEF-onscreen.md`](BRIEF-onscreen.md);裁定 [`INTEGRATOR-DECISIONS-P12.md`](INTEGRATOR-DECISIONS-P12.md)(ID-P12-1..15);只读审计 `map-*.md` 四份。
- 形状:server APK 自建 `ANativeWindow`(`MobileGLDisplayActivity` 的 SurfaceView,进程内 TCP server,会话串行)把渲染流**上屏**;离屏路径保留、同一时刻一条活跃;client 以 `WindowKind::ServerOwned`(控制修订 3)**完全无头**接入,几何由 server 回传;失窗 → `Fatal{ServerWindowLost}` → 干净 device-lost。
- 进展:15 个 `(P12)` 提交 + 审查轮修复(10 个问题,ID-P12-5..11)已并入 `feat/disaggregated`;主机门绿(ID-P12-14)、G1 成立(ID-P12-15);审查后在 `1fb18d9e` 上完成 P12 定向 CTest 46/46 和 Redmi 真机七项复测。当前分支 `0fe01588` 后续提交只改了文档、文档检查范围和基准脚本说明文字,未改 P12 实现。
- **2026-09-25 追加(会话交接 §4/§5.1)**:**device-lost 的 DECLINED 回复容忍**与**纹理上传的等待粒度**已落地,各有自己的 red-once 门(前者就是进世界后 `Fatal{ProtocolCorruption, "Fence.reply"}` 那条崩溃;后者让单条纯上传的回包等待 1 → 0)。真机端到端(进世界不崩、那一帧墙钟)与 G1 本机复核仍待做,见 [`DEVICELOST-AND-UPLOAD-WAITS.md`](DEVICELOST-AND-UPLOAD-WAITS.md)。
- 未完成:出口门 (a) FCL 同机 spawn + 杀 server 的 device-lost、(b) 跨机 TCP 入世界 + P6.5 必测数;`CONTRACT-P12.md`;阶段表行里的其余条目(`unix:` 监听、DirectGLES 去全局、freezer、多 context、FCL 开关接线、D8 白名单)。
## 阶段表行(原 `ROADMAP.md`)
@@ -99,6 +100,7 @@ Android 上的 server 自建窗口(自己的 SurfaceView)把 IPC 渲染流**
| 文件 | 内容 |
|---|---|
| [`DEVICELOST-AND-UPLOAD-WAITS.md`](DEVICELOST-AND-UPLOAD-WAITS.md) | 2026-09-25 落地记录:DECLINED 回复容忍(A)与纹理上传等待粒度(B),含门、red-once 与未做项 |
| [`BRIEF-onscreen.md`](BRIEF-onscreen.md) | P12 subset "on-screen server window" — implementation brief (2026-09-23) |
| [`fix-icd.sh`](fix-icd.sh) | 钉 lavapipe ICD |
| [`g1-base-names.sh`](g1-base-names.sh) | G1 基准符号名生成 |