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  	of := func() { calls++ }
    23  	f := sync.OnceFunc(of)
    24  	allocs := testing.AllocsPerRun(10, f)
    25  	if calls != 1 {
    26  		t.Errorf("want calls==1, got %d", calls)
    27  	}
    28  	if allocs != 0 {
    29  		t.Errorf("want 0 allocations per call to f, got %v", allocs)
    30  	}
    31  	allocs = testing.AllocsPerRun(10, func() {
    32  		f = sync.OnceFunc(of)
    33  	})
    34  	if allocs > 2 {
    35  		t.Errorf("want at most 2 allocations per call to OnceFunc, got %v", allocs)
    36  	}
    37  }
    38  
    39  func TestOnceValue(t *testing.T) {
    40  	calls := 0
    41  	of := func() int {
    42  		calls++
    43  		return calls
    44  	}
    45  	f := sync.OnceValue(of)
    46  	allocs := testing.AllocsPerRun(10, func() { f() })
    47  	value := f()
    48  	if calls != 1 {
    49  		t.Errorf("want calls==1, got %d", calls)
    50  	}
    51  	if value != 1 {
    52  		t.Errorf("want value==1, got %d", value)
    53  	}
    54  	if allocs != 0 {
    55  		t.Errorf("want 0 allocations per call to f, got %v", allocs)
    56  	}
    57  	allocs = testing.AllocsPerRun(10, func() {
    58  		f = sync.OnceValue(of)
    59  	})
    60  	if allocs > 2 {
    61  		t.Errorf("want at most 2 allocations per call to OnceValue, got %v", allocs)
    62  	}
    63  }
    64  
    65  func TestOnceValues(t *testing.T) {
    66  	calls := 0
    67  	of := func() (int, int) {
    68  		calls++
    69  		return calls, calls + 1
    70  	}
    71  	f := sync.OnceValues(of)
    72  	allocs := testing.AllocsPerRun(10, func() { f() })
    73  	v1, v2 := f()
    74  	if calls != 1 {
    75  		t.Errorf("want calls==1, got %d", calls)
    76  	}
    77  	if v1 != 1 || v2 != 2 {
    78  		t.Errorf("want v1==1 and v2==2, got %d and %d", v1, v2)
    79  	}
    80  	if allocs != 0 {
    81  		t.Errorf("want 0 allocations per call to f, got %v", allocs)
    82  	}
    83  	allocs = testing.AllocsPerRun(10, func() {
    84  		f = sync.OnceValues(of)
    85  	})
    86  	if allocs > 2 {
    87  		t.Errorf("want at most 2 allocations per call to OnceValues, got %v", allocs)
    88  	}
    89  }
    90  
    91  func testOncePanicX(t *testing.T, calls *int, f func()) {
    92  	testOncePanicWith(t, calls, f, func(label string, p any) {
    93  		if p != "x" {
    94  			t.Fatalf("%s: want panic %v, got %v", label, "x", p)
    95  		}
    96  	})
    97  }
    98  
    99  func testOncePanicWith(t *testing.T, calls *int, f func(), check func(label string, p any)) {
   100  	// Check that the each call to f panics with the same value, but the
   101  	// underlying function is only called once.
   102  	for _, label := range []string{"first time", "second time"} {
   103  		var p any
   104  		panicked := true
   105  		func() {
   106  			defer func() {
   107  				p = recover()
   108  			}()
   109  			f()
   110  			panicked = false
   111  		}()
   112  		if !panicked {
   113  			t.Fatalf("%s: f did not panic", label)
   114  		}
   115  		check(label, p)
   116  	}
   117  	if *calls != 1 {
   118  		t.Errorf("want calls==1, got %d", *calls)
   119  	}
   120  }
   121  
   122  func TestOnceFuncPanic(t *testing.T) {
   123  	calls := 0
   124  	f := sync.OnceFunc(func() {
   125  		calls++
   126  		panic("x")
   127  	})
   128  	testOncePanicX(t, &calls, f)
   129  }
   130  
   131  func TestOnceValuePanic(t *testing.T) {
   132  	calls := 0
   133  	f := sync.OnceValue(func() int {
   134  		calls++
   135  		panic("x")
   136  	})
   137  	testOncePanicX(t, &calls, func() { f() })
   138  }
   139  
   140  func TestOnceValuesPanic(t *testing.T) {
   141  	calls := 0
   142  	f := sync.OnceValues(func() (int, int) {
   143  		calls++
   144  		panic("x")
   145  	})
   146  	testOncePanicX(t, &calls, func() { f() })
   147  }
   148  
   149  func TestOnceFuncPanicNil(t *testing.T) {
   150  	calls := 0
   151  	f := sync.OnceFunc(func() {
   152  		calls++
   153  		panic(nil)
   154  	})
   155  	testOncePanicWith(t, &calls, f, func(label string, p any) {
   156  		switch p.(type) {
   157  		case nil, *runtime.PanicNilError:
   158  			return
   159  		}
   160  		t.Fatalf("%s: want nil panic, got %v", label, p)
   161  	})
   162  }
   163  
   164  func TestOnceFuncGoexit(t *testing.T) {
   165  	// If f calls Goexit, the results are unspecified. But check that f doesn't
   166  	// get called twice.
   167  	calls := 0
   168  	f := sync.OnceFunc(func() {
   169  		calls++
   170  		runtime.Goexit()
   171  	})
   172  	var wg sync.WaitGroup
   173  	for i := 0; i < 2; i++ {
   174  		wg.Add(1)
   175  		go func() {
   176  			defer wg.Done()
   177  			defer func() { recover() }()
   178  			f()
   179  		}()
   180  		wg.Wait()
   181  	}
   182  	if calls != 1 {
   183  		t.Errorf("want calls==1, got %d", calls)
   184  	}
   185  }
   186  
   187  func TestOnceFuncPanicTraceback(t *testing.T) {
   188  	// Test that on the first invocation of a OnceFunc, the stack trace goes all
   189  	// the way to the origin of the panic.
   190  	f := sync.OnceFunc(onceFuncPanic)
   191  
   192  	defer func() {
   193  		if p := recover(); p != "x" {
   194  			t.Fatalf("want panic %v, got %v", "x", p)
   195  		}
   196  		stack := debug.Stack()
   197  		want := "sync_test.onceFuncPanic"
   198  		if !bytes.Contains(stack, []byte(want)) {
   199  			t.Fatalf("want stack containing %v, got:\n%s", want, string(stack))
   200  		}
   201  	}()
   202  	f()
   203  }
   204  
   205  func onceFuncPanic() {
   206  	panic("x")
   207  }
   208  
   209  func TestOnceXGC(t *testing.T) {
   210  	fns := map[string]func([]byte) func(){
   211  		"OnceFunc": func(buf []byte) func() {
   212  			return sync.OnceFunc(func() { buf[0] = 1 })
   213  		},
   214  		"OnceValue": func(buf []byte) func() {
   215  			f := sync.OnceValue(func() any { buf[0] = 1; return nil })
   216  			return func() { f() }
   217  		},
   218  		"OnceValues": func(buf []byte) func() {
   219  			f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil })
   220  			return func() { f() }
   221  		},
   222  		"OnceFunc panic": func(buf []byte) func() {
   223  			return sync.OnceFunc(func() { buf[0] = 1; panic("test panic") })
   224  		},
   225  		"OnceValue panic": func(buf []byte) func() {
   226  			f := sync.OnceValue(func() any { buf[0] = 1; panic("test panic") })
   227  			return func() { f() }
   228  		},
   229  		"OnceValues panic": func(buf []byte) func() {
   230  			f := sync.OnceValues(func() (any, any) { buf[0] = 1; panic("test panic") })
   231  			return func() { f() }
   232  		},
   233  	}
   234  	for n, fn := range fns {
   235  		t.Run(n, func(t *testing.T) {
   236  			buf := make([]byte, 1024)
   237  			var gc atomic.Bool
   238  			runtime.AddCleanup(&buf[0], func(g *atomic.Bool) { g.Store(true) }, &gc)
   239  			f := fn(buf)
   240  			gcwaitfin()
   241  			if gc.Load() != false {
   242  				t.Fatal("wrapped function garbage collected too early")
   243  			}
   244  			func() {
   245  				defer func() { recover() }()
   246  				f()
   247  			}()
   248  			gcwaitfin()
   249  			if gc.Load() != true {
   250  				// Even if f is still alive, the function passed to Once(Func|Value|Values)
   251  				// is not kept alive after the first call to f.
   252  				t.Fatal("wrapped function should be garbage collected, but still live")
   253  			}
   254  			func() {
   255  				defer func() { recover() }()
   256  				f()
   257  			}()
   258  		})
   259  	}
   260  }
   261  
   262  // gcwaitfin performs garbage collection and waits for all finalizers to run.
   263  func gcwaitfin() {
   264  	runtime.GC()
   265  	runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64)
   266  }
   267  
   268  //go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue
   269  func runtime_blockUntilEmptyFinalizerQueue(int64) bool
   270  
   271  var (
   272  	onceFunc = sync.OnceFunc(func() {})
   273  
   274  	onceFuncOnce sync.Once
   275  
   276  	onceFuncFunc func()
   277  )
   278  
   279  func doOnceFunc() {
   280  	onceFuncOnce.Do(func() {})
   281  }
   282  
   283  func BenchmarkOnceFunc(b *testing.B) {
   284  	b.Run("v=Once", func(b *testing.B) {
   285  		b.ReportAllocs()
   286  		for i := 0; i < b.N; i++ {
   287  			// The baseline is direct use of sync.Once.
   288  			doOnceFunc()
   289  		}
   290  	})
   291  	b.Run("v=Global", func(b *testing.B) {
   292  		b.ReportAllocs()
   293  		for i := 0; i < b.N; i++ {
   294  			// As of 3/2023, the compiler doesn't recognize that onceFunc is
   295  			// never mutated and is a closure that could be inlined.
   296  			// Too bad, because this is how OnceFunc will usually be used.
   297  			onceFunc()
   298  		}
   299  	})
   300  	b.Run("v=Local", func(b *testing.B) {
   301  		b.ReportAllocs()
   302  		// As of 3/2023, the compiler *does* recognize this local binding as an
   303  		// inlinable closure. This is the best case for OnceFunc, but probably
   304  		// not typical usage.
   305  		f := sync.OnceFunc(func() {})
   306  		for i := 0; i < b.N; i++ {
   307  			f()
   308  		}
   309  	})
   310  	b.Run("v=Make", func(b *testing.B) {
   311  		b.ReportAllocs()
   312  		for i := 0; i < b.N; i++ {
   313  			onceFuncFunc = sync.OnceFunc(func() {})
   314  		}
   315  	})
   316  }
   317  
   318  var (
   319  	onceValue = sync.OnceValue(func() int { return 42 })
   320  
   321  	onceValueOnce  sync.Once
   322  	onceValueValue int
   323  
   324  	onceValueFunc func() int
   325  )
   326  
   327  func doOnceValue() int {
   328  	onceValueOnce.Do(func() {
   329  		onceValueValue = 42
   330  	})
   331  	return onceValueValue
   332  }
   333  
   334  func BenchmarkOnceValue(b *testing.B) {
   335  	// See BenchmarkOnceFunc
   336  	b.Run("v=Once", func(b *testing.B) {
   337  		b.ReportAllocs()
   338  		for i := 0; i < b.N; i++ {
   339  			if want, got := 42, doOnceValue(); want != got {
   340  				b.Fatalf("want %d, got %d", want, got)
   341  			}
   342  		}
   343  	})
   344  	b.Run("v=Global", func(b *testing.B) {
   345  		b.ReportAllocs()
   346  		for i := 0; i < b.N; i++ {
   347  			if want, got := 42, onceValue(); want != got {
   348  				b.Fatalf("want %d, got %d", want, got)
   349  			}
   350  		}
   351  	})
   352  	b.Run("v=Local", func(b *testing.B) {
   353  		b.ReportAllocs()
   354  		onceValue := sync.OnceValue(func() int { return 42 })
   355  		for i := 0; i < b.N; i++ {
   356  			if want, got := 42, onceValue(); want != got {
   357  				b.Fatalf("want %d, got %d", want, got)
   358  			}
   359  		}
   360  	})
   361  	b.Run("v=Make", func(b *testing.B) {
   362  		b.ReportAllocs()
   363  		for i := 0; i < b.N; i++ {
   364  			onceValueFunc = sync.OnceValue(func() int { return 42 })
   365  		}
   366  	})
   367  }
   368  
   369  const (
   370  	onceValuesWant1 = 42
   371  	onceValuesWant2 = true
   372  )
   373  
   374  var (
   375  	onceValues = sync.OnceValues(func() (int, bool) {
   376  		return onceValuesWant1, onceValuesWant2
   377  	})
   378  
   379  	onceValuesOnce   sync.Once
   380  	onceValuesValue1 int
   381  	onceValuesValue2 bool
   382  
   383  	onceValuesFunc func() (int, bool)
   384  )
   385  
   386  func doOnceValues() (int, bool) {
   387  	onceValuesOnce.Do(func() {
   388  		onceValuesValue1 = onceValuesWant1
   389  		onceValuesValue2 = onceValuesWant2
   390  	})
   391  	return onceValuesValue1, onceValuesValue2
   392  }
   393  
   394  func BenchmarkOnceValues(b *testing.B) {
   395  	// See BenchmarkOnceFunc
   396  	b.Run("v=Once", func(b *testing.B) {
   397  		b.ReportAllocs()
   398  		for i := 0; i < b.N; i++ {
   399  			if got1, got2 := doOnceValues(); got1 != onceValuesWant1 {
   400  				b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
   401  			} else if got2 != onceValuesWant2 {
   402  				b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
   403  			}
   404  		}
   405  	})
   406  	b.Run("v=Global", func(b *testing.B) {
   407  		b.ReportAllocs()
   408  		for i := 0; i < b.N; i++ {
   409  			if got1, got2 := onceValues(); got1 != onceValuesWant1 {
   410  				b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
   411  			} else if got2 != onceValuesWant2 {
   412  				b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
   413  			}
   414  		}
   415  	})
   416  	b.Run("v=Local", func(b *testing.B) {
   417  		b.ReportAllocs()
   418  		onceValues := sync.OnceValues(func() (int, bool) {
   419  			return onceValuesWant1, onceValuesWant2
   420  		})
   421  		for i := 0; i < b.N; i++ {
   422  			if got1, got2 := onceValues(); got1 != onceValuesWant1 {
   423  				b.Fatalf("value 1: got %d, want %d", got1, onceValuesWant1)
   424  			} else if got2 != onceValuesWant2 {
   425  				b.Fatalf("value 2: got %v, want %v", got2, onceValuesWant2)
   426  			}
   427  		}
   428  	})
   429  	b.Run("v=Make", func(b *testing.B) {
   430  		b.ReportAllocs()
   431  		for i := 0; i < b.N; i++ {
   432  			onceValuesFunc = sync.OnceValues(func() (int, bool) {
   433  				return onceValuesWant1, onceValuesWant2
   434  			})
   435  		}
   436  	})
   437  }
   438  

View as plain text