summaryrefslogtreecommitdiffhomepage
path: root/pkg/sync/gate_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/sync/gate_test.go')
-rw-r--r--pkg/sync/gate_test.go129
1 files changed, 129 insertions, 0 deletions
diff --git a/pkg/sync/gate_test.go b/pkg/sync/gate_test.go
new file mode 100644
index 000000000..82ce02b97
--- /dev/null
+++ b/pkg/sync/gate_test.go
@@ -0,0 +1,129 @@
+// Copyright 2018 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sync
+
+import (
+ "context"
+ "runtime"
+ "sync/atomic"
+ "testing"
+ "time"
+)
+
+func TestGateBasic(t *testing.T) {
+ var g Gate
+
+ if !g.Enter() {
+ t.Fatalf("Enter failed before Close")
+ }
+ g.Leave()
+
+ g.Close()
+ if g.Enter() {
+ t.Fatalf("Enter succeeded after Close")
+ }
+}
+
+func TestGateConcurrent(t *testing.T) {
+ // Each call to testGateConcurrentOnce tests behavior around a single call
+ // to Gate.Close, so run many short tests to increase the probability of
+ // flushing out any issues.
+ totalTime := 5 * time.Second
+ timePerTest := 20 * time.Millisecond
+ numTests := int(totalTime / timePerTest)
+ for i := 0; i < numTests; i++ {
+ testGateConcurrentOnce(t, timePerTest)
+ }
+}
+
+func testGateConcurrentOnce(t *testing.T, d time.Duration) {
+ const numGoroutines = 1000
+
+ ctx, cancel := context.WithCancel(context.Background())
+ var wg WaitGroup
+ defer func() {
+ cancel()
+ wg.Wait()
+ }()
+
+ var g Gate
+ closeState := int32(0) // set to 1 before g.Close() and 2 after it returns
+
+ // Start a large number of goroutines that repeatedly attempt to enter the
+ // gate and get the expected result.
+ for i := 0; i < numGoroutines; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for ctx.Err() == nil {
+ closedBeforeEnter := atomic.LoadInt32(&closeState) == 2
+ if g.Enter() {
+ closedBeforeLeave := atomic.LoadInt32(&closeState) == 2
+ g.Leave()
+ if closedBeforeEnter {
+ t.Errorf("Enter succeeded after Close")
+ return
+ }
+ if closedBeforeLeave {
+ t.Errorf("Close returned before Leave")
+ return
+ }
+ } else {
+ if atomic.LoadInt32(&closeState) == 0 {
+ t.Errorf("Enter failed before Close")
+ return
+ }
+ }
+ // Go does not preempt busy loops until Go 1.14.
+ runtime.Gosched()
+ }
+ }()
+ }
+
+ // Allow goroutines to enter the gate successfully for half of the test's
+ // duration, then close the gate and allow goroutines to fail to enter the
+ // gate for the remaining half.
+ time.Sleep(d / 2)
+ atomic.StoreInt32(&closeState, 1)
+ g.Close()
+ atomic.StoreInt32(&closeState, 2)
+ time.Sleep(d / 2)
+}
+
+func BenchmarkGateEnterLeave(b *testing.B) {
+ var g Gate
+ for i := 0; i < b.N; i++ {
+ g.Enter()
+ g.Leave()
+ }
+}
+
+func BenchmarkGateClose(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ var g Gate
+ g.Close()
+ }
+}
+
+func BenchmarkGateEnterLeaveAsyncClose(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ var g Gate
+ g.Enter()
+ go func() {
+ g.Leave()
+ }()
+ g.Close()
+ }
+}