Collect concurrent errors with Group
When you need to run multiple operations concurrently and collect any errors they produce, you can use multierror.Group. This is useful for tasks like parallelizing outgoing API requests or performing concurrent cleanup operations where you need to know if any single step failed.
You can start using a Group with a simple zero-value declaration. Schedule functions to be run in their own goroutines using the Go method. A call to the Wait method will block until all scheduled functions have completed. If all functions execute successfully and return nil, Wait will also return nil.
The following example schedules two functions that both succeed. It uses an atomic counter to confirm that both functions ran before Wait returned. The program panics if Wait returns an error or if the counter does not reflect that both functions executed.
package main
import (
"sync/atomic"
"github.com/hashicorp/go-multierror"
)
func main() {
var group multierror.Group
var ran atomic.Int32
group.Go(func() error { ran.Add(1); return nil })
group.Go(func() error { ran.Add(1); return nil })
result := group.Wait()
if result != nil || ran.Load() != 2 {
panic("expected both functions and no errors")
}
}
If any function passed to Go returns a non-nil error, Wait collects it. After all functions have finished, Wait returns a single, non-nil error value containing all the errors that were collected. The order in which errors are collected is not guaranteed, so you should not depend on it. The primary use is to check whether the result of Wait is nil to determine if any failures occurred.
This example demonstrates a scenario where two different errors are returned. The call to Wait blocks until both functions complete, and the final result is verified to be non-nil. As in the success case, an atomic counter deterministically verifies that both functions were executed.
package main
import (
"errors"
"sync/atomic"
"github.com/hashicorp/go-multierror"
)
func main() {
var group multierror.Group
var ran atomic.Int32
group.Go(func() error { ran.Add(1); return errors.New("alpha") })
group.Go(func() error { ran.Add(1); return errors.New("beta") })
result := group.Wait()
if result == nil || ran.Load() != 2 {
panic("expected both functions and errors")
}
}