gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sync/gate_test.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // +checkalignedignore 16 package sync 17 18 import ( 19 "context" 20 "runtime" 21 "sync/atomic" 22 "testing" 23 "time" 24 ) 25 26 func TestGateBasic(t *testing.T) { 27 var g Gate 28 29 if !g.Enter() { 30 t.Fatalf("Enter failed before Close") 31 } 32 g.Leave() 33 34 g.Close() 35 if g.Enter() { 36 t.Fatalf("Enter succeeded after Close") 37 } 38 } 39 40 func TestGateConcurrent(t *testing.T) { 41 // Each call to testGateConcurrentOnce tests behavior around a single call 42 // to Gate.Close, so run many short tests to increase the probability of 43 // flushing out any issues. 44 totalTime := 5 * time.Second 45 timePerTest := 20 * time.Millisecond 46 numTests := int(totalTime / timePerTest) 47 for i := 0; i < numTests; i++ { 48 testGateConcurrentOnce(t, timePerTest) 49 } 50 } 51 52 func testGateConcurrentOnce(t *testing.T, d time.Duration) { 53 const numGoroutines = 1000 54 55 ctx, cancel := context.WithCancel(context.Background()) 56 var wg WaitGroup 57 defer func() { 58 cancel() 59 wg.Wait() 60 }() 61 62 var g Gate 63 closeState := int32(0) // set to 1 before g.Close() and 2 after it returns 64 65 // Start a large number of goroutines that repeatedly attempt to enter the 66 // gate and get the expected result. 67 for i := 0; i < numGoroutines; i++ { 68 wg.Add(1) 69 go func() { 70 defer wg.Done() 71 for ctx.Err() == nil { 72 closedBeforeEnter := atomic.LoadInt32(&closeState) == 2 73 if g.Enter() { 74 closedBeforeLeave := atomic.LoadInt32(&closeState) == 2 75 g.Leave() 76 if closedBeforeEnter { 77 t.Errorf("Enter succeeded after Close") 78 return 79 } 80 if closedBeforeLeave { 81 t.Errorf("Close returned before Leave") 82 return 83 } 84 } else { 85 if atomic.LoadInt32(&closeState) == 0 { 86 t.Errorf("Enter failed before Close") 87 return 88 } 89 } 90 // Go does not preempt busy loops until Go 1.14. 91 runtime.Gosched() 92 } 93 }() 94 } 95 96 // Allow goroutines to enter the gate successfully for half of the test's 97 // duration, then close the gate and allow goroutines to fail to enter the 98 // gate for the remaining half. 99 time.Sleep(d / 2) 100 atomic.StoreInt32(&closeState, 1) 101 g.Close() 102 atomic.StoreInt32(&closeState, 2) 103 time.Sleep(d / 2) 104 } 105 106 func BenchmarkGateEnterLeave(b *testing.B) { 107 var g Gate 108 for i := 0; i < b.N; i++ { 109 g.Enter() 110 g.Leave() 111 } 112 } 113 114 func BenchmarkGateClose(b *testing.B) { 115 for i := 0; i < b.N; i++ { 116 var g Gate 117 g.Close() 118 } 119 } 120 121 func BenchmarkGateEnterLeaveAsyncClose(b *testing.B) { 122 for i := 0; i < b.N; i++ { 123 var g Gate 124 g.Enter() 125 go func() { 126 g.Leave() 127 }() 128 g.Close() 129 } 130 }