Shared mutable stacks across fibers would break GC stack maps and the memory model. The runtime must align with language channel-only rules.
Channels and synchronization - Contracts and edge cases
Platform spec article
Channels and synchronization - Contracts and edge cases
Missing owner or submitter in frontmatter for this article.
-
Runtime enforces queue transfer; stack pointers must not cross fibers.
Context
Decision
Rule Detail Mechanism Channels are the only approved cross-fiber data transfer at runtime Happens-before Successful Send happens-before Receive that observes the value Values Copied or heap handles with GC tracing — no stack pointer transfer Builtins channel_*,mutex_*,wait_group_*implement queues and coordinationCompiler Must not lower cross-fiber stack sharing Corelib policy: D-CORE-CONC-0009.
Consequences
New sync primitives require spec + ABI + corelib trifecta before export.
Verification anchors
beskid_runtimechannel tests; git12ee673. -
Per-hub cursor scans registered channels to avoid starvation.
Context
Multiplexing without language
selectneeds deterministic fairness when one registered channel is always ready.Decision
Rule Detail Homogeneity Hub<T>— homogeneousTonly in v1Scan Cursor starts at k; scank, k+1, …modulo registration count for first ready channelAfter success Cursor advances past chosen index (wrap) v1 scope WaitSend out of scope; no language selectEmpty hub WaitReceive with zero registrations → HubError::EmptyMatches D-CORE-CONC-0003.
Consequences
Runtime
hub_wait_receive_*implements rotation; corelib documents registration limits for hot paths.Verification anchors
Hub builtins; corelib hub tests.
-
Closed, full, and cancelled operations surface as Result/Option at the FFI boundary.
Context
Panicking on backpressure or closed channels makes cooperative code fragile and inconsistent with corelib
Resulttypes.Decision
Operation Runtime behavior Send after Close Closedin ResultReceive on closed empty ClosedTrySend / TryReceive Optionfor full/emptyCancel Parked ops wake with Cancelledafter childOnCancelledJoin on cancelled child FiberError::CancelledPanic Forbidden for ordinary channel/hub/mutex errors Aligns with D-CORE-CONC-0005.
Consequences
Builtin implementations and corelib wrappers must share error enums. Duplicate Close is idempotent-safe.
Verification anchors
Contracts and edge cases; concurrency runtime tests.
0 revisions (git unavailable at build; counts may be empty)
No commits recorded for this path.
| Section id | Required | Found |
|---|---|---|
what-this-feature-specifies | yes | yes |
implementation-anchors | yes | yes |
Full tree: run pnpm verify:platform-spec-layout (writes src/generated/platform-spec-layout-report.json).
Runtime must mirror corelib Result semantics at the builtin boundary (no panic for closed/cancelled channel ops).
Duplicate Close is safe. Receive on closed empty queue returns Closed. Send after Close returns Closed.
Hub.WaitReceive with zero registrations returns HubError::Empty. Hub uses round-robin readiness scan (normative).
Multiple fibers may Receive on the same channel; each message goes to one receiver (FIFO).
Cancellation wakes parked fibers with Cancelled after OnCancelled on the child fiber.
Pointer-payload channels (Phase B)
Section titled “Pointer-payload channels (Phase B)”The runtime exposes pointer-payload channel ops at the ABI: channel_send_ptr,
channel_try_send_ptr, channel_receive_ptr, channel_try_receive_ptr. These ops
share the existing ChannelId table and the same FIFO semantics described above; only
the payload moves a GC-managed pointer instead of a raw i64.
Implementations must:
- Register the in-flight pointer as an external GC handle before pushing the handle id onto the internal queue, and drop the registration on both the receiver side and on any error path. The pointer is reachable to the GC for the full duration it sits in the queue.
- Apply the Dijkstra insertion write barrier (
gc_write_barrier) on both the send and the receive sides. The barrier is correct regardless of whether the runtime is in Phase A or Phase B; in Phase A it is effectively a no-op. - Return Closed when no active mutator is registered, rather than leaking the external handle.
- When called from an OS thread that is not a Beskid fiber (a Phase B mutator
registered via
attach_phase_b_mutator), back-pressure on a bounded queue must not call into the fiber scheduler. The runtime instead falls back totry_send/try_receivepolling withstd::thread::yield_now.