135 lines
3.7 KiB
Go
135 lines
3.7 KiB
Go
// 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
|
|
}
|
|
}
|
|
}
|