Skip to content

Commit

Permalink
singleflight: add panicError.Unwrap method
Browse files Browse the repository at this point in the history
Currently when singleflight recovers from a panic, it wraps it with the private
error type panicError. This change adds an `Unwrap` method to panicError to
allow wrapped errors to be returned.

Updates golang/go#62511

Change-Id: Ia510ad7d5881207ef71f9eb89c1766835af19b6b
Reviewed-on: https://go-review.googlesource.com/c/sync/+/526171
Auto-Submit: Bryan Mills <[email protected]>
Reviewed-by: Than McIntosh <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
  • Loading branch information
ewohltman authored and gopherbot committed Sep 26, 2023
1 parent 93782cc commit 22ba207
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
9 changes: 9 additions & 0 deletions singleflight/singleflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ func (p *panicError) Error() string {
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
}

func (p *panicError) Unwrap() error {
err, ok := p.value.(error)
if !ok {
return nil
}

return err
}

func newPanicError(v interface{}) error {
stack := debug.Stack()

Expand Down
63 changes: 63 additions & 0 deletions singleflight/singleflight_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,69 @@ import (
"time"
)

type errValue struct{}

func (err *errValue) Error() string {
return "error value"
}

func TestPanicErrorUnwrap(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
panicValue interface{}
wrappedErrorType bool
}{
{
name: "panicError wraps non-error type",
panicValue: &panicError{value: "string value"},
wrappedErrorType: false,
},
{
name: "panicError wraps error type",
panicValue: &panicError{value: new(errValue)},
wrappedErrorType: false,
},
}

for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

var recovered interface{}

group := &Group{}

func() {
defer func() {
recovered = recover()
t.Logf("after panic(%#v) in group.Do, recovered %#v", tc.panicValue, recovered)
}()

_, _, _ = group.Do(tc.name, func() (interface{}, error) {
panic(tc.panicValue)
})
}()

if recovered == nil {
t.Fatal("expected a non-nil panic value")
}

err, ok := recovered.(error)
if !ok {
t.Fatalf("recovered non-error type: %T", recovered)
}

if !errors.Is(err, new(errValue)) && tc.wrappedErrorType {
t.Errorf("unexpected wrapped error type %T; want %T", err, new(errValue))
}
})
}
}

func TestDo(t *testing.T) {
var g Group
v, err, _ := g.Do("key", func() (interface{}, error) {
Expand Down

0 comments on commit 22ba207

Please sign in to comment.