Files
MobileGL/docs/Disaggregated/notes/p5f/fc-contract-draft.md
T
swung0x48 d5ed139cc5 [MG_Remote, MG_Test] (Disaggregated): P5f fc - the EGL control plane crosses as value frames, not function pointers
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.
2026-09-19 20:30:21 -04:00

96 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# fc — 控制面帧契约草稿(P5f 包 fc;CONTRACT-P5F.md 本体留给集成收口)
> Status: DRAFT, landed in code by package `fc` on branch `p5f/fc`. Authority once integrated: this
> feeds CONTRACT-P5F.md. Until then it supplements CONTRACT-P5C (audit row G4, discharged here) and
> CONTRACT-P5E §2.5 (the wait-before-forwarder discipline, unchanged), and it answers the
> control-plane rows of `docs/Disaggregated/P6-CONTRACT-DRAFT.md` §4 ahead of schedule.
> Code anchors: `MobileGL/MG_Remote/Server/SurfaceControlFrame.h` (the frame),
> `MobileGL/MG_Remote/Protocol/SurfaceOpCodec.{h,cpp}` (the wire codec and the named refusals),
> `MobileGL/MG_Remote/Server/ServerLoop.cpp` (`RunSurfaceControlFrame` /
> `ApplySurfaceControlFrame`).
---
## 1. Ownership
1. A **SurfaceControlFrame is a value**. The poster fills the request fields; ownership of the
slot's content passes to the channel at publish; the dispatch on the apply thread fills the
reply half (`ok`, `eglMajor`, `eglMinor`) into the same value; the poster's copy-out happens
after the blocking handshake returns. No field is a pointer and none may become one — the
`static_assert(std::is_trivially_copyable_v<...>)` in `SurfaceControlFrame.h` is the
enforcement, not a courtesy.
2. `seq` is minted by the channel's poster side (`RunSurfaceControlFrame`), monotonically from 1
per session, and echoed in the reply. Under spawn the client mints (P6-CONTRACT-DRAFT table 0);
the field does not change shape.
3. `display` / `surface` / `readSurface` / `context` are Uint64 **tokens**. Inproc they are the
client's EGL handle values bit-cast — legal only because both roles share a process and a
driver, and the N-3 tuple bookkeeping compares them by value. Under spawn they are
client-minted dense tokens (P6-CONTRACT-DRAFT table 0, unchanged). No code may dereference a
token as a pointer on a path a non-inproc transport can reach.
4. `windowBackend` + `nativeToken` + `width`/`height` carry a `WindowHandle` as values. The frame
names the **backend** enum, never the wire's `WindowKind`; the two enums meet only inside
`SurfaceOpCodec.cpp`'s explicit tables (`Surfaceless`/`Pbuffer` are surface shapes and answer
"no backend" there). A `nativeToken` whose backend is Android is an `ANativeWindow*` — an
address in the client's process — and may never cross (§3.4).
## 2. Ordering
1. The channel keeps its P5 semantics: one blocking slot, `m_callerMutex` serialising posters,
pumped by the apply thread **between drain batches**, publish-then-ring. Frames are FIFO
against each other; they are NOT ordered against SEG_CMD records by the channel itself.
2. The client-side discipline that orders frames against records is unchanged and remains
mandatory under spawn: `WaitForApplyBeforeEglForwarder` waits `appliedSeq` to
`LastPublishedSeq` before every forwarder (CONTRACT-P5E §2.5), because the channel is pumped
between drain batches and a control frame would otherwise overtake in-flight records.
3. **All twelve ops stay blocking in fc**, including the three f0-egl §4.3 marked relaxable
(`SetSwapInterval`, `ReleaseSurface`, `SetWindowHandle`). Relaxing them to fire-and-forget is
a P6 decision and must preserve: a `SetWindowHandle` is in effect before any later
`CreateWindowSurface` (one ordered channel suffices), and `ReleaseResources` NEVER relaxes —
`MobileGL::Destroy()` walks on the moment it answers (ServerLoop.h's header note).
4. Re-entrancy is unchanged: a post from the apply thread dispatches inline (the
`~BackendObject_DirectGLES` → `ReleaseEGLResources` path). This is thread semantics, not wire
semantics; under spawn the destructor path runs server-side natively and never posts.
## 3. Error semantics
1. Loop down, backend alive → `Fatal{ApplyThreadNotRunning}` (M-7, unchanged).
2. Loop down, no backend → `MOBILEGL_ERR_NOT_INITIALIZED`, nothing runs on the caller (C2,
unchanged).
3. Backend null at dispatch → `MOBILEGL_ERR_NOT_INITIALIZED` with `ok=false`; the forwarder's
answer is the old `false`/void-drop, unchanged.
4. Wire entry (`ServerApplyWireSurfaceOp`, the future P6 control pump's entry point; P5f drives
it from tests only): a decode failure is **`Fatal{ProtocolCorruption, "SurfaceOp"}`**, never a
dropped frame (a dropped control frame is a client waiting on a reply that never comes); a
decoded `WindowKind::AndroidNativeWindow` is
**`Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"}`** — the window kind is legal schema,
the token is the part that means nothing in the server's process, and real window arrival is
P12. Inproc-only kinds (`InitCapabilitiesInprocOnly`, `SwapBuffersInprocOnly`,
`InitWindowSurfaceInprocOnly`, `ProbeForTesting`) cannot arrive: the encoder refuses them
(`SurfaceWireError::InprocOnlyOpOnTheWire`).
5. A frame that reaches the inproc dispatch with `kind == None` or a kind the switch does not know
is `Fatal{ProtocolCorruption, "SurfaceOp.kind"}`; a `windowBackend` tag outside the enum is
`Fatal{ProtocolCorruption, "SurfaceOp.windowBackend"}`. Inproc these are corruption shapes, not
bad input — same discipline as the ring's header check.
## 4. Reply carriers — and the deliberate non-use of `SurfaceReply.defaultFb`
1. The default framebuffer's shape already crosses as `kEventSurfaceChanged` on SEG_EVENT (P5c
ev), and the client drains it at the RPC return (`BackendObject_Remote.cpp`'s create/resize
arms). fc rules that `SurfaceReply.defaultFb` **stays unset**: two spellings of one fact is how
the two sides come to disagree about it (the `CapsSnapshot` deprecated-fields lesson,
`protocol.fbs`). Enabling it later is a schema amendment, not a silent flip.
2. The caps republish side effects are unchanged and are server-spontaneous frames on the wire:
a real bind under `MakeCurrent` republishes (R-12 arm (a)); `InitCapabilities` publishes. The
client's absorption point — pump the control plane after the RPC returns — is the same in both
worlds: "after this op's reply, before the next op" is a total order the client can rely on.
## 5. What fc deliberately did NOT do
- No transport: no socket, no spawn pump, no client-minted token table (the frame fields are
token-width already, so P6 is transport-only here).
- The two dead forwarders (`ServerSwapEGLBuffers`, `ServerInitWindowSurface`) and
`ServerInitCapabilities` were NOT given wire ops (f0-egl F8/§4.2): they ride the inproc channel
as inproc-only kinds because the function-pointer mailbox is gone entirely, and G14 forbids
deleting their pinning tests.
- Blocking semantics, the shadow, the doorbell order and the C2 exit block: unchanged.