SEDA bus · Go · 0.1.0
No pool needed — goroutines already fit the shape.
seda-bus-go needs neither a hand-rolled pool (Rust, C++) nor a borrowed built-in one
(Java, C#) — Go's own primitives are close enough to the SEDA shape that no pool abstraction
was needed at all. It's also the only one of the seven ports with an actually clean
go test -race run behind it.
go bus.drain(ch) goroutine per scheduled drainra-common-go onlymessaging.Envelope, aliased as sedabus.Envelope via a real Go type aliasDynamicRoutingSlip — LIFO via append/pop at a slice's end*time.Duration, deliberately not context.Contextgo test -race, clean, five repeated runsHow it implements the design
The language's own primitives, used as idiomatically as possible.
Goroutines are cheap enough (a few KB of stack, M:N-scheduled onto OS threads by the Go
runtime) that pooling them would fight the language rather than match it — this port's
biggest structural departure from the shared design's “one shared worker pool.” A
bus-wide buffered channel used as a counting semaphore still bounds how many drain goroutines
run concurrently, playing the role a sized pool plays elsewhere, layered on top of each stage's
own per-channel semaphore — Go's standard idiom for a semaphore, since the language ships
no counting-semaphore type in its standard library. Go has no default/named parameters, so the
envelope's optional slip/sender/headers become the functional-options pattern
(MakeEnvelope(to, payload, WithSlip(...), WithSender(...))) instead.
sync.Mutex + sync.Cond back the per-stage queue, itself a
container/list.List rather than a slice — repeatedly re-slicing a FIFO from
the front never shrinks the backing array, a real memory-growth gotcha for a long-running bus,
while a linked list gives true O(1) push/pop at both ends. sync.Cond has no
built-in timeout, so a time.AfterFunc timer broadcasts on expiry and the waiter
re-checks its own deadline on wake — the same pattern other ports implement with
wait_until/Monitor.Wait(timeout). And since nothing here is a pool
Shutdown can join, an atomic in-flight counter (the same technique C# needs for
the same reason) lets awaitDrain wait for zero in-flight and zero depth,
not depth alone.
The one real bug found and fixed: a lost-wakeup race in schedule().
It silently gave up when a bus-wide permit acquisition failed, with no guarantee anything would
ever retry the channel — if a channel's own producer had already finished and its last
drain goroutine lost that race, its remaining backlog sat queued until Shutdown's
timeout expired. This showed up intermittently in this very benchmark's chan
config across two separate sessions. The fix: releaseBusPermit now sweeps every
registered channel with a non-empty queue and re-offers it to schedule() whenever
a permit frees, not just the channel that happened to release it. Verified: go test
-race clean, 10 consecutive full benchmark runs (30 chan trials) with zero
drain failures, versus 2-of-3 failing before the fix under the same conditions.
Results
Strong channel-splitting gains, and a clean bill of health on races.
| Config | Sustained eps | 0.5× | 1.0× | 1.5× |
|---|---|---|---|---|
cap1 | 179,706 | 0.95 | 0.94 | 0.65 |
cap8 | 399,558 | 0.95 | 0.89 | 0.60 |
| Config | eps | vs. seq |
|---|---|---|
seq (1 producer) | 409,220 | — |
par (8 producers, 1 channel) | 368,838 | 0.90× |
chan (8 producers, 8 channels) | 1,517,294 | 3.71× |
Reading the numbers
par's flat ratio is misleading on its own — read it against chan instead.
par barely beating (here, slightly trailing) seq looks like Go gets
little from parallelism, but that's misleading rather than wrong: this port's seq
is itself already backlogged (p50/elapsed 0.46, a real sustained-backlog case), not a clean
baseline — comparing par against an already-degraded seq
produces a ratio that says nothing about whether parallelism helped. chan's
3.71× gain against the same seq is the number that actually answers the
question, and it's one of the strongest channel-splitting gains in the whole comparison.
The full cross-language results, including the schedule() race investigation
Source
Read the code, or the rest of the family.
seda-bus-go on GitHub
— source, its own DESIGN.md, and the test suite.