Skip to main content

Collect concurrent errors with Group

When you need to run multiple operations concurrently and be sure that all of them have completed while collecting any errors they produce, you can use multierror.Group. This struct manages a collection of goroutines and aggregates their errors.

To use it, you create a multierror.Group and then call its Go receiver method for each function you want to execute concurrently. The Go method takes a function of type func() error and runs it in a new goroutine. After scheduling all the desired functions, you call the Wait receiver method. Wait blocks until every function scheduled with Go has finished executing.

Handling Successful Operations

If all the functions you run with Go complete successfully and return a nil error, Wait will also return nil. This provides a clear signal that the entire group of operations succeeded. You can verify that all scheduled functions ran by using shared atomic counters.

package main

import (
"github.com/hashicorp/go-multierror"
"sync/atomic"
)

func main() {
var g multierror.Group
var runCount uint32

g.Go(func() error {
atomic.AddUint32(&runCount, 1)
return nil
})
g.Go(func() error {
atomic.AddUint32(&runCount, 1)
return nil
})

if g.Wait() != nil {
panic("expected a nil error")
}

if atomic.LoadUint32(&runCount) != 2 {
panic("not all functions were run")
}
}

Collecting Errors from Operations

If any of the functions passed to Go returns a non-nil error, Wait will collect it. When at least one function returns an error, the final error returned by Wait will be non-nil. This allows you to detect failure without needing to inspect the contents of the error itself. The order in which goroutines execute or in which errors are collected is not guaranteed.

package main

import (
"errors"
"github.com/hashicorp/go-multierror"
"sync/atomic"
)

func main() {
var g multierror.Group
var runCount uint32

g.Go(func() error {
atomic.AddUint32(&runCount, 1)
return errors.New("first error")
})
g.Go(func() error {
atomic.AddUint32(&runCount, 1)
return errors.New("second error")
})

if g.Wait() == nil {
panic("expected a non-nil error")
}

if atomic.LoadUint32(&runCount) != 2 {
panic("not all functions were run")
}
}