1// Copyright 2022 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package sync_test 6 7import ( 8 "bytes" 9 "math" 10 "runtime" 11 "runtime/debug" 12 "sync" 13 "sync/atomic" 14 "testing" 15 _ "unsafe" 16) 17 18// We assume that the Once.Do tests have already covered parallelism. 19 20func TestOnceFunc(t *testing.T) { 21 calls := 0 22 f := sync.OnceFunc(func() { calls++ }) 23 allocs := testing.AllocsPerRun(10, f) 24 if calls != 1 { 25 t.Errorf("want calls==1, got %d", calls) 26 } 27 if allocs != 0 { 28 t.Errorf("want 0 allocations per call, got %v", allocs) 29 } 30} 31 32func TestOnceValue(t *testing.T) { 33 calls := 0 34 f := sync.OnceValue(func() int { 35 calls++ 36 return calls 37 }) 38 allocs := testing.AllocsPerRun(10, func() { f() }) 39 value := f() 40 if calls != 1 { 41 t.Errorf("want calls==1, got %d", calls) 42 } 43 if value != 1 { 44 t.Errorf("want value==1, got %d", value) 45 } 46 if allocs != 0 { 47 t.Errorf("want 0 allocations per call, got %v", allocs) 48 } 49} 50 51func TestOnceValues(t *testing.T) { 52 calls := 0 53 f := sync.OnceValues(func() (int, int) { 54 calls++ 55 return calls, calls + 1 56 }) 57 allocs := testing.AllocsPerRun(10, func() { f() }) 58 v1, v2 := f() 59 if calls != 1 { 60 t.Errorf("want calls==1, got %d", calls) 61 } 62 if v1 != 1 || v2 != 2 { 63 t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2) 64 } 65 if allocs != 0 { 66 t.Errorf("want 0 allocations per call, got %v", allocs) 67 } 68} 69 70func testOncePanicX(t *testing.T, calls *int, f func()) { 71 testOncePanicWith(t, calls, f, func(label string, p any) { 72 if p != "x" { 73 t.Fatalf("%s: want panic %v, got %v", label, "x", p) 74 } 75 }) 76} 77 78func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) { 79 // Check that the each call to f panics with the same value, but the 80 // underlying function is only called once. 81 for _, label := range []string{"first time", "second time"} { 82 var p any 83 panicked := true 84 func() { 85 defer func() { 86 p = recover() 87 }() 88 f() 89 panicked = false 90 }() 91 if !panicked { 92 t.Fatalf("%s: f did not panic", label) 93 } 94 check(label, p) 95 } 96 if *calls != 1 { 97 t.Errorf("want calls==1, got %d", *calls) 98 } 99} 100 101func TestOnceFuncPanic(t *testing.T) { 102 calls := 0 103 f := sync.OnceFunc(func() { 104 calls++ 105 panic("x") 106 }) 107 testOncePanicX(t, &calls, f) 108} 109 110func TestOnceValuePanic(t *testing.T) { 111 calls := 0 112 f := sync.OnceValue(func() int { 113 calls++ 114 panic("x") 115 }) 116 testOncePanicX(t, &calls, func() { f() }) 117} 118 119func TestOnceValuesPanic(t *testing.T) { 120 calls := 0 121 f := sync.OnceValues(func() (int, int) { 122 calls++ 123 panic("x") 124 }) 125 testOncePanicX(t, &calls, func() { f() }) 126} 127 128func TestOnceFuncPanicNil(t *testing.T) { 129 calls := 0 130 f := sync.OnceFunc(func() { 131 calls++ 132 panic(nil) 133 }) 134 testOncePanicWith(t, &calls, f, func(label string, p any) { 135 switch p.(type) { 136 case nil, *runtime.PanicNilError: 137 return 138 } 139 t.Fatalf("%s: want nil panic, got %v", label, p) 140 }) 141} 142 143func TestOnceFuncGoexit(t *testing.T) { 144 // If f calls Goexit, the results are unspecified. But check that f doesn't 145 // get called twice. 146 calls := 0 147 f := sync.OnceFunc(func() { 148 calls++ 149 runtime.Goexit() 150 }) 151 var wg sync.WaitGroup 152 for i := 0; i < 2; i++ { 153 wg.Add(1) 154 go func() { 155 defer wg.Done() 156 defer func() { recover() }() 157 f() 158 }() 159 wg.Wait() 160 } 161 if calls != 1 { 162 t.Errorf("want calls==1, got %d", calls) 163 } 164} 165 166func TestOnceFuncPanicTraceback(t *testing.T) { 167 // Test that on the first invocation of a OnceFunc, the stack trace goes all 168 // the way to the origin of the panic. 169 f := sync.OnceFunc(onceFuncPanic) 170 171 defer func() { 172 if p := recover(); p != "x" { 173 t.Fatalf("want panic %v, got %v", "x", p) 174 } 175 stack := debug.Stack() 176 want := "sync_test.onceFuncPanic" 177 if !bytes.Contains(stack, []byte(want)) { 178 t.Fatalf("want stack containing %v, got:\n%s", want, string(stack)) 179 } 180 }() 181 f() 182} 183 184func onceFuncPanic() { 185 panic("x") 186} 187 188func TestOnceXGC(t *testing.T) { 189 fns := map[string]func([]byte) func(){ 190 "OnceFunc": func(buf []byte) func() { 191 return sync.OnceFunc(func() { buf[0] = 1 }) 192 }, 193 "OnceValue": func(buf []byte) func() { 194 f := sync.OnceValue(func() any { buf[0] = 1; return nil }) 195 return func() { f() } 196 }, 197 "OnceValues": func(buf []byte) func() { 198 f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil }) 199 return func() { f() } 200 }, 201 } 202 for n, fn := range fns { 203 t.Run(n, func(t *testing.T) { 204 buf := make([]byte, 1024) 205 var gc atomic.Bool 206 runtime.SetFinalizer(&buf[0], func(_ *byte) { 207 gc.Store(true) 208 }) 209 f := fn(buf) 210 gcwaitfin() 211 if gc.Load() != false { 212 t.Fatal("wrapped function garbage collected too early") 213 } 214 f() 215 gcwaitfin() 216 if gc.Load() != true { 217 // Even if f is still alive, the function passed to Once(Func|Value|Values) 218 // is not kept alive after the first call to f. 219 t.Fatal("wrapped function should be garbage collected, but still live") 220 } 221 f() 222 }) 223 } 224} 225 226// gcwaitfin performs garbage collection and waits for all finalizers to run. 227func gcwaitfin() { 228 runtime.GC() 229 runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64) 230} 231 232//go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue 233func runtime_blockUntilEmptyFinalizerQueue(int64) bool 234 235var ( 236 onceFunc = sync.OnceFunc(func() {}) 237 238 onceFuncOnce sync.Once 239) 240 241func doOnceFunc() { 242 onceFuncOnce.Do(func() {}) 243} 244 245func BenchmarkOnceFunc(b *testing.B) { 246 b.Run("v=Once", func(b *testing.B) { 247 b.ReportAllocs() 248 for i := 0; i < b.N; i++ { 249 // The baseline is direct use of sync.Once. 250 doOnceFunc() 251 } 252 }) 253 b.Run("v=Global", func(b *testing.B) { 254 b.ReportAllocs() 255 for i := 0; i < b.N; i++ { 256 // As of 3/2023, the compiler doesn't recognize that onceFunc is 257 // never mutated and is a closure that could be inlined. 258 // Too bad, because this is how OnceFunc will usually be used. 259 onceFunc() 260 } 261 }) 262 b.Run("v=Local", func(b *testing.B) { 263 b.ReportAllocs() 264 // As of 3/2023, the compiler *does* recognize this local binding as an 265 // inlinable closure. This is the best case for OnceFunc, but probably 266 // not typical usage. 267 f := sync.OnceFunc(func() {}) 268 for i := 0; i < b.N; i++ { 269 f() 270 } 271 }) 272} 273 274var ( 275 onceValue = sync.OnceValue(func() int { return 42 }) 276 277 onceValueOnce sync.Once 278 onceValueValue int 279) 280 281func doOnceValue() int { 282 onceValueOnce.Do(func() { 283 onceValueValue = 42 284 }) 285 return onceValueValue 286} 287 288func BenchmarkOnceValue(b *testing.B) { 289 // See BenchmarkOnceFunc 290 b.Run("v=Once", func(b *testing.B) { 291 b.ReportAllocs() 292 for i := 0; i < b.N; i++ { 293 if want, got := 42, doOnceValue(); want != got { 294 b.Fatalf("want %d, got %d", want, got) 295 } 296 } 297 }) 298 b.Run("v=Global", func(b *testing.B) { 299 b.ReportAllocs() 300 for i := 0; i < b.N; i++ { 301 if want, got := 42, onceValue(); want != got { 302 b.Fatalf("want %d, got %d", want, got) 303 } 304 } 305 }) 306 b.Run("v=Local", func(b *testing.B) { 307 b.ReportAllocs() 308 onceValue := sync.OnceValue(func() int { return 42 }) 309 for i := 0; i < b.N; i++ { 310 if want, got := 42, onceValue(); want != got { 311 b.Fatalf("want %d, got %d", want, got) 312 } 313 } 314 }) 315} 316