| | |
| | |
| | |
| |
|
| | package sync_test |
| |
|
| | import ( |
| | "bytes" |
| | "math" |
| | "runtime" |
| | "runtime/debug" |
| | "sync" |
| | "sync/atomic" |
| | "testing" |
| | _ "unsafe" |
| | ) |
| |
|
| | |
| |
|
| | func TestOnceFunc(t *testing.T) { |
| | calls := 0 |
| | of := func() { calls++ } |
| | f := sync.OnceFunc(of) |
| | allocs := testing.AllocsPerRun(10, f) |
| | if calls != 1 { |
| | t.Errorf("want calls==1, got %d", calls) |
| | } |
| | if allocs != 0 { |
| | t.Errorf("want 0 allocations per call to f, got %v", allocs) |
| | } |
| | allocs = testing.AllocsPerRun(10, func() { |
| | f = sync.OnceFunc(of) |
| | }) |
| | if allocs > 2 { |
| | t.Errorf("want at most 2 allocations per call to OnceFunc, got %v", allocs) |
| | } |
| | } |
| |
|
| | func TestOnceValue(t *testing.T) { |
| | calls := 0 |
| | of := func() int { |
| | calls++ |
| | return calls |
| | } |
| | f := sync.OnceValue(of) |
| | allocs := testing.AllocsPerRun(10, func() { f() }) |
| | value := f() |
| | if calls != 1 { |
| | t.Errorf("want calls==1, got %d", calls) |
| | } |
| | if value != 1 { |
| | t.Errorf("want value==1, got %d", value) |
| | } |
| | if allocs != 0 { |
| | t.Errorf("want 0 allocations per call to f, got %v", allocs) |
| | } |
| | allocs = testing.AllocsPerRun(10, func() { |
| | f = sync.OnceValue(of) |
| | }) |
| | if allocs > 2 { |
| | t.Errorf("want at most 2 allocations per call to OnceValue, got %v", allocs) |
| | } |
| | } |
| |
|
| | func TestOnceValues(t *testing.T) { |
| | calls := 0 |
| | of := func() (int, int) { |
| | calls++ |
| | return calls, calls + 1 |
| | } |
| | f := sync.OnceValues(of) |
| | allocs := testing.AllocsPerRun(10, func() { f() }) |
| | v1, v2 := f() |
| | if calls != 1 { |
| | t.Errorf("want calls==1, got %d", calls) |
| | } |
| | if v1 != 1 || v2 != 2 { |
| | t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) |
| | } |
| | if allocs != 0 { |
| | t.Errorf("want 0 allocations per call to f, got %v", allocs) |
| | } |
| | allocs = testing.AllocsPerRun(10, func() { |
| | f = sync.OnceValues(of) |
| | }) |
| | if allocs > 2 { |
| | t.Errorf("want at most 2 allocations per call to OnceValues, got %v", allocs) |
| | } |
| | } |
| |
|
| | func testOncePanicX(t *testing.T, calls *int, f func()) { |
| | testOncePanicWith(t, calls, f, func(label string, p any) { |
| | if p != "x" { |
| | t.Fatalf("%s: want panic %v, got %v", label, "x", p) |
| | } |
| | }) |
| | } |
| |
|
| | func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { |
| | |
| | |
| | for _, label := range []string{"first time", "second time"} { |
| | var p any |
| | panicked := true |
| | func() { |
| | defer func() { |
| | p = recover() |
| | }() |
| | f() |
| | panicked = false |
| | }() |
| | if !panicked { |
| | t.Fatalf("%s: f did not panic", label) |
| | } |
| | check(label, p) |
| | } |
| | if *calls != 1 { |
| | t.Errorf("want calls==1, got %d", *calls) |
| | } |
| | } |
| |
|
| | func TestOnceFuncPanic(t *testing.T) { |
| | calls := 0 |
| | f := sync.OnceFunc(func() { |
| | calls++ |
| | panic("x") |
| | }) |
| | testOncePanicX(t, &calls, f) |
| | } |
| |
|
| | func TestOnceValuePanic(t *testing.T) { |
| | calls := 0 |
| | f := sync.OnceValue(func() int { |
| | calls++ |
| | panic("x") |
| | }) |
| | testOncePanicX(t, &calls, func() { f() }) |
| | } |
| |
|
| | func TestOnceValuesPanic(t *testing.T) { |
| | calls := 0 |
| | f := sync.OnceValues(func() (int, int) { |
| | calls++ |
| | panic("x") |
| | }) |
| | testOncePanicX(t, &calls, func() { f() }) |
| | } |
| |
|
| | func TestOnceFuncPanicNil(t *testing.T) { |
| | calls := 0 |
| | f := sync.OnceFunc(func() { |
| | calls++ |
| | panic(nil) |
| | }) |
| | testOncePanicWith(t, &calls, f, func(label string, p any) { |
| | switch p.(type) { |
| | case nil, *runtime.PanicNilError: |
| | return |
| | } |
| | t.Fatalf("%s: want nil panic, got %v", label, p) |
| | }) |
| | } |
| |
|
| | func TestOnceFuncGoexit(t *testing.T) { |
| | |
| | |
| | calls := 0 |
| | f := sync.OnceFunc(func() { |
| | calls++ |
| | runtime.Goexit() |
| | }) |
| | var wg sync.WaitGroup |
| | for i := 0; i < 2; i++ { |
| | wg.Add(1) |
| | go func() { |
| | defer wg.Done() |
| | defer func() { recover() }() |
| | f() |
| | }() |
| | wg.Wait() |
| | } |
| | if calls != 1 { |
| | t.Errorf("want calls==1, got %d", calls) |
| | } |
| | } |
| |
|
| | func TestOnceFuncPanicTraceback(t *testing.T) { |
| | |
| | |
| | f := sync.OnceFunc(onceFuncPanic) |
| |
|
| | defer func() { |
| | if p := recover(); p != "x" { |
| | t.Fatalf("want panic %v, got %v", "x", p) |
| | } |
| | stack := debug.Stack() |
| | want := "sync_test.onceFuncPanic" |
| | if !bytes.Contains(stack, []byte(want)) { |
| | t.Fatalf("want stack containing %v, got:\n%s", want, string(stack)) |
| | } |
| | }() |
| | f() |
| | } |
| |
|
| | func onceFuncPanic() { |
| | panic("x") |
| | } |
| |
|
| | func TestOnceXGC(t *testing.T) { |
| | fns := map[string]func([]byte) func(){ |
| | "OnceFunc": func(buf []byte) func() { |
| | return sync.OnceFunc(func() { buf[0] = 1 }) |
| | }, |
| | "OnceValue": func(buf []byte) func() { |
| | f := sync.OnceValue(func() any { buf[0] = 1; return nil }) |
| | return func() { f() } |
| | }, |
| | "OnceValues": func(buf []byte) func() { |
| | f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil }) |
| | return func() { f() } |
| | }, |
| | "OnceFunc panic": func(buf []byte) func() { |
| | return sync.OnceFunc(func() { buf[0] = 1; panic("test panic") }) |
| | }, |
| | "OnceValue panic": func(buf []byte) func() { |
| | f := sync.OnceValue(func() any { buf[0] = 1; panic("test panic") }) |
| | return func() { f() } |
| | }, |
| | "OnceValues panic": func(buf []byte) func() { |
| | f := sync.OnceValues(func() (any, any) { buf[0] = 1; panic("test panic") }) |
| | return func() { f() } |
| | }, |
| | } |
| | for n, fn := range fns { |
| | t.Run(n, func(t *testing.T) { |
| | buf := make([]byte, 1024) |
| | var gc atomic.Bool |
| | runtime.AddCleanup(&buf[0], func(g *atomic.Bool) { g.Store(true) }, &gc) |
| | f := fn(buf) |
| | runCleanups() |
| | if gc.Load() != false { |
| | t.Fatal("wrapped function garbage collected too early") |
| | } |
| | func() { |
| | defer func() { recover() }() |
| | f() |
| | }() |
| | runCleanups() |
| | if gc.Load() != true { |
| | |
| | |
| | t.Fatal("wrapped function should be garbage collected, but still live") |
| | } |
| | func() { |
| | defer func() { recover() }() |
| | f() |
| | }() |
| | }) |
| | } |
| | } |
| |
|
| | |
| | func runCleanups() { |
| | runtime.GC() |
| | runtime_blockUntilEmptyCleanupQueue(math.MaxInt64) |
| | } |
| |
|
| | |
| | func runtime_blockUntilEmptyCleanupQueue(int64) bool |
| |
|
| | var ( |
| | onceFunc = sync.OnceFunc(func() {}) |
| |
|
| | onceFuncOnce sync.Once |
| |
|
| | onceFuncFunc func() |
| | ) |
| |
|
| | func doOnceFunc() { |
| | onceFuncOnce.Do(func() {}) |
| | } |
| |
|
| | func BenchmarkOnceFunc(b *testing.B) { |
| | b.Run("v=Once", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | |
| | doOnceFunc() |
| | } |
| | }) |
| | b.Run("v=Global", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | |
| | |
| | |
| | onceFunc() |
| | } |
| | }) |
| | b.Run("v=Local", func(b *testing.B) { |
| | b.ReportAllocs() |
| | |
| | |
| | |
| | f := sync.OnceFunc(func() {}) |
| | for i := 0; i < b.N; i++ { |
| | f() |
| | } |
| | }) |
| | b.Run("v=Make", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | onceFuncFunc = sync.OnceFunc(func() {}) |
| | } |
| | }) |
| | } |
| |
|
| | var ( |
| | onceValue = sync.OnceValue(func() int { return 42 }) |
| |
|
| | onceValueOnce sync.Once |
| | onceValueValue int |
| |
|
| | onceValueFunc func() int |
| | ) |
| |
|
| | func doOnceValue() int { |
| | onceValueOnce.Do(func() { |
| | onceValueValue = 42 |
| | }) |
| | return onceValueValue |
| | } |
| |
|
| | func BenchmarkOnceValue(b *testing.B) { |
| | |
| | b.Run("v=Once", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | if want, got := 42, doOnceValue(); want != got { |
| | b.Fatalf("want %d, got %d", want, got) |
| | } |
| | } |
| | }) |
| | b.Run("v=Global", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | if want, got := 42, onceValue(); want != got { |
| | b.Fatalf("want %d, got %d", want, got) |
| | } |
| | } |
| | }) |
| | b.Run("v=Local", func(b *testing.B) { |
| | b.ReportAllocs() |
| | onceValue := sync.OnceValue(func() int { return 42 }) |
| | for i := 0; i < b.N; i++ { |
| | if want, got := 42, onceValue(); want != got { |
| | b.Fatalf("want %d, got %d", want, got) |
| | } |
| | } |
| | }) |
| | b.Run("v=Make", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | onceValueFunc = sync.OnceValue(func() int { return 42 }) |
| | } |
| | }) |
| | } |
| |
|
| | const ( |
| | onceValuesWant1 = 42 |
| | onceValuesWant2 = true |
| | ) |
| |
|
| | var ( |
| | onceValues = sync.OnceValues(func() (int, bool) { |
| | return onceValuesWant1, onceValuesWant2 |
| | }) |
| |
|
| | onceValuesOnce sync.Once |
| | onceValuesValue1 int |
| | onceValuesValue2 bool |
| |
|
| | onceValuesFunc func() (int, bool) |
| | ) |
| |
|
| | func doOnceValues() (int, bool) { |
| | onceValuesOnce.Do(func() { |
| | onceValuesValue1 = onceValuesWant1 |
| | onceValuesValue2 = onceValuesWant2 |
| | }) |
| | return onceValuesValue1, onceValuesValue2 |
| | } |
| |
|
| | func BenchmarkOnceValues(b *testing.B) { |
| | |
| | b.Run("v=Once", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | if got1, got2 := doOnceValues(); got1 != onceValuesWant1 { |
| | b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1) |
| | } else if got2 != onceValuesWant2 { |
| | b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2) |
| | } |
| | } |
| | }) |
| | b.Run("v=Global", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | if got1, got2 := onceValues(); got1 != onceValuesWant1 { |
| | b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1) |
| | } else if got2 != onceValuesWant2 { |
| | b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2) |
| | } |
| | } |
| | }) |
| | b.Run("v=Local", func(b *testing.B) { |
| | b.ReportAllocs() |
| | onceValues := sync.OnceValues(func() (int, bool) { |
| | return onceValuesWant1, onceValuesWant2 |
| | }) |
| | for i := 0; i < b.N; i++ { |
| | if got1, got2 := onceValues(); got1 != onceValuesWant1 { |
| | b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1) |
| | } else if got2 != onceValuesWant2 { |
| | b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2) |
| | } |
| | } |
| | }) |
| | b.Run("v=Make", func(b *testing.B) { |
| | b.ReportAllocs() |
| | for i := 0; i < b.N; i++ { |
| | onceValuesFunc = sync.OnceValues(func() (int, bool) { |
| | return onceValuesWant1, onceValuesWant2 |
| | }) |
| | } |
| | }) |
| | } |
| |
|