// Package errgroup provides synchronization, error propagation, and Context // cancelation for groups of goroutines working on subtasks of a common task. // Based on golangorg/x/sync package tool import ( "context" "sync" multierror "github.com/hashicorp/go-multierror" ) // A Group is a collection of goroutines working on subtasks that are part of // the same overall task. // // A zero Group is valid and does not cancel on error. type Group struct { cancel func() wg sync.WaitGroup errOnce sync.Once errs error // appended with each error errLock sync.RWMutex } // 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 } // Wait blocks until all function calls from the Go method have returned, then // returns an error representing all errors encountered from any goroutines in the group. func (g *Group) Wait() error { g.wg.Wait() if g.cancel != nil { g.cancel() } g.errLock.RLock() err := g.errs g.errLock.RUnlock() return err } // Go calls the given function in a new goroutine. // // The first call to return a non-nil error cancels the group; its error will be // returned by Wait. func (g *Group) Go(f func() error) { g.wg.Add(1) go func() { defer g.wg.Done() if err := f(); err != nil { g.errLock.Lock() g.errs = multierror.Append(g.errs, err) g.errLock.Unlock() g.errOnce.Do(func() { if g.cancel != nil { g.cancel() } }) } }() }