Go

Explain errgroup: WithContext, SetLimit, TryGo. What are its semantics and gotchas?

Question 244HardGo 1.22 to 1.25

golang.org/x/sync/errgroup is a WaitGroup plus error propagation plus cancellation.

  • g.Go(f) runs f in a goroutine.
  • g.Wait() blocks until all functions return, then returns the first non-nil error.
  • WithContext derives a context that is cancelled on the first error or when Wait returns.
  • SetLimit(n) bounds the number of active goroutines; Go blocks when the limit is reached.
  • TryGo is the non-blocking version and returns false at the limit.
func FetchAll(ctx context.Context, urls []string) ([]Page, error) {
	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(8)
	pages := make([]Page, len(urls))
	for i, u := range urls {
		g.Go(func() error {
			p, err := fetch(ctx, u) // must honor ctx to stop early
			if err != nil {
				return fmt.Errorf("fetch %q: %w", u, err)
			}
			pages[i] = p // distinct indices: no race
			return nil
		})
	}
	if err := g.Wait(); err != nil {
		return nil, err
	}
	return pages, nil
}

Gotchas:

  • Cancellation is cooperative. If your functions ignore ctx, they keep running after the first error.
  • Only the first error is kept. Use errors.Join yourself if you need all of them.
  • The group context is cancelled as soon as Wait returns. Shadowing ctx (as above) hides the parent, so any code after Wait that still uses ctx sees an already-cancelled context. Name it gctx if you need the parent afterwards.
  • Calling SetLimit while goroutines are active panics.
  • errgroup does not turn panics into errors. Do not rely on it to recover them; if a panic in f must become an error, recover inside f.

More on Concurrency Patterns & sync

All 38 Concurrency Patterns & sync questions