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