2
2
// Use of this source code is governed by a BSD-style
3
3
// license that can be found in the LICENSE file.
4
4
5
- package singleflight
5
+ package singleflight // import "golang.org/x/sync/singleflight"
6
6
7
7
import (
8
8
"bytes"
@@ -19,6 +19,68 @@ import (
19
19
"time"
20
20
)
21
21
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 any
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
+ for _ , tc := range testCases {
48
+ tc := tc
49
+
50
+ t .Run (tc .name , func (t * testing.T ) {
51
+ t .Parallel ()
52
+
53
+ var recovered any
54
+
55
+ group := & Group [any ]{}
56
+
57
+ func () {
58
+ defer func () {
59
+ recovered = recover ()
60
+ t .Logf ("after panic(%#v) in group.Do, recovered %#v" , tc .panicValue , recovered )
61
+ }()
62
+
63
+ _ , _ , _ = group .Do (tc .name , func () (any , error ) {
64
+ panic (tc .panicValue )
65
+ })
66
+ }()
67
+
68
+ if recovered == nil {
69
+ t .Fatal ("expected a non-nil panic value" )
70
+ }
71
+
72
+ err , ok := recovered .(error )
73
+ if ! ok {
74
+ t .Fatalf ("recovered non-error type: %T" , recovered )
75
+ }
76
+
77
+ if ! errors .Is (err , new (errValue )) && tc .wrappedErrorType {
78
+ t .Errorf ("unexpected wrapped error type %T; want %T" , err , new (errValue ))
79
+ }
80
+ })
81
+ }
82
+ }
83
+
22
84
func TestDo (t * testing.T ) {
23
85
var g Group [string ]
24
86
v , err , _ := g .Do ("key" , func () (string , error ) {
@@ -95,7 +157,7 @@ func TestDoDupSuppress(t *testing.T) {
95
157
// Test that singleflight behaves correctly after Forget called.
96
158
// See https://github.com/golang/go/issues/31420
97
159
func TestForget (t * testing.T ) {
98
- var g Group [any ]
160
+ var g Group [int ]
99
161
100
162
var (
101
163
firstStarted = make (chan struct {})
@@ -104,7 +166,7 @@ func TestForget(t *testing.T) {
104
166
)
105
167
106
168
go func () {
107
- g .Do ("key" , func () (i any , e error ) {
169
+ g .Do ("key" , func () (i int , e error ) {
108
170
close (firstStarted )
109
171
<- unblockFirst
110
172
close (firstFinished )
@@ -115,15 +177,15 @@ func TestForget(t *testing.T) {
115
177
g .Forget ("key" )
116
178
117
179
unblockSecond := make (chan struct {})
118
- secondResult := g .DoChan ("key" , func () (i any , e error ) {
180
+ secondResult := g .DoChan ("key" , func () (i int , e error ) {
119
181
<- unblockSecond
120
182
return 2 , nil
121
183
})
122
184
123
185
close (unblockFirst )
124
186
<- firstFinished
125
187
126
- thirdResult := g .DoChan ("key" , func () (i any , e error ) {
188
+ thirdResult := g .DoChan ("key" , func () (i int , e error ) {
127
189
return 3 , nil
128
190
})
129
191
@@ -223,11 +285,24 @@ func TestGoexitDo(t *testing.T) {
223
285
}
224
286
}
225
287
226
- func TestPanicDoChan (t * testing.T ) {
227
- if runtime .GOOS == "js" {
228
- t .Skipf ("js does not support exec" )
288
+ func executable (t testing.TB ) string {
289
+ exe , err := os .Executable ()
290
+ if err != nil {
291
+ t .Skipf ("skipping: test executable not found" )
292
+ }
293
+
294
+ // Control case: check whether exec.Command works at all.
295
+ // (For example, it might fail with a permission error on iOS.)
296
+ cmd := exec .Command (exe , "-test.list=^$" )
297
+ cmd .Env = []string {}
298
+ if err := cmd .Run (); err != nil {
299
+ t .Skipf ("skipping: exec appears not to work on %s: %v" , runtime .GOOS , err )
229
300
}
230
301
302
+ return exe
303
+ }
304
+
305
+ func TestPanicDoChan (t * testing.T ) {
231
306
if os .Getenv ("TEST_PANIC_DOCHAN" ) != "" {
232
307
defer func () {
233
308
recover ()
@@ -243,7 +318,7 @@ func TestPanicDoChan(t *testing.T) {
243
318
244
319
t .Parallel ()
245
320
246
- cmd := exec .Command (os . Args [ 0 ] , "-test.run=" + t .Name (), "-test.v" )
321
+ cmd := exec .Command (executable ( t ) , "-test.run=" + t .Name (), "-test.v" )
247
322
cmd .Env = append (os .Environ (), "TEST_PANIC_DOCHAN=1" )
248
323
out := new (bytes.Buffer )
249
324
cmd .Stdout = out
@@ -266,10 +341,6 @@ func TestPanicDoChan(t *testing.T) {
266
341
}
267
342
268
343
func TestPanicDoSharedByDoChan (t * testing.T ) {
269
- if runtime .GOOS == "js" {
270
- t .Skipf ("js does not support exec" )
271
- }
272
-
273
344
if os .Getenv ("TEST_PANIC_DOCHAN" ) != "" {
274
345
blocked := make (chan struct {})
275
346
unblock := make (chan struct {})
@@ -297,7 +368,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
297
368
298
369
t .Parallel ()
299
370
300
- cmd := exec .Command (os . Args [ 0 ] , "-test.run=" + t .Name (), "-test.v" )
371
+ cmd := exec .Command (executable ( t ) , "-test.run=" + t .Name (), "-test.v" )
301
372
cmd .Env = append (os .Environ (), "TEST_PANIC_DOCHAN=1" )
302
373
out := new (bytes.Buffer )
303
374
cmd .Stdout = out
@@ -318,3 +389,33 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
318
389
t .Errorf ("Test subprocess failed, but the crash isn't caused by panicking in Do" )
319
390
}
320
391
}
392
+
393
+ func ExampleGroup () {
394
+ g := new (Group [string ])
395
+
396
+ block := make (chan struct {})
397
+ res1c := g .DoChan ("key" , func () (string , error ) {
398
+ <- block
399
+ return "func 1" , nil
400
+ })
401
+ res2c := g .DoChan ("key" , func () (string , error ) {
402
+ <- block
403
+ return "func 2" , nil
404
+ })
405
+ close (block )
406
+
407
+ res1 := <- res1c
408
+ res2 := <- res2c
409
+
410
+ // Results are shared by functions executed with duplicate keys.
411
+ fmt .Println ("Shared:" , res2 .Shared )
412
+ // Only the first function is executed: it is registered and started with "key",
413
+ // and doesn't complete before the second function is registered with a duplicate key.
414
+ fmt .Println ("Equal results:" , res1 .Val == res2 .Val )
415
+ fmt .Println ("Result:" , res1 .Val )
416
+
417
+ // Output:
418
+ // Shared: true
419
+ // Equal results: true
420
+ // Result: func 1
421
+ }
0 commit comments