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.
ra-common, log, parking_lot, crossbeam-queuera_common::Envelope (rewired at 0.4.0)DynamicRoutingSlip — LIFOBus::new(0) sizes the pool to available_parallelism()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.
| Config | Sustained eps | 0.5× | 1.0× | 1.5× |
|---|---|---|---|---|
cap1 | 679,363 | 0.94 | 0.87 | 0.64 |
cap8 | 456,160 | 0.89 | 0.82 | 0.64 |
679,363 sustained eps at cap1 is the highest single-stage number of any of the seven
ports.
| Config | eps | vs. seq |
|---|---|---|
seq (1 producer) | 606,442 | — |
par (8 producers, 1 channel) | 727,257 | 1.20× |
chan (8 producers, 8 channels) | 725,894 | 1.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.
seda-bus-rust on GitHub — source, tests, and examples.