diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/gate/gate.go | 134 | ||||
-rw-r--r-- | pkg/gate/gate_state_autogen.go | 3 | ||||
-rw-r--r-- | pkg/sync/gate_unsafe.go | 150 | ||||
-rw-r--r-- | pkg/sync/runtime_unsafe.go | 6 | ||||
-rw-r--r-- | pkg/tcpip/link/waitable/waitable.go | 6 | ||||
-rw-r--r-- | pkg/unet/unet.go | 4 |
6 files changed, 160 insertions, 143 deletions
diff --git a/pkg/gate/gate.go b/pkg/gate/gate.go deleted file mode 100644 index bda6aae09..000000000 --- a/pkg/gate/gate.go +++ /dev/null @@ -1,134 +0,0 @@ -// 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 gate provides a usage Gate synchronization primitive. -package gate - -import ( - "sync/atomic" -) - -const ( - // gateClosed is the bit set in the gate's user count to indicate that - // it has been closed. It is the MSB of the 32-bit field; the other 31 - // bits carry the actual count. - gateClosed = 0x80000000 -) - -// 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. -// -// Many goroutines are allowed to enter the gate concurrently, but only one is -// allowed to close it. -// -// This is similar to a r/w critical section, except that goroutines "entering" -// never block: they either enter immediately or fail to enter. The closer will -// block waiting for all goroutines currently inside the gate to leave. -// -// This function is implemented efficiently. On x86, only one interlocked -// operation is performed on enter, and one on leave. -// -// 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 gates, 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 uint32 - done chan struct{} -} - -// 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 g == nil { - return false - } - - for { - v := atomic.LoadUint32(&g.userCount) - if v&gateClosed != 0 { - return false - } - - if atomic.CompareAndSwapUint32(&g.userCount, v, v+1) { - return true - } - } -} - -// 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() { - for { - v := atomic.LoadUint32(&g.userCount) - if v&^gateClosed == 0 { - panic("leaving a gate with zero usage count") - } - - if atomic.CompareAndSwapUint32(&g.userCount, v, v-1) { - if v == gateClosed+1 { - close(g.done) - } - return - } - } -} - -// Close closes the gate for entering, 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() { - for { - v := atomic.LoadUint32(&g.userCount) - if v&^gateClosed != 0 && g.done == nil { - g.done = make(chan struct{}) - } - if atomic.CompareAndSwapUint32(&g.userCount, v, v|gateClosed) { - if v&^gateClosed != 0 { - <-g.done - } - return - } - } -} diff --git a/pkg/gate/gate_state_autogen.go b/pkg/gate/gate_state_autogen.go deleted file mode 100644 index 221af659e..000000000 --- a/pkg/gate/gate_state_autogen.go +++ /dev/null @@ -1,3 +0,0 @@ -// automatically generated by stateify. - -package gate diff --git a/pkg/sync/gate_unsafe.go b/pkg/sync/gate_unsafe.go new file mode 100644 index 000000000..ae32287ef --- /dev/null +++ b/pkg/sync/gate_unsafe.go @@ -0,0 +1,150 @@ +// 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) +} diff --git a/pkg/sync/runtime_unsafe.go b/pkg/sync/runtime_unsafe.go index 119ff832a..158985709 100644 --- a/pkg/sync/runtime_unsafe.go +++ b/pkg/sync/runtime_unsafe.go @@ -56,12 +56,16 @@ func goready(gp uintptr, traceskip int) // Values for the reason argument to gopark, from Go's src/runtime/runtime2.go. const ( - WaitReasonSelect uint8 = 9 + WaitReasonSelect uint8 = 9 + WaitReasonChanReceive uint8 = 14 + WaitReasonSemacquire uint8 = 18 ) // Values for the traceEv argument to gopark, from Go's src/runtime/trace.go. const ( + TraceEvGoBlockRecv byte = 23 TraceEvGoBlockSelect byte = 24 + TraceEvGoBlockSync byte = 25 ) // Rand32 returns a non-cryptographically-secure random uint32. diff --git a/pkg/tcpip/link/waitable/waitable.go b/pkg/tcpip/link/waitable/waitable.go index 20259b285..ce5113746 100644 --- a/pkg/tcpip/link/waitable/waitable.go +++ b/pkg/tcpip/link/waitable/waitable.go @@ -22,7 +22,7 @@ package waitable import ( - "gvisor.dev/gvisor/pkg/gate" + "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/pkg/tcpip/stack" @@ -30,10 +30,10 @@ import ( // Endpoint is a waitable link-layer endpoint. type Endpoint struct { - dispatchGate gate.Gate + dispatchGate sync.Gate dispatcher stack.NetworkDispatcher - writeGate gate.Gate + writeGate sync.Gate lower stack.LinkEndpoint } diff --git a/pkg/unet/unet.go b/pkg/unet/unet.go index c976d7230..22dd40f21 100644 --- a/pkg/unet/unet.go +++ b/pkg/unet/unet.go @@ -23,7 +23,7 @@ import ( "sync/atomic" "syscall" - "gvisor.dev/gvisor/pkg/gate" + "gvisor.dev/gvisor/pkg/sync" ) // backlog is used for the listen request. @@ -67,7 +67,7 @@ func eventFD() (int, error) { // Socket is a connected unix domain socket. type Socket struct { // gate protects use of fd. - gate gate.Gate + gate sync.Gate // fd is the bound socket. // |