github.com/cbeuw/gotfo@v0.0.0-20180331191851-f2b091af84de/fd_mutex.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Note fd_mutex in go1.8 and go1.9 are nearly identical,
     6  // but go1.8's error messages all begin with "net: ..."
     7  
     8  package gotfo
     9  
    10  import (
    11  	"sync/atomic"
    12  )
    13  
    14  // fdMutex is a specialized synchronization primitive that manages
    15  // lifetime of an fd and serializes access to Read, Write and Close
    16  // methods on netFD.
    17  type fdMutex struct {
    18  	state uint64
    19  	rsema uint32
    20  	wsema uint32
    21  }
    22  
    23  // fdMutex.state is organized as follows:
    24  // 1 bit - whether netFD is closed, if set all subsequent lock operations will fail.
    25  // 1 bit - lock for read operations.
    26  // 1 bit - lock for write operations.
    27  // 20 bits - total number of references (read+write+misc).
    28  // 20 bits - number of outstanding read waiters.
    29  // 20 bits - number of outstanding write waiters.
    30  const (
    31  	mutexClosed  = 1 << 0
    32  	mutexRLock   = 1 << 1
    33  	mutexWLock   = 1 << 2
    34  	mutexRef     = 1 << 3
    35  	mutexRefMask = (1<<20 - 1) << 3
    36  	mutexRWait   = 1 << 23
    37  	mutexRMask   = (1<<20 - 1) << 23
    38  	mutexWWait   = 1 << 43
    39  	mutexWMask   = (1<<20 - 1) << 43
    40  )
    41  
    42  // Read operations must do rwlock(true)/rwunlock(true).
    43  //
    44  // Write operations must do rwlock(false)/rwunlock(false).
    45  //
    46  // Misc operations must do incref/decref.
    47  // Misc operations include functions like setsockopt and setDeadline.
    48  // They need to use incref/decref to ensure that they operate on the
    49  // correct fd in presence of a concurrent close call (otherwise fd can
    50  // be closed under their feet).
    51  //
    52  // Close operations must do increfAndClose/decref.
    53  
    54  // incref adds a reference to mu.
    55  // It reports whether mu is available for reading or writing.
    56  func (mu *fdMutex) incref() bool {
    57  	for {
    58  		old := atomic.LoadUint64(&mu.state)
    59  		if old&mutexClosed != 0 {
    60  			return false
    61  		}
    62  		new := old + mutexRef
    63  		if new&mutexRefMask == 0 {
    64  			panic("net: inconsistent fdMutex")
    65  		}
    66  		if atomic.CompareAndSwapUint64(&mu.state, old, new) {
    67  			return true
    68  		}
    69  	}
    70  }
    71  
    72  // increfAndClose sets the state of mu to closed.
    73  // It reports whether there is no remaining reference.
    74  func (mu *fdMutex) increfAndClose() bool {
    75  	for {
    76  		old := atomic.LoadUint64(&mu.state)
    77  		if old&mutexClosed != 0 {
    78  			return false
    79  		}
    80  		// Mark as closed and acquire a reference.
    81  		new := (old | mutexClosed) + mutexRef
    82  		if new&mutexRefMask == 0 {
    83  			panic("net: inconsistent fdMutex")
    84  		}
    85  		// Remove all read and write waiters.
    86  		new &^= mutexRMask | mutexWMask
    87  		if atomic.CompareAndSwapUint64(&mu.state, old, new) {
    88  			// Wake all read and write waiters,
    89  			// they will observe closed flag after wakeup.
    90  			for old&mutexRMask != 0 {
    91  				old -= mutexRWait
    92  				runtime_Semrelease(&mu.rsema)
    93  			}
    94  			for old&mutexWMask != 0 {
    95  				old -= mutexWWait
    96  				runtime_Semrelease(&mu.wsema)
    97  			}
    98  			return true
    99  		}
   100  	}
   101  }
   102  
   103  // decref removes a reference from mu.
   104  // It reports whether there is no remaining reference.
   105  func (mu *fdMutex) decref() bool {
   106  	for {
   107  		old := atomic.LoadUint64(&mu.state)
   108  		if old&mutexRefMask == 0 {
   109  			panic("net: inconsistent fdMutex")
   110  		}
   111  		new := old - mutexRef
   112  		if atomic.CompareAndSwapUint64(&mu.state, old, new) {
   113  			return new&(mutexClosed|mutexRefMask) == mutexClosed
   114  		}
   115  	}
   116  }
   117  
   118  // lock adds a reference to mu and locks mu.
   119  // It reports whether mu is available for reading or writing.
   120  func (mu *fdMutex) rwlock(read bool) bool {
   121  	var mutexBit, mutexWait, mutexMask uint64
   122  	var mutexSema *uint32
   123  	if read {
   124  		mutexBit = mutexRLock
   125  		mutexWait = mutexRWait
   126  		mutexMask = mutexRMask
   127  		mutexSema = &mu.rsema
   128  	} else {
   129  		mutexBit = mutexWLock
   130  		mutexWait = mutexWWait
   131  		mutexMask = mutexWMask
   132  		mutexSema = &mu.wsema
   133  	}
   134  	for {
   135  		old := atomic.LoadUint64(&mu.state)
   136  		if old&mutexClosed != 0 {
   137  			return false
   138  		}
   139  		var new uint64
   140  		if old&mutexBit == 0 {
   141  			// Lock is free, acquire it.
   142  			new = (old | mutexBit) + mutexRef
   143  			if new&mutexRefMask == 0 {
   144  				panic("net: inconsistent fdMutex")
   145  			}
   146  		} else {
   147  			// Wait for lock.
   148  			new = old + mutexWait
   149  			if new&mutexMask == 0 {
   150  				panic("net: inconsistent fdMutex")
   151  			}
   152  		}
   153  		if atomic.CompareAndSwapUint64(&mu.state, old, new) {
   154  			if old&mutexBit == 0 {
   155  				return true
   156  			}
   157  			runtime_Semacquire(mutexSema)
   158  			// The signaller has subtracted mutexWait.
   159  		}
   160  	}
   161  }
   162  
   163  // unlock removes a reference from mu and unlocks mu.
   164  // It reports whether there is no remaining reference.
   165  func (mu *fdMutex) rwunlock(read bool) bool {
   166  	var mutexBit, mutexWait, mutexMask uint64
   167  	var mutexSema *uint32
   168  	if read {
   169  		mutexBit = mutexRLock
   170  		mutexWait = mutexRWait
   171  		mutexMask = mutexRMask
   172  		mutexSema = &mu.rsema
   173  	} else {
   174  		mutexBit = mutexWLock
   175  		mutexWait = mutexWWait
   176  		mutexMask = mutexWMask
   177  		mutexSema = &mu.wsema
   178  	}
   179  	for {
   180  		old := atomic.LoadUint64(&mu.state)
   181  		if old&mutexBit == 0 || old&mutexRefMask == 0 {
   182  			panic("net: inconsistent fdMutex")
   183  		}
   184  		// Drop lock, drop reference and wake read waiter if present.
   185  		new := (old &^ mutexBit) - mutexRef
   186  		if old&mutexMask != 0 {
   187  			new -= mutexWait
   188  		}
   189  		if atomic.CompareAndSwapUint64(&mu.state, old, new) {
   190  			if old&mutexMask != 0 {
   191  				runtime_Semrelease(mutexSema)
   192  			}
   193  			return new&(mutexClosed|mutexRefMask) == mutexClosed
   194  		}
   195  	}
   196  }
   197  
   198  // incref adds a reference to fd.
   199  // It returns an error when fd cannot be used.
   200  func (fd *netFD) incref() error {
   201  	if !fd.fdmu.incref() {
   202  		return errClosing
   203  	}
   204  	return nil
   205  }
   206  
   207  // decref removes a reference from fd.
   208  // It also closes fd when the state of fd is set to closed and there
   209  // is no remaining reference.
   210  func (fd *netFD) decref() error {
   211  	if fd.fdmu.decref() {
   212  		return fd.destroy()
   213  	}
   214  
   215  	return nil
   216  }
   217  
   218  // readLock adds a reference to fd and locks fd for reading.
   219  // It returns an error when fd cannot be used for reading.
   220  func (fd *netFD) readLock() error {
   221  	if !fd.fdmu.rwlock(true) {
   222  		return errClosing
   223  	}
   224  	return nil
   225  }
   226  
   227  // readUnlock removes a reference from fd and unlocks fd for reading.
   228  // It also closes fd when the state of fd is set to closed and there
   229  // is no remaining reference.
   230  func (fd *netFD) readUnlock() {
   231  	if fd.fdmu.rwunlock(true) {
   232  		fd.destroy()
   233  	}
   234  }
   235  
   236  // writeLock adds a reference to fd and locks fd for writing.
   237  // It returns an error when fd cannot be used for writing.
   238  func (fd *netFD) writeLock() error {
   239  	if !fd.fdmu.rwlock(false) {
   240  		return errClosing
   241  	}
   242  	return nil
   243  }
   244  
   245  // writeUnlock removes a reference from fd and unlocks fd for writing.
   246  // It also closes fd when the state of fd is set to closed and there
   247  // is no remaining reference.
   248  func (fd *netFD) writeUnlock() {
   249  	if fd.fdmu.rwunlock(false) {
   250  		fd.destroy()
   251  	}
   252  }