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
7package syscall
8
9import "sync"
10
11// forkExecPipe atomically opens a pipe with O_CLOEXEC set on both file
12// descriptors.
13func forkExecPipe(p []int) error {
14	return Pipe2(p, O_CLOEXEC)
15}
16
17var (
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.
28func 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.
39func 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.
84func 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