github.com/flowerwrong/netstack@v0.0.0-20191009141956-e5848263af28/gate/gate.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package gate provides a usage Gate synchronization primitive.
    16  package gate
    17  
    18  import (
    19  	"sync/atomic"
    20  )
    21  
    22  const (
    23  	// gateClosed is the bit set in the gate's user count to indicate that
    24  	// it has been closed. It is the MSB of the 32-bit field; the other 31
    25  	// bits carry the actual count.
    26  	gateClosed = 0x80000000
    27  )
    28  
    29  // Gate is a synchronization primitive that allows concurrent goroutines to
    30  // "enter" it as long as it hasn't been closed yet. Once it's been closed,
    31  // goroutines cannot enter it anymore, but are allowed to leave, and the closer
    32  // will be informed when all goroutines have left.
    33  //
    34  // Many goroutines are allowed to enter the gate concurrently, but only one is
    35  // allowed to close it.
    36  //
    37  // This is similar to a r/w critical section, except that goroutines "entering"
    38  // never block: they either enter immediately or fail to enter. The closer will
    39  // block waiting for all goroutines currently inside the gate to leave.
    40  //
    41  // This function is implemented efficiently. On x86, only one interlocked
    42  // operation is performed on enter, and one on leave.
    43  //
    44  // This is useful, for example, in cases when a goroutine is trying to clean up
    45  // an object for which multiple goroutines have pointers. In such a case, users
    46  // would be required to enter and leave the gates, and the cleaner would wait
    47  // until all users are gone (and no new ones are allowed) before proceeding.
    48  //
    49  // Users:
    50  //
    51  //	if !g.Enter() {
    52  //		// Gate is closed, we can't use the object.
    53  //		return
    54  //	}
    55  //
    56  //	// Do something with object.
    57  //	[...]
    58  //
    59  //	g.Leave()
    60  //
    61  // Closer:
    62  //
    63  //	// Prevent new users from using the object, and wait for the existing
    64  //	// ones to complete.
    65  //	g.Close()
    66  //
    67  //	// Clean up the object.
    68  //	[...]
    69  //
    70  type Gate struct {
    71  	userCount uint32
    72  	done      chan struct{}
    73  }
    74  
    75  // Enter tries to enter the gate. It will succeed if it hasn't been closed yet,
    76  // in which case the caller must eventually call Leave().
    77  //
    78  // This function is thread-safe.
    79  func (g *Gate) Enter() bool {
    80  	if g == nil {
    81  		return false
    82  	}
    83  
    84  	for {
    85  		v := atomic.LoadUint32(&g.userCount)
    86  		if v&gateClosed != 0 {
    87  			return false
    88  		}
    89  
    90  		if atomic.CompareAndSwapUint32(&g.userCount, v, v+1) {
    91  			return true
    92  		}
    93  	}
    94  }
    95  
    96  // Leave leaves the gate. This must only be called after a successful call to
    97  // Enter(). If the gate has been closed and this is the last one inside the
    98  // gate, it will notify the closer that the gate is done.
    99  //
   100  // This function is thread-safe.
   101  func (g *Gate) Leave() {
   102  	for {
   103  		v := atomic.LoadUint32(&g.userCount)
   104  		if v&^gateClosed == 0 {
   105  			panic("leaving a gate with zero usage count")
   106  		}
   107  
   108  		if atomic.CompareAndSwapUint32(&g.userCount, v, v-1) {
   109  			if v == gateClosed+1 {
   110  				close(g.done)
   111  			}
   112  			return
   113  		}
   114  	}
   115  }
   116  
   117  // Close closes the gate for entering, and waits until all goroutines [that are
   118  // currently inside the gate] leave before returning.
   119  //
   120  // Only one goroutine can call this function.
   121  func (g *Gate) Close() {
   122  	for {
   123  		v := atomic.LoadUint32(&g.userCount)
   124  		if v&^gateClosed != 0 && g.done == nil {
   125  			g.done = make(chan struct{})
   126  		}
   127  		if atomic.CompareAndSwapUint32(&g.userCount, v, v|gateClosed) {
   128  			if v&^gateClosed != 0 {
   129  				<-g.done
   130  			}
   131  			return
   132  		}
   133  	}
   134  }