feat(review 006 Unit 3): teardown-race log, early-arrival bound docs, count accessor (E-03, E-04, N-2)

- E-03: the open wrapper's handler-exit teardown no longer discards
  UnknownChannel silently — debug log + benign-race pinning comment
  (ledger take is the atomic gate; no double-decrement)
- E-04: consumer-facing doc note on the 64-parked-chunks observable
  bound (channel-client.md + EARLY_ARRIVAL_CAP const doc) for
  tunnel-style push-first producers
- N-2: ChannelManager::early_arrival_count() accessor (the
  observability choice over removing the write-only counter),
  documented monotonic, with a park/adopt-drain monotonicity test
- review 006: Unit 3 marked implemented in Status and remediation plan

Verification: 617 tests pass; clippy -D warnings clean (host +
wasm32 check); fmt clean; doc clean
This commit is contained in:
glm-5.3-flash committed 2026-09-06 19:35:18 +00:00
1 parent f8dad9dbc8
commit 36e74cda11
4 files changed
+126 -8

No files matched your search

+29
View File
@@ -108,6 +108,35 @@ and `subscribe_resources` method are deferred (OQ-40, OQ-41). The
`ResourceEntry.access` preview was dropped by ADR-047 §6 — it is
available via `services/schema` on the op spec.
## The early-arrival park bound (push-first producers)
The open-op response carrying `channel_id` races the producer's first
data-plane writes: the producer's handler can start pumping before the
consumer's `adopt_channel` runs. The demux parks those first chunks
per-channel (FIFO) instead of dropping them, and `adopt_channel` drains
the parked chunks into the new receiver — a push-first producer (a TTY
backend's banner, a sub protocol's greeting) must not lose its first
chunks.
The park is bounded: **up to 64 chunks per channel** are parked
(`EARLY_ARRIVAL_CAP`, ADR-040's memory bounds); chunks arriving past
the cap are dropped silently — at the consumer this presents as a
truncated stream with clean framing everywhere else, not as an error.
Consumer-facing consequences (review 006 E-04, noted for tunnel-style
consumers):
- Size any pre-adopt buffering math (e.g. a UDP POC's MTU-vs-buffer
sizing) against **64 parked chunks as the observable bound**, and
adopt promptly — the open reply resolves *before* the first data
arrives by design, so the adopt is the consumer's next step, not a
slow path.
- The producer side observes both halves of the race via
`ChannelManager::early_arrival_count()` (chunks parked) and
`ChannelManager::dropped_unknown_chunks()` (chunks lost to the cap) —
a non-zero dropped counter under a push-first producer means the
adopter was too slow for the producer's burst, and the affected
streams were truncated at the park boundary.
## Transport-agnostic by construction
`ChannelClient` is the client side of the channels protocol. The channels