Source file src/syscall/forkpipe2.go

     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  

View as plain text