#143·conc

In `ErrorPool`, store errors as `[]error` instead of flattened `error`

Author: akalluCreated Nov 6, 2024Updated Dec 19, 2024

In the following snippet, we can see that errors are joined as they come in:

go
func (p *ErrorPool) addErr(err error) {
	if err != nil {
		p.mu.Lock()
		if p.onlyFirstError {
			if p.errs == nil {
				p.errs = err
			}
		} else {
			p.errs = multierror.Join(p.errs, err)
		}
		p.mu.Unlock()
	}
}

But this makes it incredibly difficult, without a recursive function, to pull the underlying errors out when consuming them from ErrorPool#Wait() (the current approach also stores the errors in a more suboptimal manner leading to more memory usage, but this isn't a huge deal):

go
func unwrapErrors(err error) []error {
	if err == nil {
		return nil
	}
	v, ok := err.(interface{ Unwrap() []error })
	if !ok {
		return []error{err}
	}
	var errs []error
	for _, wErr := range v.Unwrap() {
		errs = append(errs, unwrapErrors(wErr)...)
	}
	return errs
}

If the errors were kept as []error, and errors.Join(p.errs) was done inside ErrorPool#Wait(), it would be a 1-liner to pull the underlying errors out:

go
res, err := myPool.Wait()
errs := err.(interface{ Unwrap() []error }).Unwrap()

I don't mind doing the work for this, and it should be noted that it is backwards compatible w/ folks in the wild who have created their own functions like unwrapErrors above.