// 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) }