Quick review of errgroup:
An errgroup.Group unifies error propagation and context cancelation.
The calling routines Wait() for subtasks to complete; if any subtask returns an error, Wait() returns that error back to the caller.
If any subtask returns an error, the group Context is canceled, which early-terminates all subtasks.
Regarding the group's Context:
If the parent Context (say, an http request context) is canceled, the group Context is also canceled. This helps avoid unnecessary work. For example, if the user navigates away from the page and cancels the http request, we can stop immediately.
You can see from the code that when an errgroup is created, a new context is created:
// WithContext returns a new Group and an associated Context derived from ctx.
//
// The derived Context is canceled the first time a function passed to Go
// returns a non-nil error or the first time Wait returns, whichever occurs
// first.
func WithContext(ctx context.Context) (*Group, context.Context) {
ctx, cancel := context.WithCancel(ctx)
return &Group{cancel: cancel}, ctx
}
The cancel function that you see on the group is a return value of the withCancel function, and belongs to this new context, not directly to the group.
This context needs to be cancelled as soon as one function returns an error, but also if the operation completes, as you can see from the code's documentation: "or the first time Wait Returns"
Why?
The wait is a wait on the entire wait group.
// Wait blocks until all function calls from the Go method have returned, then
// returns the first non-nil error (if any) from them.
func (g *Group) Wait() error {
g.wg.Wait()
if g.cancel != nil {
g.cancel()
}
return g.err
}
This means that the entire operation is complete. Its context needs to be cancelled to clear up the context's resources. Perhaps the operation even started a child context which is still running and no longer needed, and the cancellation needs to be propagated to it.