Source file src/sync/oncefunc_test.go

     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  
     5  package sync_test
     6  
     7  import (
     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  
    20  func 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  
    32  func 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  
    51  func 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  
    70  func 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  
    78  func 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  
   101  func 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  
   110  func 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  
   119  func 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  
   128  func 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  
   143  func 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  
   166  func 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  
   184  func onceFuncPanic() {
   185  	panic("x")
   186  }
   187  
   188  func 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.
   227  func gcwaitfin() {
   228  	runtime.GC()
   229  	runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64)
   230  }
   231  
   232  //go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue
   233  func runtime_blockUntilEmptyFinalizerQueue(int64) bool
   234  
   235  var (
   236  	onceFunc = sync.OnceFunc(func() {})
   237  
   238  	onceFuncOnce sync.Once
   239  )
   240  
   241  func doOnceFunc() {
   242  	onceFuncOnce.Do(func() {})
   243  }
   244  
   245  func 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  
   274  var (
   275  	onceValue = sync.OnceValue(func() int { return 42 })
   276  
   277  	onceValueOnce  sync.Once
   278  	onceValueValue int
   279  )
   280  
   281  func doOnceValue() int {
   282  	onceValueOnce.Do(func() {
   283  		onceValueValue = 42
   284  	})
   285  	return onceValueValue
   286  }
   287  
   288  func 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  

View as plain text