Structured, Observable Concurrency for Go

go get vawter.tech/stopper/v2
Package stopper brings structured, observable concurrency to Go programs. It extends the standard library context.Context with a hierarchical task model and two-phase graceful shutdown, providing deep runtime visibility into your application's task tree.
Features
- Structured concurrency – Tasks created with
WithContext form a
hierarchy: stopping a parent automatically stops its children, while
Len() and Wait() account for tasks across the entire tree.
- Observability – every
Context is associated with a TaskGroup
providing real-time visibility into the task hierarchy. Use
TaskGroupFrom(ctx), TaskInfoFrom(ctx), and TaskTree(group, out)
to inspect active tasks and their states.
- Always-on
runtime/trace – every stopper Context and every
task automatically creates a runtime/trace.Task, so the full
hierarchy appears in Go execution traces with zero extra code.
Built-in middleware in limit and retry annotate blocking waits
with trace.StartRegion for concurrency, rate-limit, and retry
visibility.
- Two-phase shutdown – calling
Stop() closes the Stopping()
channel, giving tasks a window to drain gracefully before the context
is canceled (Done()). Use Stopping() in a select or call
IsStopping() in a loop to respond to the signal.
- Generic task adaptors – the package-level
Go, GoN, Call, and
Defer functions accept any signature from func() to
func(stopper.Context) error.
- Middleware – composable
Middleware functions wrap task execution
for cross-cutting concerns such as concurrency or rate limiting (see
the limit
sub-package).
- Panic recovery – every task launched via
Call or Go is
wrapped in a default panic handler. If a task panics, the value is
recovered and wrapped in a RecoveredError that captures the
goroutine stack at the point of the panic. The error is then handled
exactly like a returned error (passed to the task's ErrorHandler for
Go, or returned to the caller for Call). Use
errors.As(err, &re) to extract the RecoveredError and inspect
its Stack field or call its String() method for a human-readable
trace.
- Context interop – works with any library that produces custom
context.Context values via WithDelegate() and
StoppingContext().
- OS signal integration –
StopOnReceive triggers a graceful
shutdown when a value arrives on any channel, making it easy to wire
up os/signal.Notify.
- Retry – the
retry
sub-package provides Middleware for retrying failed tasks. Backoff
offers exponential backoff with jitter, and Loop implements a
simple synchronous retry.
- Concurrent sequence processing – the
seq
sub-package provides bounded-concurrency helpers for iter.Seq and
iter.Seq2 sequences. ForEach / ForEach2 apply a callback to
every element; Map / Map2 and MapUnordered / MapUnordered2
transform elements into a new iter.Seq2[R, error], preserving or
relaxing input order respectively.
- Test helpers – the
linger
sub-package detects tasks that fail to exit promptly.
Competitive Analysis
For a detailed comparison of stopper against other Go concurrency libraries (such as errgroup, conc, and oklog/run), see the Competitive Market Analysis.
Quick Start
ctx := stopper.New()
_ = stopper.Go(ctx, func(ctx stopper.Context) {
for {
select {
case <-ctx.Stopping():
return
case item := <-work:
process(item)
}
}
})
ctx.Stop(stopper.StopGracePeriod(time.Second))
if err := ctx.Wait(); err != nil {
log.Fatal(err)
}
Deep Observability
The stopper package provides real-time visibility into your goroutine hierarchy:
ctx := stopper.New(stopper.WithName("api-server"))
_ = stopper.Go(ctx, task1, stopper.TaskName("request-handler"))
_ = stopper.Go(ctx, task2, stopper.TaskName("background-sync"))
if g, ok := stopper.TaskGroupFrom(ctx); ok {
stopper.TaskTree(g, os.Stdout)
}
Example output:
api-server
├── api-server.request-handler (started 2026-04-08T21:53:00Z) (running)
└── api-server.background-sync (started 2026-04-08T21:53:00Z) (running)
For simple loops that don't need a select, use the boolean shorthand:
_ = stopper.Go(ctx, func(ctx stopper.Context) {
for !ctx.IsStopping() {
}
})
Examples
The package
examples
demonstrate a variety of patterns:
Project History
Version 1 of this repository was extracted from
github.com/cockroachdb/field-eng-powertools using
git filter-repo --subdirectory-filter stopper --path LICENSE
by the code's original author.
Version 2 is a complete overhaul of the library's API with a focus on
structured, observable concurrency and better composition.