1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
// 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 (
"fmt"
"math"
"sync/atomic"
"unsafe"
"gvisor.dev/gvisor/pkg/gohacks"
)
// Gate is a synchronization primitive that allows concurrent goroutines to
// "enter" it as long as it hasn't been closed yet. Once it's been closed,
// goroutines cannot enter it anymore, but are allowed to leave, and the closer
// will be informed when all goroutines have left.
//
// Gate is similar to WaitGroup:
//
// - Gate.Enter() is analogous to WaitGroup.Add(1), but may be called even if
// the Gate counter is 0 and fails if Gate.Close() has been called.
//
// - Gate.Leave() is equivalent to WaitGroup.Done().
//
// - Gate.Close() is analogous to WaitGroup.Wait(), but also causes future
//
// calls to Gate.Enter() to fail and may only be called once, from a single
// goroutine.
//
// This is useful, for example, in cases when a goroutine is trying to clean up
// an object for which multiple goroutines have pointers. In such a case, users
// would be required to enter and leave the Gate, and the cleaner would wait
// until all users are gone (and no new ones are allowed) before proceeding.
//
// Users:
//
// if !g.Enter() {
// // Gate is closed, we can't use the object.
// return
// }
//
// // Do something with object.
// [...]
//
// g.Leave()
//
// Closer:
//
// // Prevent new users from using the object, and wait for the existing
// // ones to complete.
// g.Close()
//
// // Clean up the object.
// [...]
type Gate struct {
userCount int32
closingG uintptr
}
const preparingG = 1
// Enter tries to enter the gate. It will succeed if it hasn't been closed yet,
// in which case the caller must eventually call Leave().
//
// This function is thread-safe.
func (g *Gate) Enter() bool {
if atomic.AddInt32(&g.userCount, 1) > 0 {
return true
}
g.leaveAfterFailedEnter()
return false
}
// leaveAfterFailedEnter is identical to Leave, but is marked noinline to
// prevent it from being inlined into Enter, since as of this writing inlining
// Leave into Enter prevents Enter from being inlined into its callers.
//
//go:noinline
func (g *Gate) leaveAfterFailedEnter() {
if atomic.AddInt32(&g.userCount, -1) == math.MinInt32 {
g.leaveClosed()
}
}
// Leave leaves the gate. This must only be called after a successful call to
// Enter(). If the gate has been closed and this is the last one inside the
// gate, it will notify the closer that the gate is done.
//
// This function is thread-safe.
func (g *Gate) Leave() {
if atomic.AddInt32(&g.userCount, -1) == math.MinInt32 {
g.leaveClosed()
}
}
func (g *Gate) leaveClosed() {
if atomic.LoadUintptr(&g.closingG) == 0 {
return
}
if g := atomic.SwapUintptr(&g.closingG, 0); g > preparingG {
goready(g, 0)
}
}
// Close closes the gate, causing future calls to Enter to fail, and waits
// until all goroutines that are currently inside the gate leave before
// returning.
//
// Only one goroutine can call this function.
func (g *Gate) Close() {
if atomic.LoadInt32(&g.userCount) == math.MinInt32 {
// The gate is already closed, with no goroutines inside. For legacy
// reasons, we have to allow Close to be called again in this case.
return
}
if v := atomic.AddInt32(&g.userCount, math.MinInt32); v == math.MinInt32 {
// userCount was already 0.
return
} else if v >= 0 {
panic("concurrent Close of sync.Gate")
}
if g := atomic.SwapUintptr(&g.closingG, preparingG); g != 0 {
panic(fmt.Sprintf("invalid sync.Gate.closingG during Close: %#x", g))
}
if atomic.LoadInt32(&g.userCount) == math.MinInt32 {
// The last call to Leave arrived while we were setting up closingG.
return
}
// WaitReasonSemacquire/TraceEvGoBlockSync are consistent with WaitGroup.
gopark(gateCommit, gohacks.Noescape(unsafe.Pointer(&g.closingG)), WaitReasonSemacquire, TraceEvGoBlockSync, 0)
}
//go:norace
//go:nosplit
func gateCommit(g uintptr, closingG unsafe.Pointer) bool {
return RaceUncheckedAtomicCompareAndSwapUintptr((*uintptr)(closingG), preparingG, g)
}
|