Skip to content

errors: Using errors.Join on a joinError should expand the existing joined error #60209

@ItalyPaleAle

Description

@ItalyPaleAle

What version of Go are you using (go version)?

$ go version
go version go1.20.4 linux/amd64

Does this issue reproduce with the latest release?

Yes

Description

Consider this code (Playground)

var err error
err = errors.Join(err, errors.New("err1"))
err = errors.Join(err, errors.New("err2"))
err = errors.Join(err, nil)

errs := err.(interface{ Unwrap() []error }).Unwrap()
fmt.Println(len(errs), errs)

// Output:
// 1 [err1
// err2]

This happens because when calling errors.Join, it does not check if one of the errors is a joinError already, so it creates a new one every time.

  1. After err = errors.Join(err, errors.New("err1")), err is a joinError with 1 item inside (and that's expected)
  2. After err = errors.Join(err, errors.New("err2")), err is a joinError in which item 1 is a joinError itself, and item 2 is the error.
  3. After err = errors.Join(err, nil), err is a joinError that contains 2 joinError's (and the first one contains a joinError itself)

We found this bug while trying to implement a pattern where we collect all errors from a channel. We have N goroutines running in parallel, and we wanted to use this pattern to both collect errors (which could be nil) and wait for all goroutines to complete:

errCh := make(chan error, 3)
errCh<-errors.New("err1")
errCh<-errors.New("err2")
errCh<-nil

var err error
for i := 0; i < 3; i++ {
	err = errors.Join(err, <-errCh)
}

In the case above, calling Unwrap() []error on the result lead to unexpected results because of errors being wrapped multiple times in joinError objects

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions