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
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package singleflight
|
package singleflight // import "golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -19,6 +19,68 @@ import (
|
|||||||
"time"
|
"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) {
|
func TestDo(t *testing.T) {
|
||||||
var g Group[string]
|
var g Group[string]
|
||||||
v, err, _ := g.Do("key", func() (string, error) {
|
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.
|
// Test that singleflight behaves correctly after Forget called.
|
||||||
// See https://github.com/golang/go/issues/31420
|
// See https://github.com/golang/go/issues/31420
|
||||||
func TestForget(t *testing.T) {
|
func TestForget(t *testing.T) {
|
||||||
var g Group[any]
|
var g Group[int]
|
||||||
|
|
||||||
var (
|
var (
|
||||||
firstStarted = make(chan struct{})
|
firstStarted = make(chan struct{})
|
||||||
@ -104,7 +166,7 @@ func TestForget(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
g.Do("key", func() (i any, e error) {
|
g.Do("key", func() (i int, e error) {
|
||||||
close(firstStarted)
|
close(firstStarted)
|
||||||
<-unblockFirst
|
<-unblockFirst
|
||||||
close(firstFinished)
|
close(firstFinished)
|
||||||
@ -115,7 +177,7 @@ func TestForget(t *testing.T) {
|
|||||||
g.Forget("key")
|
g.Forget("key")
|
||||||
|
|
||||||
unblockSecond := make(chan struct{})
|
unblockSecond := make(chan struct{})
|
||||||
secondResult := g.DoChan("key", func() (i any, e error) {
|
secondResult := g.DoChan("key", func() (i int, e error) {
|
||||||
<-unblockSecond
|
<-unblockSecond
|
||||||
return 2, nil
|
return 2, nil
|
||||||
})
|
})
|
||||||
@ -123,7 +185,7 @@ func TestForget(t *testing.T) {
|
|||||||
close(unblockFirst)
|
close(unblockFirst)
|
||||||
<-firstFinished
|
<-firstFinished
|
||||||
|
|
||||||
thirdResult := g.DoChan("key", func() (i any, e error) {
|
thirdResult := g.DoChan("key", func() (i int, e error) {
|
||||||
return 3, nil
|
return 3, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -223,11 +285,24 @@ func TestGoexitDo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPanicDoChan(t *testing.T) {
|
func executable(t testing.TB) string {
|
||||||
if runtime.GOOS == "js" {
|
exe, err := os.Executable()
|
||||||
t.Skipf("js does not support exec")
|
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") != "" {
|
if os.Getenv("TEST_PANIC_DOCHAN") != "" {
|
||||||
defer func() {
|
defer func() {
|
||||||
recover()
|
recover()
|
||||||
@ -243,7 +318,7 @@ func TestPanicDoChan(t *testing.T) {
|
|||||||
|
|
||||||
t.Parallel()
|
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")
|
cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
cmd.Stdout = out
|
cmd.Stdout = out
|
||||||
@ -266,10 +341,6 @@ func TestPanicDoChan(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPanicDoSharedByDoChan(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") != "" {
|
if os.Getenv("TEST_PANIC_DOCHAN") != "" {
|
||||||
blocked := make(chan struct{})
|
blocked := make(chan struct{})
|
||||||
unblock := make(chan struct{})
|
unblock := make(chan struct{})
|
||||||
@ -297,7 +368,7 @@ func TestPanicDoSharedByDoChan(t *testing.T) {
|
|||||||
|
|
||||||
t.Parallel()
|
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")
|
cmd.Env = append(os.Environ(), "TEST_PANIC_DOCHAN=1")
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
cmd.Stdout = out
|
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")
|
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
|
// Package singleflight provides a duplicate function call suppression
|
||||||
// mechanism.
|
// mechanism.
|
||||||
package singleflight
|
package singleflight // import "golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -31,6 +31,15 @@ func (p *panicError) Error() string {
|
|||||||
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
|
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 {
|
func newPanicError(v any) error {
|
||||||
stack := debug.Stack()
|
stack := debug.Stack()
|
||||||
|
|
||||||
@ -52,10 +61,6 @@ type call[T any] struct {
|
|||||||
val T
|
val T
|
||||||
err error
|
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
|
// These fields are read and written with the singleflight
|
||||||
// mutex held before the WaitGroup is done, and are read but
|
// mutex held before the WaitGroup is done, and are read but
|
||||||
// not written after the WaitGroup is done.
|
// 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.err = errGoexit
|
||||||
}
|
}
|
||||||
|
|
||||||
c.wg.Done()
|
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
defer g.mu.Unlock()
|
defer g.mu.Unlock()
|
||||||
if !c.forgotten {
|
c.wg.Done()
|
||||||
|
if g.m[key] == c {
|
||||||
delete(g.m, key)
|
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.
|
// an earlier call to complete.
|
||||||
func (g *Group[T]) Forget(key string) {
|
func (g *Group[T]) Forget(key string) {
|
||||||
g.mu.Lock()
|
g.mu.Lock()
|
||||||
if c, ok := g.m[key]; ok {
|
|
||||||
c.forgotten = true
|
|
||||||
}
|
|
||||||
delete(g.m, key)
|
delete(g.m, key)
|
||||||
g.mu.Unlock()
|
g.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user