-
Notifications
You must be signed in to change notification settings - Fork 17.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
syscall: swap use of read-write locks for ForkLock #54162
Comments
This is not an API change so it doesn't have to go through the proposal process. |
This is a breaking change. Consumers that have to set close-on-exec on fds themselves may already be using |
|
No, Also, your statement that kernels newer that 2.6 can do this atomically (via |
Using an either-or lock is ok? See issues/23558 Like this (only the first fork holds the write lock):
|
I'm assuming you meant to write |
Yes, a typo error. There is a potential problem. The latency to get |
Unfortunately, I think you'd have to be able to ask the |
Perhaps we should close this issue as a dup of #23558, as they are about the same problem. |
Let me add that both this and #23558 have the same problem: we unfortunately exported |
OK, I think I see a way: https://go.dev/cl/421441. Anybody see a way to improve that code? |
Change https://go.dev/cl/421441 mentions this issue: |
@ianlancetaylor This is the approach that @junchuan-tzh mentioned above. As discussed, it has an issue where if |
@rittneje My apologies for not reading more carefully. I've updated https://go.dev/cl/421441 to avoid that problem. But it may be too complicated now. What do you think of that approach? Thanks. |
Indeed, it is fairly complex. The choice of 10 as the concurrency limit (sort of) also seems very arbitrary. Perhaps var forkingCond = sync.NewCond(new(sync.Mutex))
...
forkingCond.L.Lock()
if forking > 10 {
forkingCond.Wait()
}
if forking == 0 {
syscall.ForkLock.Lock()
}
forking++
forkingCond.L.Unlock()
defer func() {
forkingCond.L.Lock()
forking--
if forking == 0 {
syscall.ForkLock.Unlock()
forkingCond.Broadcast()
}
forkingCond.L.Unlock()
}() |
My personal feeling is that |
I've updated https://go.dev/cl/421441 to use a different approach, using internal secret knowledge about |
@ianlancetaylor I am confused by the call to |
@rittneje Thanks, you're right, that's not needed. I was thinking that the |
@ianlancetaylor I think there may still be a starvation potential hidden here. There is no guarantee that goroutines will acquire the (logical) fork lock in order. For example:
In short, there is a possibility that a goroutine that is intending to fork keeps losing out to other goroutines indefinitely. |
@rittneje I don't think that can happen with the current |
To put it another way, I think that if starvation is possible with this CL, then it is possible with any use of |
But that doesn't matter. Even if D blocks, then when C gets scheduled it will immediately release read lock, which unblocks D. Then when C goes to the top of the for loop, it will observe the I think the correct fix is to remove the for loop. Instead, it should unconditionally acquire logical fork lock after waiting for pending readers once. |
Oh, sorry, I did misunderstand. I see what you mean. Done. |
Thanks, I think it looks good now. One question I do have is whether it would be worth checking for pending writers in addition to pending readers. Otherwise, if there is code outside syscall doing |
I think this is as expected, if the user misuses forklock |
I'm OK with assuming that no program calls |
Change https://go.dev/cl/507355 mentions this issue: |
…is not atomic In CL 421441, we changed syscall to allow concurrent calls to forkExec. On platforms that support the pipe2 syscall that is the right behavior, because pipe2 atomically opens the pipe with CLOEXEC already set. However, on platforms that do not support pipe2 (currently aix and darwin), syscall.forkExecPipe is not atomic, and the pipes do not initially have CLOEXEC set. If two calls to forkExec proceed concurrently, a pipe intended for one child process can be accidentally inherited by the other. If the process is long-lived, the pipe can be held open unexpectedly and prevent the parent process from reaching EOF reading the child's status from the pipe. Fixes #61080. Updates #23558. Updates #54162. Change-Id: I83edcc80674ff267a39d06260c5697c654ff5a4b Reviewed-on: https://go-review.googlesource.com/c/go/+/507355 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Auto-Submit: Bryan Mills <[email protected]>
…is not atomic In CL 421441, we changed syscall to allow concurrent calls to forkExec. On platforms that support the pipe2 syscall that is the right behavior, because pipe2 atomically opens the pipe with CLOEXEC already set. However, on platforms that do not support pipe2 (currently aix and darwin), syscall.forkExecPipe is not atomic, and the pipes do not initially have CLOEXEC set. If two calls to forkExec proceed concurrently, a pipe intended for one child process can be accidentally inherited by the other. If the process is long-lived, the pipe can be held open unexpectedly and prevent the parent process from reaching EOF reading the child's status from the pipe. Fixes golang#61080. Updates golang#23558. Updates golang#54162. Change-Id: I83edcc80674ff267a39d06260c5697c654ff5a4b Reviewed-on: https://go-review.googlesource.com/c/go/+/507355 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Run-TryBot: Bryan Mills <[email protected]> Auto-Submit: Bryan Mills <[email protected]>
Fixes golang#23558 Fixes golang#54162 Change-Id: I3cf6efe466080cdb17e171218e9385ccb272c301 Reviewed-on: https://go-review.googlesource.com/c/go/+/421441 Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
Fixes golang#23558 Fixes golang#54162 Change-Id: I3cf6efe466080cdb17e171218e9385ccb272c301 Reviewed-on: https://go-review.googlesource.com/c/go/+/421441 Run-TryBot: Ian Lance Taylor <[email protected]> Auto-Submit: Ian Lance Taylor <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: David Chase <[email protected]> Reviewed-by: Bryan Mills <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
The ForkLock disables fork while creating new fd and marking it close-on-exec. Fd ops hold read lock, and fork holds write lock.
In kernels newer than 2.6, the two fd ops can be done in one step, and thus the ForkLock is no need. Actually, the ForkLock brings a bottleneck to concurrent forks.
Therefore, we exchange read-write locks: Fd ops hold write lock, and fork holds read lock. It is reasonable since fd ops "modify" process state, and fork just "reads" state. With this change, the newer kernels can remove the concurrent bottlenect, and the older kernels can still ensure the safety of fd close-on-exec.
The text was updated successfully, but these errors were encountered: