Skip to content

Latest commit

 

History

23 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go library for structured concurrency

go.dev reference

Structured concurrency helps reasoning about the behaviour of parallel programs. parallel implements structured concurrency for Go.

func subtask(ctx context.Context) error {
    // to be run in parallel
}

type subtaskWithData struct { /* ... */ }

func (swd *subtaskWithData) Run(ctx context.Context) error {
    // to be run in parallel
}

err := parallel.Run(ctx, func(ctx context.Context, spawn parallel.SpawnFn) error {
    swd := &subtaskWithData{}

    // do some synchronous initialization here

    spawn("subtask", parallel.Fail, subtask)
    spawn("subtaskWithData", parallel.Fail, swd.Run)
    return nil
})

Runs initialization within parallel.Run(), and then waits until the context is canceled, or one of the spawned tasks finishes. Panics in goroutines are captured.

See the documentation for additional features:

  • task groups without inversion of control
  • tasks that may exit and keep the group running
  • tasks that may exit and cause the group to stop gracefully

Compared to errgroup

golang.org/x/sync/errgroup covers the common case: run N goroutines, cancel a shared context when one fails, return that error. parallel differs where a long-lived process needs more than that.

  • Tasks have names, and the name appears in the error. errgroup's tasks are anonymous, so a failure tells you what went wrong but not which of them went wrong.
  • Exiting is a policy, not an accident. Continue, Exit and Fail say what a task returning nil means: a finite job that is done, a request to shut the group down gracefully, or a task that should never have stopped and whose stopping is itself a failure. errgroup has no way to express the third.
  • Every failure survives. GroupFailedError.Errors() lists all of them in order and names the one that caused the shutdown. errgroup keeps the first error and discards the rest.
  • Cancellation carries a cause. context.Cause on the group's context returns a ClosingError naming why shutdown began, so a task can tell being interrupted apart from failing.
  • Panics become errors rather than unwinding the program.
  • Initialization runs inside the group, so a setup failure shuts down whatever already started.

Errors

A task group has to answer three different questions when something goes wrong, and they have three different answers:

  • What did the group return? GroupFailedError. It unwraps as the cause, and Errors() lists every task failure that was not itself a consequence of the shutdown, in chronological order.
  • Why was the context canceled? ClosingError, available through context.Cause. It names the cause and the group, so cancellation of a parent group is distinguishable from cancellation of a child.
  • What did an individual task do? TaskFailedError, or TaskSucceededError for a task that finished cleanly in a mode where that ends the group.

ClosingError deliberately unwraps as context.Canceled rather than as its own cause. If task A fails to read a file and the ensuing shutdown interrupts its sibling task B, it does not mean that B failed to read a file — only that B was interrupted. Unwrapping to the cause would make errors.Is(err, fs.ErrNotExist) report true for a task that never touched the filesystem.

History

This library was written at Ridge and first published, at version 0.1.x, as dottedmag/parallel. It was later carried into a second codebase, where the error model above replaced the original single-error reporting: the earlier version kept only the first error, reported task failures as formatted strings, cancelled without a cause, and distinguished shutdown-induced cancellations from real ones by heuristic. Panic capture moved out into fault.

Legal

Copyright Tectonic Labs Ltd. (original), Onboard Inc. (error model, 2026).

Licensed under Apache 2.0 license.

Authors:

Releases

Packages

Contributors

Languages