Skip to content

refactor(channels): prototype the 0.7 design - #146

Draft
tisonkun wants to merge 1 commit into
mainfrom
codex/redesign-channels
Draft

refactor(channels): prototype the 0.7 design#146
tisonkun wants to merge 1 commit into
mainfrom
codex/redesign-channels

Conversation

@tisonkun

Copy link
Copy Markdown
Collaborator

Status

This is a breaking, correctness-first design draft for the 0.7 channel surface. It is intended to make the complete shape reviewable; it is not proposed as a merge-ready performance replacement.

Summary

  • replace the 0.6 root-level channel implementations with mea::channel
  • implement oneshot plus SPSC, MPSC, SPMC, and MPMC competing-consumer queues
  • implement overflow, backpressure, and unbounded Broadcast retention as distinct public endpoint types
  • add a coalescing Watch channel
  • add single-producer and multi-producer Disruptor-style multicast sequencers
  • introduce the proposed sync, channel, coordination, and atomic 0.7 module groups
  • keep atomicbox available, while making every new channel implementation independent of both atomicbox and standard atomics

The design rationale, ecosystem comparison, invariants, and proposed feature grouping are in docs/channel-design.md.

Public shape

Family Variants Delivery
oneshot one slot one value once
spsc, mpsc, spmc, mpmc rendezvous, bounded, unbounded each value to one competing receiver
broadcast::overflow bounded overwrite every retained value, with exact Lagged reporting
broadcast::backpressure bounded wait/reject every value, gated by the slowest receiver
broadcast::unbounded grow and reclaim every value, reclaimed after all receivers advance
watch one current version latest state, with intermediate versions coalesced
disruptor::{single_producer,multi_producer} power-of-two bounded ring every contiguous published sequence

Producer and consumer cardinalities are part of the queue endpoint's nominal type. A single-side endpoint is non-Clone, non-Sync, and requires mutable access; a multiple-side endpoint is Clone + Sync and uses shared access. Bounded constructors accept NonZeroUsize, while Disruptor accepts a validated power-of-two Capacity.

Broadcast correctness and policy semantics

The Broadcast log, committed tail, receiver cursors, and waiter metadata are serialized by one state mutex. Publication appends the complete immutable value before advancing the tail, so receivers cannot observe a reservation hole and an older producer cannot overwrite a newer publication after wrap-around. This draft therefore includes the correctness fix explored in #145 while replacing that implementation with the full policy design.

Retention is visible in the endpoint type and API:

  • overflow and unbounded send are synchronous because they never wait for capacity
  • backpressure send is async and try_send reports Full
  • only overflow receive errors contain Lagged; lossless policies reuse the common receive errors
  • concurrent overflow tests cover one committed retained suffix and exact lag counts across wrap-around
  • cancelled receive and backpressure-send futures remove their precise Waker registrations

Queue and waiting model

The queue reference core uses a short mutex-protected VecDeque, explicit rendezvous handoffs, endpoint counts, and monotonic waiter identities. try_send / try_recv perform the immediate transition; async send / recv register Wakers and park only the current task. Wakers and replaced or reclaimed user values are released after the internal lock is dropped.

Bounded loss is explicit: force_send(FullBehavior::{DropOldest, DropNewest}) returns SendOutcome::Replaced(value). Ordinary send().await applies backpressure and try_send rejects instead of silently dropping.

Disruptor prototype

The multi-producer sequencer separates claim from publication, records per-slot availability generations, and exposes only the highest contiguous published prefix. Subscriber cursors gate wrap-around. The single-producer publisher is statically non-cloneable; the multi-producer publisher is cloneable and concurrent.

This preserves the sequencing contract without copying Java's CAS implementation. It currently parks tasks with Wakers and intentionally omits busy-spin/yield/phased-backoff loops, preallocated mutable event factories, batch translation, and consumer dependency graphs.

0.7 grouping

The draft makes these groups concrete while retaining the non-channel root modules during review:

mea::sync::{...}
mea::channel::{oneshot, spsc, mpsc, spmc, mpmc, broadcast, watch, disruptor}
mea::coordination::{admission, shutdown, singleflight}
mea::atomic::{AtomicBox, AtomicOptionBox}

The document proposes additive umbrella and leaf Cargo features, but does not add conditional compilation in this PR. That migration can follow after the module and endpoint names settle.

Open review questions

  1. Ordinary queues currently use one endpoint type per topology across rendezvous, bounded, and unbounded capacities, following Crossbeam/Flume. Capacity-typed endpoints, following Tokio's bounded/unbounded split, would remove impossible methods and allow synchronous unbounded send at the cost of more public types.
  2. The Broadcast policies have distinct nominal types and APIs but share log/cursor machinery. They can receive separate storage backends without changing callers if simpler invariants or measurements justify it.
  3. The mutex-based core is the semantic reference, not a throughput conclusion. Candidate specializations include bounded per-slot rings, segmented unbounded lists, single-writer cursors, and multi-writer CAS claims.
  4. Disruptor batch APIs, event preallocation, sequence barriers/dependency graphs, and a separate thread-driven wait-strategy layer need concrete workloads before becoming public API.
  5. Stream / Sink adapters should remain an interoperability layer rather than shape the core state machine.
  6. This draft removes the old root-level channel paths. The exact migration and feature-gating plan remains a 0.7 release decision.

Validation

  • cargo x lint
  • cargo x test --no-capture
  • cargo +1.85.0 test --workspace --no-default-features
  • cargo test -p mea channel::tests -- --nocapture
  • cargo x bench

The current benchmark run is only a smoke test. The rewritten oneshot reference measured roughly 60-75 ns locally, slower than the tuned implementation it replaces; topology- and contention-specific benchmarks are required before treating this backend as a performance candidate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant