SEDA bus · Rust · 0.4.0

The cleanest expression of one real shared thread pool.

seda-bus-rust hand-rolls everything the runtime doesn't give you for free: the pool, the permits, the bounded queue. No garbage collector means the shape of the design translates almost directly into the implementation — and it posted the highest single-stage sustained throughput of any of the seven ports.

Worker pool
Hand-rolled — OS threads parked on a shared job queue
True stage parallelism
Yes — OS threads
Dependencies
ra-common, log, parking_lot, crossbeam-queue
Envelope
ra_common::Envelope (rewired at 0.4.0)
Routing slip
DynamicRoutingSlip — LIFO
Concurrency permits
Hand-rolled atomic-CAS loop, not a semaphore type
Guaranteed delivery
No — in-memory only
Batch size
16 envelopes per drain
Bus sizing
Bus::new(0) sizes the pool to available_parallelism()
Race/sanitizer verified
Not run — TSan attempt untrustworthy in sandbox

How it implements the design

Real threads, real locks, hand-rolled by necessity.

Bus is Arc<Inner>, cheap to clone and hand to consumers and callbacks — how a consumer re-publishes mid-pipeline. The pool is a fixed set of OS threads parked on a shared job queue; concurrency permits are a hand-rolled atomic-CAS loop (AtomicUsize + compare_exchange_weak), not a semaphore type. Per-hop attempts live on the channel, keyed by envelope id, since ra_common::Envelope carries none — that map is only touched when a stage's max attempts exceed one, so the default single-attempt case never pays for the lock.

A Consumer is a trait, blanket-implemented for any Fn(&mut Envelope) -> bool + Send + Sync, so closures subscribe directly with no adapter type. No persistence, no datatype filter, no pull model — the minimal core the shared design asks for, and nothing more.

Two real concurrency bugs were found and fixed by the benchmark comparison, in two separate parts of the pool. The pool's job queue originally used std::sync::mpsc, which parks a waiting worker immediately with no spin phase — a real cold-wake stall (up to ~260ms) on the benchmark's Docker host. Swapping to parking_lot's Mutex+Condvar (which spins briefly before parking, closer to what Java's ExecutorService does) fixed it for a real but small (~4.6%) throughput cost under contention. Separately, the channel's own data queue — originally Mutex<VecDeque<Envelope>>, shared by every producer and consumer on a stage — showed an 8-producer lock-contention cliff (p50 ~38µs, p99 66–70ms). Replacing it with crossbeam-queue's lock-free, bounded ArrayQueue, plus a waiter-count gate so poll() skips the lock entirely when nothing is waiting, took that config's throughput from 427,650 to 709,629 eps (+66%).

Results

The highest ceiling of any port — and a real backlog once nothing bottlenecks it anymore.

Capacity curve — sustained throughput under real, bounded back-pressure
ConfigSustained eps0.5×1.0×1.5×
cap1679,3630.940.870.64
cap8456,1600.890.820.64

679,363 sustained eps at cap1 is the highest single-stage number of any of the seven ports.

Firehose throughput (envelopes/sec, unthrottled — diagnostic, not capacity planning)
Configepsvs. seq
seq (1 producer)606,442
par (8 producers, 1 channel)727,2571.20×
chan (8 producers, 8 channels)725,8941.20×

Reading the numbers

The fix moved real work — which is why latency went up, not down.

par now beats seq (1.20×, nearly matching chan) — direct evidence the ArrayQueue fix worked: removing the lock let 8 producers actually add throughput instead of contending it away.

Both par and chan now show a real, sustained latency backlog (p50/elapsed 0.34/0.60 and 0.33/0.72 respectively) where par used to show a rare, severe cliff instead. That's the fix working, not a regression — by Little's law, a system doing 66% more real work per second naturally carries a bigger average queue depth. Separately, chan's 8 channels need 16 concurrently-progressing threads on this benchmark's 12-core host — a hard oversubscription regardless of software design; reducing channel count to fit the host's real core budget recovers most of the latency at no throughput cost. An earlier finding that the ra_common envelope rewire cost real dispatch-time throughput turned out, on closer inspection, to be mostly a construction-cost artifact rather than an ongoing per-envelope tax — flagged as the natural next check, not re-confirmed.

The full cross-language results, including the core-budget investigation

Source

Read the code, or the rest of the family.