Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
ItalyPaleAle opened this issue May 15, 2023 · 5 comments · May be fixed by #60215
Closed

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

ItalyPaleAle opened this issue May 15, 2023 · 5 comments · May be fixed by #60215

Comments

@ItalyPaleAle
Copy link

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

@seankhliao
Copy link
Member

We explicitly decided against this in #53435 (comment)

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale May 15, 2023
@ItalyPaleAle
Copy link
Author

@seankhliao thanks for the link, although I'm a bit bummed the code above (which looks cleaner than the alternative of collecting all errors in a slice first) can't be used :(

Would it be possible to have this included in the docs so it's clear the example above is not the intended use?

@ianlancetaylor
Copy link
Contributor

@ItalyPaleAle I'm not clear on how the docs should be changed. Do you want to send a patch or at least outline the doc change here? Thanks.

ItalyPaleAle added a commit to ItalyPaleAle/go that referenced this issue May 16, 2023
Updates the documentation for Join to explain that it does not append to an existing joinError (i.e. the result of calling Join), and if one of the errors is a joinError, it will be wrapped again. See golang#60209

It also adds a test to provide an example of said behavior, as well as formalizing it in the API's contract.
@ItalyPaleAle
Copy link
Author

@ianlancetaylor I have opened #60215. It's my first contribution to Go itself so I hope I did it right :)

@gopherbot
Copy link

Change https://go.dev/cl/495076 mentions this issue: errors: clarify that Join doesn't extend already-joined errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants