forkpipe2.mx raw

   1  // Copyright 2017 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  //go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris
   6  
   7  package syscall
   8  
   9  import "sync"
  10  
  11  // forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file
  12  // descriptors.
  13  func forkExecPipe(p []int) error {
  14  	return Pipe2(p, O_CLOEXEC)
  15  }
  16  
  17  var (
  18  	// Guard the forking variable.
  19  	forkingLock sync.Mutex
  20  	// Number of goroutines currently forking, and thus the
  21  	// number of goroutines holding a conceptual write lock
  22  	// on ForkLock.
  23  	forking int
  24  )
  25  
  26  // hasWaitingReaders reports whether any goroutine is waiting
  27  // to acquire a read lock on rw. It is defined in the sync package.
  28  func hasWaitingReaders(rw *sync.RWMutex) bool
  29  
  30  // acquireForkLock acquires a write lock on ForkLock.
  31  // ForkLock is exported and we've promised that during a fork
  32  // we will call ForkLock.Lock, so that no other threads create
  33  // new fds that are not yet close-on-exec before we fork.
  34  // But that forces all fork calls to be serialized, which is bad.
  35  // But we haven't promised that serialization, and it is essentially
  36  // undetectable by other users of ForkLock, which is good.
  37  // Avoid the serialization by ensuring that ForkLock is locked
  38  // at the first fork and unlocked when there are no more forks.
  39  func acquireForkLock() {
  40  	forkingLock.Lock()
  41  	defer forkingLock.Unlock()
  42  
  43  	if forking == 0 {
  44  		// There is no current write lock on ForkLock.
  45  		ForkLock.Lock()
  46  		forking++
  47  		return
  48  	}
  49  
  50  	// ForkLock is currently locked for writing.
  51  
  52  	if hasWaitingReaders(&ForkLock) {
  53  		// ForkLock is locked for writing, and at least one
  54  		// goroutine is waiting to read from it.
  55  		// To avoid lock starvation, allow readers to proceed.
  56  		// The simple way to do this is for us to acquire a
  57  		// read lock. That will block us until all current
  58  		// conceptual write locks are released.
  59  		//
  60  		// Note that this case is unusual on modern systems
  61  		// with O_CLOEXEC and SOCK_CLOEXEC. On those systems
  62  		// the standard library should never take a read
  63  		// lock on ForkLock.
  64  
  65  		forkingLock.Unlock()
  66  
  67  		ForkLock.RLock()
  68  		ForkLock.RUnlock()
  69  
  70  		forkingLock.Lock()
  71  
  72  		// Readers got a chance, so now take the write lock.
  73  
  74  		if forking == 0 {
  75  			ForkLock.Lock()
  76  		}
  77  	}
  78  
  79  	forking++
  80  }
  81  
  82  // releaseForkLock releases the conceptual write lock on ForkLock
  83  // acquired by acquireForkLock.
  84  func releaseForkLock() {
  85  	forkingLock.Lock()
  86  	defer forkingLock.Unlock()
  87  
  88  	if forking <= 0 {
  89  		panic("syscall.releaseForkLock: negative count")
  90  	}
  91  
  92  	forking--
  93  
  94  	if forking == 0 {
  95  		// No more conceptual write locks.
  96  		ForkLock.Unlock()
  97  	}
  98  }
  99