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
6
7// OnceFunc returns a function that invokes f only once. The returned function
8// may be called concurrently.
9//
10// If f panics, the returned function will panic with the same value on every call.
11func OnceFunc(f func()) func() {
12	var (
13		once  Once
14		valid bool
15		p     any
16	)
17	// Construct the inner closure just once to reduce costs on the fast path.
18	g := func() {
19		defer func() {
20			p = recover()
21			if !valid {
22				// Re-panic immediately so on the first call the user gets a
23				// complete stack trace into f.
24				panic(p)
25			}
26		}()
27		f()
28		f = nil      // Do not keep f alive after invoking it.
29		valid = true // Set only if f does not panic.
30	}
31	return func() {
32		once.Do(g)
33		if !valid {
34			panic(p)
35		}
36	}
37}
38
39// OnceValue returns a function that invokes f only once and returns the value
40// returned by f. The returned function may be called concurrently.
41//
42// If f panics, the returned function will panic with the same value on every call.
43func OnceValue[T any](f func() T) func() T {
44	var (
45		once   Once
46		valid  bool
47		p      any
48		result T
49	)
50	g := func() {
51		defer func() {
52			p = recover()
53			if !valid {
54				panic(p)
55			}
56		}()
57		result = f()
58		f = nil
59		valid = true
60	}
61	return func() T {
62		once.Do(g)
63		if !valid {
64			panic(p)
65		}
66		return result
67	}
68}
69
70// OnceValues returns a function that invokes f only once and returns the values
71// returned by f. The returned function may be called concurrently.
72//
73// If f panics, the returned function will panic with the same value on every call.
74func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) {
75	var (
76		once  Once
77		valid bool
78		p     any
79		r1    T1
80		r2    T2
81	)
82	g := func() {
83		defer func() {
84			p = recover()
85			if !valid {
86				panic(p)
87			}
88		}()
89		r1, r2 = f()
90		f = nil
91		valid = true
92	}
93	return func() (T1, T2) {
94		once.Do(g)
95		if !valid {
96			panic(p)
97		}
98		return r1, r2
99	}
100}
101