Collect concurrent errors with Group
Collect Concurrent Errors with a Group
When you need to run multiple independent operations concurrently and collect any errors they produce, you can use a multierror.Group. This is useful for tasks like parallelizing outgoing API requests or performing concurrent background processing, where you want to wait for all operations to finish and know if any of them failed.
The Group provides two main methods: Go and Wait. You call the Go receiver method to schedule a function to run in a new goroutine. You then call the Wait receiver method to block until all scheduled functions have completed. Wait collects any errors returned by the functions and returns a single error value.
Handling Successful Operations
If all functions scheduled with Go complete successfully and return nil, the Wait method will also return nil. This indicates that the entire group of operations succeeded.
You start by declaring a multierror.Group variable. Then, for each concurrent function you want to run, you call group.Go with a function literal. Finally, you call group.Wait() and verify its result is nil.
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")
}
}
Collecting Errors
If any of the functions passed to Go return a non-nil error, Wait collects it. After all goroutines have finished, Wait returns a non-nil error value that combines all the errors that occurred.
The order in which the goroutines execute is not guaranteed, so the order of the underlying errors is also not specified. Your code should not depend on the order of errors.
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")
}
}