Skip to content

Commit 22ba207

Browse files
ewohltmangopherbot
authored andcommitted
singleflight: add panicError.Unwrap method
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 <bcmills@google.com> Reviewed-by: Than McIntosh <thanm@google.com> Reviewed-by: Bryan Mills <bcmills@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 93782cc commit 22ba207

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

singleflight/singleflight.go

+9
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ func (p *panicError) Error() string {
3131
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
3232
}
3333

34+
func (p *panicError) Unwrap() error {
35+
err, ok := p.value.(error)
36+
if !ok {
37+
return nil
38+
}
39+
40+
return err
41+
}
42+
3443
func newPanicError(v interface{}) error {
3544
stack := debug.Stack()
3645

singleflight/singleflight_test.go

+63
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,69 @@ import (
1919
"time"
2020
)
2121

22+
type errValue struct{}
23+
24+
func (err *errValue) Error() string {
25+
return "error value"
26+
}
27+
28+
func TestPanicErrorUnwrap(t *testing.T) {
29+
t.Parallel()
30+
31+
testCases := []struct {
32+
name string
33+
panicValue interface{}
34+
wrappedErrorType bool
35+
}{
36+
{
37+
name: "panicError wraps non-error type",
38+
panicValue: &panicError{value: "string value"},
39+
wrappedErrorType: false,
40+
},
41+
{
42+
name: "panicError wraps error type",
43+
panicValue: &panicError{value: new(errValue)},
44+
wrappedErrorType: false,
45+
},
46+
}
47+
48+
for _, tc := range testCases {
49+
tc := tc
50+
51+
t.Run(tc.name, func(t *testing.T) {
52+
t.Parallel()
53+
54+
var recovered interface{}
55+
56+
group := &Group{}
57+
58+
func() {
59+
defer func() {
60+
recovered = recover()
61+
t.Logf("after panic(%#v) in group.Do, recovered %#v", tc.panicValue, recovered)
62+
}()
63+
64+
_, _, _ = group.Do(tc.name, func() (interface{}, error) {
65+
panic(tc.panicValue)
66+
})
67+
}()
68+
69+
if recovered == nil {
70+
t.Fatal("expected a non-nil panic value")
71+
}
72+
73+
err, ok := recovered.(error)
74+
if !ok {
75+
t.Fatalf("recovered non-error type: %T", recovered)
76+
}
77+
78+
if !errors.Is(err, new(errValue)) && tc.wrappedErrorType {
79+
t.Errorf("unexpected wrapped error type %T; want %T", err, new(errValue))
80+
}
81+
})
82+
}
83+
}
84+
2285
func TestDo(t *testing.T) {
2386
var g Group
2487
v, err, _ := g.Do("key", func() (interface{}, error) {

0 commit comments

Comments
 (0)