r/golang • u/Reddit-ka-pilla • 18h ago
help Channel composition is hard: lessons from building a zero-dependency concurrency toolkit in Go
Hey gophers,
I’ve been working on FlowChan, a zero-dependency concurrency toolkit focused on composable channel-based primitives: heterogeneous worker pools, typed pipelines, ordered/unordered maps, batching (fixed + sliding window), retry with jitter + DLQ, and explicit termination.
The library is intentionally opinionated about a few things:
- Every operation is of the form `<-chan T → <-chan Result[T]`
- Context cancellation is checked at every blocking point
- WaitGroups track workers, never individual tasks
- User functions are always wrapped in recover so a panic becomes an error
- Metrics and rate limiting are opt-in, never global
I’m looking for feedback from people who have shipped similar infrastructure (CI runners, stream processors, job systems, or internal concurrency libraries).
Specific design questions I’m still wrestling with
- Backpressure vs. deadlock risk in the batcher
The current `Batcher` (and RealtimeBatcher) uses a capacity-1 output channel and a blocking send. Under a slow consumer the batcher goroutine itself blocks, which means it can no longer react to `ctx.Done()` or a manual Flush.
I’ve considered:
- non-blocking send + drop/error
- configurable buffer size
- a secondary “force flush with timeout” path
What patterns have you found least surprising for library consumers when the producer and consumer speeds diverge significantly?
- Ordered concurrent processing and memory growth
`OrderedMap` tags every item with a sequence number, processes concurrently, and releases results in order. Under high variance (some items take 10ms, others 2s) the pending map can grow without bound.
Have you found a practical upper bound / circuit-breaker strategy that doesn’t destroy throughput, or do you simply document “use OrderedMap only when the variance is bounded”?
- Worker pool lifecycle for long-running services
The pool is reusable (`Start -> Submit -> Drain -> Stop`). Workers are tracked by a WaitGroup so cancellation never hangs. Rate limiting happens at Submit time (not inside workers) to avoid multiple workers firing on the same ticker tick.
Is the Start/Submit/Drain/Stop surface area the right one for a library, or have you preferred a more “set and forget” model with automatic restart of failed workers?
- Error enrichment vs. caller ergonomics
We wrap errors in `StageError` / `PoolError` that carry stage name and attempt count, while remaining compatible with `errors.Is` / `errors.As`. This has been useful for observability, but it does add a small amount of allocation and indirection on the hot path.
At what point did you decide the extra context was worth the cost, or did you keep errors completely opaque?
- Sliding-window batcher timer hygiene
The realtime batcher resets a `time.Timer` on every item. We currently ignore the return value of `Stop()` and do not drain the channel. Under continuous load this can occasionally deliver a stale tick.
Is the classic “Stop + conditional drain” pattern still the best practice in 2026, or have people moved to other approaches (e.g. a single long-lived ticker + last-activity timestamp)?
What I’m deliberately not doing (and why)
- No global registries or singleton pools
- No reflection
- No external dependencies (goleak is test-only)
- No built-in persistence or logging
I’d be particularly interested in war stories around:
- places where pure channel composition started to fall apart at scale
- how you handled “zombie” goroutines that escaped context cancellation
- decisions around ordered vs unordered processing when latency SLAs were involved
- any patterns that turned out to be more robust than the classic select + ctx.Done() + WaitGroup combination
I am undergrad trying to work in this space so any feedback regarding project structure or anything casual will also work for me. Happy to go deeper on any of the packages (pool, pipeline, batch, stream, retry, termination) if people want to critique specific implementations.
Thanks.
Repo : (https://github.com/Atul-Koundal/FlowChan) Glad of anyone wants to refer to this more!