fanring
Fast typed MPSC and MPMC channels built from one SPSC yring per producer.
Sender registration is dynamic. Each sender writes to its own ring, so producers do not contend on a shared queue tail. MPSC drains those rings directly; MPMC stages prefetched batches in stealable receiver-local queues.
Requires Rust 1.93 or newer.
Performance
Median throughput on the system named in each chart, measured with the
performance governor and CPU turbo disabled. Summary bars compare common
u64 topologies, while detailed heatmaps cover thread counts and payload sizes
with lighter cells for higher throughput and white outlines for winners.
Detailed MPSC
Detailed MPMC
MPSC
use channel;
let = channel;
let mut tx1 = tx0.try_clone.expect;
tx0.send.unwrap;
tx1.send.unwrap;
for _ in 0..2
assert_eq!;
drop;
assert_eq!;
mpsc keeps receive-side batching entirely inside recv/try_recv. It
preserves FIFO within each sender lane and relaxes order across senders.
MPMC
use channel;
let = channel;
let mut rx1 = rx0.clone;
tx.send.unwrap;
tx.send.unwrap;
let a = rx0.recv.unwrap;
let b = rx1.recv.unwrap;
assert_ne!;
mpmc drains up to 64 values from a ready sender ring. With one receiver, the
remaining values stay in a private deque. Cloning publishes that deque before
the new receiver becomes visible. With multiple receivers, remaining values
enter bounded synchronized work queues, and competitors steal up to eight at a
time. Receiver drop republishes its buffered work. Ordering is relaxed. Moving
values into that second-stage queue costs more for large inline types; box large
payloads when move bandwidth dominates.
mpmc::try_recv may return a transient Empty while bounded lane maintenance
or another receiver moves work. Disconnected is final: all senders are gone,
sender rings and staged queues are drained, and no work publication is in
flight.
Contract
- One bounded ring per dynamic sender; capacity is a per-sender HWM rounded up to a power of two.
- MPSC preserves FIFO within each sender lane. MPMC ordering is relaxed and its receiver staging can temporarily exceed sender-ring capacity.
- Nonblocking, blocking, timeout, and deadline operations are available. Blocking operations spin briefly before parking.
- Disconnection never discards buffered values. MPMC
Emptymay be transient while receivers move internal work;Disconnectedis final. - Sender hot paths and MPSC batching avoid a shared queue lock. MPMC work distribution and topology maintenance use mutexes; blocking operations may park.
Good Fit
- Many long-lived or frequently changing producers feeding one or more consumers.
- MPSC callers need only per-producer FIFO; MPMC callers accept relaxed order.
- Per-producer HWM matches the desired backpressure model.
- Callers can use built-in parking or own the backoff policy around
try_*.
Bad Fit
- Need global FIFO or strict one-item round robin.
- Need one exact capacity shared across all producers.
- Need an exact total MPMC bound that includes receiver staging.
- Need async wakeups.
Further reading
- More detail: DESIGN.md
- Development and benchmark reproduction: DEVELOPMENT.md
- Release history: CHANGELOG.md