@@ -16,7 +16,8 @@ import (
16
16
"unsafe"
17
17
)
18
18
19
- // Lock synchronizing creation of new file descriptors with fork.
19
+ // ForkLock is used to synchronize creation of new file descriptors
20
+ // with fork.
20
21
//
21
22
// We want the child in a fork/exec sequence to inherit only the
22
23
// file descriptors we intend. To do that, we mark all file
@@ -53,16 +54,14 @@ import (
53
54
// The rules for which file descriptor-creating operations use the
54
55
// ForkLock are as follows:
55
56
//
56
- // 1) Pipe. Does not block. Use the ForkLock.
57
- // 2) Socket. Does not block. Use the ForkLock.
58
- // 3) Accept. If using non-blocking mode, use the ForkLock.
59
- // Otherwise, live with the race.
60
- // 4) Open. Can block. Use O_CLOEXEC if available (Linux).
61
- // Otherwise, live with the race.
62
- // 5) Dup. Does not block. Use the ForkLock.
63
- // On Linux, could use fcntl F_DUPFD_CLOEXEC
64
- // instead of the ForkLock, but only for dup(fd, -1).
65
-
57
+ // - Pipe. Use pipe2 if available. Otherwise, does not block,
58
+ // so use ForkLock.
59
+ // - Socket. Use SOCK_CLOEXEC if available. Otherwise, does not
60
+ // block, so use ForkLock.
61
+ // - Open. Use O_CLOEXEC if available. Otherwise, may block,
62
+ // so live with the race.
63
+ // - Dup. Use F_DUPFD_CLOEXEC or dup3 if available. Otherwise,
64
+ // does not block, so use ForkLock.
66
65
var ForkLock sync.RWMutex
67
66
68
67
// StringSlicePtr converts a slice of strings to a slice of pointers
@@ -194,14 +193,11 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
194
193
return 0 , errorspkg .New ("Setctty set but Ctty not valid in child" )
195
194
}
196
195
197
- // Acquire the fork lock so that no other threads
198
- // create new fds that are not yet close-on-exec
199
- // before we fork.
200
- ForkLock .Lock ()
196
+ acquireForkLock ()
201
197
202
198
// Allocate child status pipe close on exec.
203
199
if err = forkExecPipe (p [:]); err != nil {
204
- ForkLock . Unlock ()
200
+ releaseForkLock ()
205
201
return 0 , err
206
202
}
207
203
@@ -210,10 +206,10 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
210
206
if err1 != 0 {
211
207
Close (p [0 ])
212
208
Close (p [1 ])
213
- ForkLock . Unlock ()
209
+ releaseForkLock ()
214
210
return 0 , Errno (err1 )
215
211
}
216
- ForkLock . Unlock ()
212
+ releaseForkLock ()
217
213
218
214
// Read child error status from pipe.
219
215
Close (p [1 ])
@@ -245,6 +241,89 @@ func forkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
245
241
return pid , nil
246
242
}
247
243
244
+ var (
245
+ // Guard the forking variable.
246
+ forkingLock sync.Mutex
247
+ // Number of goroutines currently forking, and thus the
248
+ // number of goroutines holding a conceptual write lock
249
+ // on ForkLock.
250
+ forking int
251
+ )
252
+
253
+ // hasWaitingReaders reports whether any goroutine is waiting
254
+ // to acquire a read lock on rw. It is defined in the sync package.
255
+ func hasWaitingReaders (rw * sync.RWMutex ) bool
256
+
257
+ // acquireForkLock acquires a write lock on ForkLock.
258
+ // ForkLock is exported and we've promised that during a fork
259
+ // we will call ForkLock.Lock, so that no other threads create
260
+ // new fds that are not yet close-on-exec before we fork.
261
+ // But that forces all fork calls to be serialized, which is bad.
262
+ // But we haven't promised that serialization, and it is essentially
263
+ // undetectable by other users of ForkLock, which is good.
264
+ // Avoid the serialization by ensuring that ForkLock is locked
265
+ // at the first fork and unlocked when there are no more forks.
266
+ func acquireForkLock () {
267
+ forkingLock .Lock ()
268
+ defer forkingLock .Unlock ()
269
+
270
+ if forking == 0 {
271
+ // There is no current write lock on ForkLock.
272
+ ForkLock .Lock ()
273
+ forking ++
274
+ return
275
+ }
276
+
277
+ // ForkLock is currently locked for writing.
278
+
279
+ if hasWaitingReaders (& ForkLock ) {
280
+ // ForkLock is locked for writing, and at least one
281
+ // goroutine is waiting to read from it.
282
+ // To avoid lock starvation, allow readers to proceed.
283
+ // The simple way to do this is for us to acquire a
284
+ // read lock. That will block us until all current
285
+ // conceptual write locks are released.
286
+ //
287
+ // Note that this case is unusual on modern systems
288
+ // with O_CLOEXEC and SOCK_CLOEXEC. On those systems
289
+ // the standard library should never take a read
290
+ // lock on ForkLock.
291
+
292
+ forkingLock .Unlock ()
293
+
294
+ ForkLock .RLock ()
295
+ ForkLock .RUnlock ()
296
+
297
+ forkingLock .Lock ()
298
+
299
+ // Readers got a chance, so now take the write lock.
300
+
301
+ if forking == 0 {
302
+ ForkLock .Lock ()
303
+ }
304
+ }
305
+
306
+ forking ++
307
+ }
308
+
309
+ // releaseForkLock releases the conceptual write lock on ForkLock
310
+ // acquired by acquireForkLock.
311
+ func releaseForkLock () {
312
+ forkingLock .Lock ()
313
+ defer forkingLock .Unlock ()
314
+
315
+ if forking <= 0 {
316
+ panic ("syscall.releaseForkLock: negative count" )
317
+ }
318
+
319
+ forking --
320
+
321
+ if forking == 0 {
322
+ // No more conceptual write locks.
323
+ ForkLock .Unlock ()
324
+ }
325
+ }
326
+
248
327
// Combination of fork and exec, careful to be thread safe.
249
328
func ForkExec (argv0 string , argv []string , attr * ProcAttr ) (pid int , err error ) {
250
329
return forkExec (argv0 , argv , attr )
0 commit comments