mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-07-18 17:38:07 +08:00
chore(pkg): update singleflight
This commit is contained in:
@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package singleflight
|
||||
package singleflight // import "golang.org/x/sync/singleflight"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -19,6 +19,68 @@ 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 any
|
||||
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 any
|
||||
|
||||
group := &Group[any]{}
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
recovered = recover()
|
||||
t.Logf("after panic(%#v) in group.Do, recovered %#v", tc.panicValue, recovered)
|
||||
}()
|
||||
|
||||
_, _, _ = group.Do(tc.name, func() (any, 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[string]
|
||||
v, err, _ := g.Do("key", func() (string, error) {
|
||||
@ -95,7 +157,7 @@ func TestDoDupSuppress(t *testing.T) {
|
||||
// Test that singleflight behaves correctly after Forget called.
|
||||
// See https://github.com/golang/go/issues/31420
|
||||
func TestForget(t *testing.T) {
|
||||
var g Group[any]
|
||||
var g Group[int]
|
||||
|
||||
var (
|
||||
firstStarted = make(chan struct{})
|
||||
@ -104,7 +166,7 @@ func TestForget(t *testing.T) {
|
||||
)
|
||||
|
||||
go func() {
|
||||
g.Do("key", func() (i any, e error) {
|
||||
g.Do("key", func() (i int, e error) {
|
||||
close(firstStarted)
|
||||
<-unblockFirst
|
||||
close(firstFinished)
|
||||
@ -115,7 +177,7 @@ func TestForget(t *testing.T) {
|
||||
g.Forget("key")
|
||||
|
||||
unblockSecond := make(chan struct{})
|
||||
secondResult := g.DoChan("key", func() (i any, e error) {
|
||||
secondResult := g.DoChan("key", func() (i int, e error) {
|
||||
<-unblockSecond
|
||||
return 2, nil
|
||||
})
|
||||
@ -123,7 +185,7 @@ func TestForget(t *testing.T) {
|
||||
close(unblockFirst)
|
||||
<-firstFinished
|
||||
|
||||
thirdResult := g.DoChan("key", func() (i any, e error) {
|
||||
thirdResult := g.DoChan("key", func() (i int, e error) {
|
||||
return 3, nil
|
||||
})
|
||||
|
||||
@ -223,11 +285,24 @@ func TestGoexitDo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPanicDoChan(t *testing.T) {
|
||||
if runtime.GOOS == "js" {
|
||||
t.Skipf("js does not support exec")
|
||||
func executable(t testing.TB) string {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Skipf("skipping: test executable not found")
|
||||
}
|
||||
|
||||
// Control case: check whether exec.Command works at all.
|
||||
// (For example, it might fail with a permission error on iOS.)
|
||||
cmd := exec.Command(exe, "-test.list=^$")
|
||||
cmd.Env = []string{}
|
||||
if err := cmd.Run(); err != nil {
|
||||
t.Skipf("skipping: exec appears not to work on %s: %v", runtime.GOOS, err)
|
||||
}
|
||||
|
||||
return exe
|
||||
}
|
||||
|
||||
func TestPanicDoChan(t *testing.T) {
|
||||
if os.Getenv("TEST_PANIC_DOCHAN") != "" {
|
||||
defer func() {
|
||||
recover()
|
||||
@ -243,7 +318,7 @@ func TestPanicDoChan(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
|
||||
cmd := exec.Command(executable(t), "-test.run="+t.Name(), "-test.v")
|
||||
cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
|
||||
out := new(bytes.Buffer)
|
||||
cmd.Stdout = out
|
||||
@ -266,10 +341,6 @@ func TestPanicDoChan(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPanicDoSharedByDoChan(t *testing.T) {
|
||||
if runtime.GOOS == "js" {
|
||||
t.Skipf("js does not support exec")
|
||||
}
|
||||
|
||||
if os.Getenv("TEST_PANIC_DOCHAN") != "" {
|
||||
blocked := make(chan struct{})
|
||||
unblock := make(chan struct{})
|
||||
@ -297,7 +368,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
cmd := exec.Command(os.Args[0], "-test.run="+t.Name(), "-test.v")
|
||||
cmd := exec.Command(executable(t), "-test.run="+t.Name(), "-test.v")
|
||||
cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
|
||||
out := new(bytes.Buffer)
|
||||
cmd.Stdout = out
|
||||
@ -318,3 +389,33 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
|
||||
t.Errorf("Test subprocess failed, but the crash isn't caused by panicking in Do")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleGroup() {
|
||||
g := new(Group[string])
|
||||
|
||||
block := make(chan struct{})
|
||||
res1c := g.DoChan("key", func() (string, error) {
|
||||
<-block
|
||||
return "func 1", nil
|
||||
})
|
||||
res2c := g.DoChan("key", func() (string, error) {
|
||||
<-block
|
||||
return "func 2", nil
|
||||
})
|
||||
close(block)
|
||||
|
||||
res1 := <-res1c
|
||||
res2 := <-res2c
|
||||
|
||||
// Results are shared by functions executed with duplicate keys.
|
||||
fmt.Println("Shared:", res2.Shared)
|
||||
// Only the first function is executed: it is registered and started with "key",
|
||||
// and doesn't complete before the second function is registered with a duplicate key.
|
||||
fmt.Println("Equal results:", res1.Val == res2.Val)
|
||||
fmt.Println("Result:", res1.Val)
|
||||
|
||||
// Output:
|
||||
// Shared: true
|
||||
// Equal results: true
|
||||
// Result: func 1
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
// Package singleflight provides a duplicate function call suppression
|
||||
// mechanism.
|
||||
package singleflight
|
||||
package singleflight // import "golang.org/x/sync/singleflight"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -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 any) error {
|
||||
stack := debug.Stack()
|
||||
|
||||
@ -52,10 +61,6 @@ type call[T any] struct {
|
||||
val T
|
||||
err error
|
||||
|
||||
// forgotten indicates whether Forget was called with this call's key
|
||||
// while the call was still in flight.
|
||||
forgotten bool
|
||||
|
||||
// These fields are read and written with the singleflight
|
||||
// mutex held before the WaitGroup is done, and are read but
|
||||
// not written after the WaitGroup is done.
|
||||
@ -148,10 +153,10 @@ func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
|
||||
c.err = errGoexit
|
||||
}
|
||||
|
||||
c.wg.Done()
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if !c.forgotten {
|
||||
c.wg.Done()
|
||||
if g.m[key] == c {
|
||||
delete(g.m, key)
|
||||
}
|
||||
|
||||
@ -204,9 +209,6 @@ func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
|
||||
// an earlier call to complete.
|
||||
func (g *Group[T]) Forget(key string) {
|
||||
g.mu.Lock()
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.forgotten = true
|
||||
}
|
||||
delete(g.m, key)
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
Reference in New Issue
Block a user