| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build dragonfly || freebsd || linux || netbsd || openbsd || solaris | |
| package syscall | |
| import "sync" | |
| // forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file | |
| // descriptors. | |
| func forkExecPipe(p []int) error { | |
| return Pipe2(p, O_CLOEXEC) | |
| } | |
| var ( | |
| // Guard the forking variable. | |
| forkingLock sync.Mutex | |
| // Number of goroutines currently forking, and thus the | |
| // number of goroutines holding a conceptual write lock | |
| // on ForkLock. | |
| forking int | |
| ) | |
| // hasWaitingReaders reports whether any goroutine is waiting | |
| // to acquire a read lock on rw. It is defined in the sync package. | |
| func hasWaitingReaders(rw *sync.RWMutex) bool | |
| // acquireForkLock acquires a write lock on ForkLock. | |
| // ForkLock is exported and we've promised that during a fork | |
| // we will call ForkLock.Lock, so that no other threads create | |
| // new fds that are not yet close-on-exec before we fork. | |
| // But that forces all fork calls to be serialized, which is bad. | |
| // But we haven't promised that serialization, and it is essentially | |
| // undetectable by other users of ForkLock, which is good. | |
| // Avoid the serialization by ensuring that ForkLock is locked | |
| // at the first fork and unlocked when there are no more forks. | |
| func acquireForkLock() { | |
| forkingLock.Lock() | |
| defer forkingLock.Unlock() | |
| if forking == 0 { | |
| // There is no current write lock on ForkLock. | |
| ForkLock.Lock() | |
| forking++ | |
| return | |
| } | |
| // ForkLock is currently locked for writing. | |
| if hasWaitingReaders(&ForkLock) { | |
| // ForkLock is locked for writing, and at least one | |
| // goroutine is waiting to read from it. | |
| // To avoid lock starvation, allow readers to proceed. | |
| // The simple way to do this is for us to acquire a | |
| // read lock. That will block us until all current | |
| // conceptual write locks are released. | |
| // | |
| // Note that this case is unusual on modern systems | |
| // with O_CLOEXEC and SOCK_CLOEXEC. On those systems | |
| // the standard library should never take a read | |
| // lock on ForkLock. | |
| forkingLock.Unlock() | |
| ForkLock.RLock() | |
| ForkLock.RUnlock() | |
| forkingLock.Lock() | |
| // Readers got a chance, so now take the write lock. | |
| if forking == 0 { | |
| ForkLock.Lock() | |
| } | |
| } | |
| forking++ | |
| } | |
| // releaseForkLock releases the conceptual write lock on ForkLock | |
| // acquired by acquireForkLock. | |
| func releaseForkLock() { | |
| forkingLock.Lock() | |
| defer forkingLock.Unlock() | |
| if forking <= 0 { | |
| panic("syscall.releaseForkLock: negative count") | |
| } | |
| forking-- | |
| if forking == 0 { | |
| // No more conceptual write locks. | |
| ForkLock.Unlock() | |
| } | |
| } |