22// Use of this source code is governed by a BSD-style
33// license that can be found in the LICENSE file.
44
5- package singleflight
5+ package singleflight // import "golang.org/x/sync/singleflight"
66
77import (
88 "bytes"
@@ -19,6 +19,68 @@ 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 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+
2284func TestDo (t * testing.T ) {
2385 var g Group [string ]
2486 v , err , _ := g .Do ("key" , func () (string , error ) {
@@ -95,7 +157,7 @@ func TestDoDupSuppress(t *testing.T) {
95157// Test that singleflight behaves correctly after Forget called.
96158// See https://github.com/golang/go/issues/31420
97159func TestForget (t * testing.T ) {
98- var g Group [any ]
160+ var g Group [int ]
99161
100162 var (
101163 firstStarted = make (chan struct {})
@@ -104,7 +166,7 @@ func TestForget(t *testing.T) {
104166 )
105167
106168 go func () {
107- g .Do ("key" , func () (i any , e error ) {
169+ g .Do ("key" , func () (i int , e error ) {
108170 close (firstStarted )
109171 <- unblockFirst
110172 close (firstFinished )
@@ -115,15 +177,15 @@ func TestForget(t *testing.T) {
115177 g .Forget ("key" )
116178
117179 unblockSecond := make (chan struct {})
118- secondResult := g .DoChan ("key" , func () (i any , e error ) {
180+ secondResult := g .DoChan ("key" , func () (i int , e error ) {
119181 <- unblockSecond
120182 return 2 , nil
121183 })
122184
123185 close (unblockFirst )
124186 <- firstFinished
125187
126- thirdResult := g .DoChan ("key" , func () (i any , e error ) {
188+ thirdResult := g .DoChan ("key" , func () (i int , e error ) {
127189 return 3 , nil
128190 })
129191
@@ -223,11 +285,24 @@ func TestGoexitDo(t *testing.T) {
223285 }
224286}
225287
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 )
229300 }
230301
302+ return exe
303+ }
304+
305+ func TestPanicDoChan (t * testing.T ) {
231306 if os .Getenv ("TEST_PANIC_DOCHAN" ) != "" {
232307 defer func () {
233308 recover ()
@@ -243,7 +318,7 @@ func TestPanicDoChan(t *testing.T) {
243318
244319 t .Parallel ()
245320
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" )
247322 cmd .Env = append (os .Environ (), "TEST_PANIC_DOCHAN=1" )
248323 out := new (bytes.Buffer )
249324 cmd .Stdout = out
@@ -266,10 +341,6 @@ func TestPanicDoChan(t *testing.T) {
266341}
267342
268343func TestPanicDoSharedByDoChan (t * testing.T ) {
269- if runtime .GOOS == "js" {
270- t .Skipf ("js does not support exec" )
271- }
272-
273344 if os .Getenv ("TEST_PANIC_DOCHAN" ) != "" {
274345 blocked := make (chan struct {})
275346 unblock := make (chan struct {})
@@ -297,7 +368,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
297368
298369 t .Parallel ()
299370
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" )
301372 cmd .Env = append (os .Environ (), "TEST_PANIC_DOCHAN=1" )
302373 out := new (bytes.Buffer )
303374 cmd .Stdout = out
@@ -318,3 +389,33 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
318389 t .Errorf ("Test subprocess failed, but the crash isn't caused by panicking in Do" )
319390 }
320391}
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