The twelve Server* forwarders' one-slot mailbox carried a raw function
pointer plus a void* to a stack-local Args struct (P5c audit row G4);
neither means anything across a process boundary. The slot now carries
one SurfaceControlFrame by value: kind is the op, scalars are the
arguments, and ok/eglMajor/eglMinor come back in the same value. The
blocking handshake, the shadow, the doorbell order and the C2 exit
block are unchanged.
- protocol.fbs: append-only - SurfaceOpKind gains SetSwapInterval=8,
ReleaseResources=9, SetWindowHandle=10; WindowKind gains MetalLayer=6;
SurfaceOp gains readSurface and context (the MakeCurrent four-tuple).
protocol_generated.h regenerated with the pinned flatc.
- Server/SurfaceControlFrame.{h,cpp}: the frame (trivially copyable by
static_assert) and the op enum - ten wire kinds plus four inproc-only
kinds (InitCapabilities is answered by CapsSnapshot on the wire,
SwapBuffers/InitWindowSurface have no production caller - f0-egl F8,
kept for the inproc test lane).
- Protocol/SurfaceOpCodec.{h,cpp}: the frame <-> SurfaceOp/SurfaceReply
codec, the explicit WindowBackend <-> WindowKind table, and the wire
entry ServerApplyWireSurfaceOp whose refusals are named:
Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"} and
Fatal{ProtocolCorruption, "SurfaceOp"}.
- ServerLoop: RunOnApplyThread(fn, void*) -> RunSurfaceControlFrame(frame)
plus the dispatch ApplySurfaceControlFrame; the old per-forwarder Args
bodies moved into the dispatch unchanged (C7/ID-54, N-3, R-12 included).
RunProbeOnApplyThreadForTesting is the test seam through the same
channel (the hook lives in a member, never in the slot).
- Tests: RemoteClientTest/ServerLoopTest migrated to the probe seam; new
SurfaceControlFrameTest suite (mapping tables, wire round-trips, the
two named refusals) plus ServerLoopTest's AVoidForwarderCrossesAsOne-
DispatchedFrame (the frame channel's red-once handle) and
ServerLoopEglTest's TheInitializeDisplayReplyArrivesThroughTheFrame.
9.2 KiB
P6 spawn transport — 包计划
基线
feat/disaggregated @ f23fbc1b。路径在MobileGL/下,行号以该头为准。 契约草稿P6-CONTRACT-DRAFT.md,c6落地时移为MobileGL/MG_Remote/CONTRACT-P6.md。 ⏸ 阻塞于 P5f(2026-09-19):本计划假定“P6 只是传输替换”,而那句话今天不成立——BARRIER_PULLED还有 15 个字段靠 client 填共享的gPipeInputs,Magma 一条未动。先跑P5F-WIRE-COMPLETENESS.md。未开工。 标
待 a6的条目由审计确认,现在不下结论。
1 树上已有的(不要重写)
| 件 | 位置 | 测试 |
|---|---|---|
ITransport(含 ShareFd/ReceiveFd) |
MG_Remote/Transport/ITransport.h |
InProcessTransportTest |
SocketDoorbell |
Transport/Doorbell.h:385-420;Doorbell.cpp:211-360 |
MG_Test/Wire/FdPassingTest.cpp:197/:227/:260/:289 |
SCM_RIGHTS fd 传递(专用 SOCK_DGRAM socketpair) |
Transport/FdPassing.cpp:92 |
FdPassingTest.cpp:267/:291 |
ShmSegment::Adopt(POSIX) |
Transport/ShmSegment.h:57;ShmSegmentPosix.cpp:120 |
FdPassingTest.cpp:114/:187 |
| inproc attach 是 dup+Adopt+Map 的真实第二份映射 | Transport/SessionRings.h:22-28、:147-159 |
现有 split 车道 |
控制面 schema:SurfaceOp/SurfaceReply/WindowKind/CtrlEnvelope |
Protocol/protocol.fbs:166-290 |
ProtocolSmokeTest、flatc-check |
| ABI 指纹握手(同机同二进制,原样继承) | CapsCodec.h:23-29 |
SessionHandshakeTest.cpp:115 |
spawn/unix:/pipe: 具名拒绝 |
ConfigLoader.cpp:310-348 |
— |
MOBILEGL_IPC_SERVER_PATH 已解析未消费 |
Config.h IpcTable::ServerPath |
— |
传输原语已付过账。P6 是装配 + 控制面 + 进程。
2 缺什么
SocketTransport。ClientSession.cpp:468目前硬拒非InProcess。ServerMain。今天的MobileGLServer(CMakeLists.txt:923)是 P0 spike 桩(tools/spikes/server_stub/main.cpp),Android-only、默认 OFF。- 进程机制:fork/execve;envp 剔除 + 子进程强制 monolith(双保险);
dladdr兜底;有界重试握手;EOF 即退;不留孤儿。 - 控制面帧。P5f 包 fc 已把帧化本身落地:forwarder 改发
SurfaceControlFrame值帧(MG_Remote/Server/SurfaceControlFrame.h),schema 缺口(3 枚枚举 +readSurface/context字段 +WindowKind::MetalLayer)已按 append-only 补齐,wire 编解码与具名拒绝在MG_Remote/Protocol/SurfaceOpCodec.cpp(ServerApplyWireSurfaceOp)。P6 的cp只剩传输:client 侧EncodeSurfaceOpFrame上 socket、server 泵进ServerApplyWireSurfaceOp、SurfaceReply回程与 §4 的死亡接线。P5c 审计 G4 行,原记 P6。 g_syncedRenderStateParameters(MG_Backend/DirectGLES/DirectGLES.cpp:4586)与ScopedDefaultUnpackState::s_synced按 context 世代重置(CONTRACT-P5C.md:538-539)。- 死亡语义,见 §4。
3 边界:P6 不做真窗口
WindowHandle::Handle(MG_Backend/BackendObject.h:561-566)在 Android 上是 ANativeWindow*,子进程用不了。protocol.fbs:186-196 的 nativeToken 注释已写明 Android 的窗口 out of band 传递——那是 P12。
- P6 落 pbuffer / surfaceless / 离屏:够
HeadlessGL、等价 split 车道、trace replay,也正是 P6 出口门量的东西。 - 真窗口的管道(令牌、
SurfaceOp、SurfaceReply)落,但到达 server 时具名拒绝。 - P6 之后手机上能跑的是 trace 和离屏,不是游戏。
4 P5e 留下的:不等待的客户端 + 会死的服务端
闩锁语义已定(docs/Disaggregated/ARCHITECTURE.md:504):EOF → device-lost latch、EGL_CONTEXT_LOST、GL_UNKNOWN_CONTEXT_RESET;MOBILEGL_IPC_RESPAWN 默认关;IDLE_EXIT_S 默认 30。照抄。
新的是 run-ahead 之后的三个状态:
| 情形 | 要求 |
|---|---|
| server 被 kill,客户端已跑在前面 | 走上述闩锁;已发布未 apply 的记录明确丢弃,不等待 |
| server 被 SIGSTOP / Android freezer 冻住(不产生挂断事件) | 有界等待 + 具名诊断 |
| server 活着但落后一帧以上 | 这是 P5e 的正常态,必须与上两者可区分 |
死亡判定取 SocketDoorbell 在 EOF 上 latch 的 m_dead(Doorbell.cpp 的 Drain),不用超时阈值。这是 P6 唯一的新设计,其余是搬运。它与 E1 对照的重新定义(ID-122)共用同一个谓词,建议一起做。
5 包与依赖序
a6 (只读审计,不写代码)
└─ c6 (契约 + 惰性线上行,行为零变化)
├─ so (SocketTransport)
├─ sm (ServerMain + fork/exec + 进程纪律)
├─ cp (控制面:12 个 forwarder → 帧)
├─ st (静态量按 context 世代重置 + G5)
└─ t6 (spawn 车道 + 阴性对照 + CI)
└─ 集成 commit:ConfigLoader 不再拒绝 spawn
a6(先跑)。"P6 只是传输替换"的证据是 P5c 时代的;P5e 之后树变了(by-handle resolver、server 角色内存、不等待的路径)。产出:逐行清单,形状照 ~/w7/notes/p5c/p5c-audit-v1.md(现 notes/p5c/)。点名查:
- 仍过线的裸指针 / 进程局部句柄。
- 进程级静态中语义属于 context 或 session 的——§2.5 是已知的两个,问题是还有几个。这类缺陷
inproc结构性看不见。 gPipeInputs在 spawn 下的归属(P5e 的 ID-135 结论要重验)。- 十二个 forwarder 对
SurfaceOpKind七个枚举,缺几个。预期缺SetSwapInterval/ReleaseResources/SetWindowHandle(SwapEGLBuffers走记录、InitCapabilities走CapsSnapshot、InitWindowSurface是客户端 no-op)——待 a6 确认。 - server 能否不链
MG_Impl(MG_Backend/MGPipe/PipeInputs.h如此声称)。做不到就记账。
c6:契约;protocol.fbs 追加枚举(只追加);令牌类型;新旋钮(ARCHITECTURE.md:639 已登记的 P6+ 项,POLL_ESCALATE 归 P10);跨包接缝的声明 + 未落地断言体;MobileGLServer 真目标。
so:SocketTransport。必须在第二个进程出现之前就绿——一次 socketpair() + 两个线程跑 InProcessTransportTest 全套,含 BUFFER_TOO_SMALL 留帧语义。理由同 SessionRings.h:22-28。
sm / cp / st / t6:见 §2 与 §3。cp 在 fc 之后只剩传输;ForgetCurrentTuple 的 N-3 语义已在帧路径的分发里原样成立(ApplySurfaceControlFrame,ServerLoopTest 的 C7/N-3 对照驱动)。
6 出口门
五部分门(ARCHITECTURE.md §13.2)之外:
- G1 pull 构建 0/0/0/0,
.text不变。 - G2/G14:
integration-spawn用例名集合与integration-split逐名相同。 integration-spawn全绿,数字与integration-split一致(今天 179/179)。- 进程树门(机检):每条用例前后数子进程,运行中恰好多一个,运行后为零;
HeadlessGLfork 预检无孤儿。 - 臂证明门:每条用例在自己的私有日志里留下子进程 pid 与
transport=spawn。ID-124 已证明假绿会发生,而一条实际跑了 monolith 的 spawn 车道能通过 1–4。 - 逐包 red-once(R-16),各一条具名对。
- 设备:Redmi
2f7cbe2e,reboot-clean + 同热窗口配对 A/B;OpenRA 在 Adreno 830 上 split SSIM ≥ 0.99。 - 性能只记录不阻塞,但必须采到一个数:socket 门铃相对 inproc condvar 的代价。P5d 把 client 线程从 15.0 压到 9.2 ms/帧,其中有门铃路径的钱。P6 唯一预期的回归。
7 阴性对照
| # | 旋钮 | 证伪什么 |
|---|---|---|
| S1 | MOBILEGL_IPC_SERVER_PATH=/nonexistent |
具名拒绝,非静默回落 monolith |
| S2 | 运行中 kill -9 server |
客户端能区分慢与死;不挂起、不继续提交 |
| S3 | 子进程 envp 未剔除 | 第二道保险挡住递归 spawn 并具名 |
| S4 | 关掉 st 的世代重置 |
第二个 context 拿到上一个的同步状态 |
| S5 | WindowKind::AndroidNativeWindow 到达 server |
具名 Fatal |
S2 的形状先想清楚再写:E1 索要的 Fatal 在整次运行里出现 0 次(ID-122)。
8 风险与未决
- 慢 / 死的区分(§4)。P6 唯一新设计,与 E1 重定义合并考虑。
- Android cached-app freezer 冻结不产生挂断事件。完整处理归 P12,诊断归 P6。
- server 能否不链
MG_Impl— 待 a6;做不到即记账,不留空头承诺。 - Windows:
Adopt是 POSIX-only,unix:/pipe:继续具名拒绝,P6 只落 POSIX。 SEG_REPLY2 MiB 单槽 cap 与 chunking(Transport/ReplySlot.h记作 P6 debt,ID-47)→ 推给 P9。- class-C 的 511 条
UnmigratedVerb(notes/p6/census-classC.md)不是 P6 的账。 - E1 对照(ID-122)不因 P6 开工而解决。
9 不属于 P6
真窗口与 android:process=":mgl" Service(P12);多 context(P12,CONTRACT-P5C.md:56 原写 P6,本计划改判);chunked readback 与 reply-slot 池(P9);DynamicBackendParameters 定宽重写(P7);POLL_ESCALATE(P10);对象类 BARRIER-PULLED 行与 frontend-keyed twin registry(P3b/P4b、P7)。
10 开工顺序
先跑 a6,只读。清单出来之前 c6 写不实,后面五包的大小是猜的。