-
Notifications
You must be signed in to change notification settings - Fork 17.7k
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
os/exec: data race between StdinPipe and Wait #9307
Comments
How could the race be prevented at the os/exec level? Should we delay Wait(), possibly indefinitely, waiting for Write to |
Before speculating on how best to fix the problem, I would like to reach agreement on the intended semantics of the API. Specifically, are writes to StdinPipe intended to be concurrent with Wait? |
It seems the only way to fix the data race is to override provide our own Either we lock the struct for Read/Close, or use a sync/atomic to load the I don't think we can continue to use os.(*File), because otherwise we will so to fix this, we will need to implement the method ourselves using syscall We need to weight the pros and cons of fixing this and the effort required Leave for others to decide. |
Note that the race only occurs if the process exits before reading the data read on the input pipe. |
This is related to issue #6817, in that if that issue were fixed this one would no longer be a problem, as os.Pipe would return an object that supports simultaneous Write/Close. |
Yes, if fixing os.Pipe will fix the underlying problem. Unfortunately, |
Yes, #6817 is hard. But, short of that, I'm not sure how hard to work to make a WriteCloser that supports simultaneous Write/Close only for the case where a program exits before reading from the input pipe. It's easy to construct such a case, but it doesn't seem like something that people are likely to do in real life. Still, I just thought of one approach that should solve the problem at least for StdinPipe. In closeOnce.Close, call Fd to get the file descriptor and call syscall.Close (or syscall.CloseHandler) on the descriptor. Add closeOnce.Write that locks a mutex before writing. Have closeOnce.Close lock the mutex after calling syscall.Close. Trust that the kernel will unblock the write when the descriptor is closed. |
On Sat, Dec 13, 2014 at 8:37 PM, Ian Lance Taylor [email protected]
A somewhat related issue is #9173. We don't know if the running child |
I think I'd argue this is a dup of #7970, although it's more troubling, because you can actually demonstrate it in a simple program. |
Too late to fix for Go 1.6. |
For what it is worth, if this bug is blocking you, for a hot fix you can get around it by setting your own stdin before cmd.Start(). I ran into this bug two days back and it got me for about 18 hours until I realized that the race condition was caused because in --- orig.go 2016-02-11 03:44:56.000000000 -0800
+++ main.go 2016-02-11 03:44:57.000000000 -0800
@@ -2,23 +2,24 @@
import (
"fmt"
+ "io"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("/bin/echo")
- stdin, err := cmd.StdinPipe()
- if err != nil {
- log.Fatalf("StdinPipe: %v", err)
- }
+ pr, pw := io.Pipe()
+
+ cmd.Stdin = pr
if err := cmd.Start(); err != nil {
log.Fatalf("Start: %v", err)
}
wrote := make(chan bool)
go func() {
defer close(wrote)
- fmt.Fprint(stdin, "echo\n")
+ fmt.Fprint(pw, "echo\n")
+ _ = pw.Close()
}()
if err := cmd.Wait(); err != nil {
log.Fatalf("Wait: %v", err) |
Screencast here https://asciinema.org/a/f13lrd8pep3ohr9ha0exe0rwh |
Missed again. |
We need to decide what to do here. |
CL https://golang.org/cl/31148 mentions this issue. |
os/exec already guards against "implicit Close during cmd.Wait racing against user calling w.Close". The CL I just sent expands that to guard against "implicit Close during cmd.Wait racing against user calling w.Write". There is still a race between w.Close and w.Write, like there is on any os.File. I'm not solving #7970, just the os/exec case. |
Change https://golang.org/cl/65490 mentions this issue: |
CL 31148 added code to protect again simultaneous calls to Close and Wait when using the standard input pipe, to fix the race condition described in issue #9307. That issue is a special case of the race between Close and Write described by issue #7970. Since issue #7970 was not fixed, CL 31148 fixed the problem specific to os/exec. Since then, issue #7970 has been fixed, so the specific fix in os/exec is no longer necessary. Remove it, effectively reverting CL 31148 and followup CL 33298. Updates #7970 Updates #9307 Updates #17647 Change-Id: Ic0b62569cb0aba44b32153cf5f9632bd1f1b411a Reviewed-on: https://go-review.googlesource.com/65490 Run-TryBot: Ian Lance Taylor <[email protected]> Reviewed-by: Daniel Martí <[email protected]> Reviewed-by: Miguel Bernabeu <[email protected]> Reviewed-by: Russ Cox <[email protected]> Reviewed-by: Joe Tsai <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
I believe there is an unintentional race in os/exec between Command.Wait and file operations. I am using go version go1.3 linux/amd64, but the race also reproduces with 1.4
A test case follows.
To observe the race, save this program to racedemo.go, and run:
The result:
The race is on a reference to the file descriptor, which is assigned -1 upon close and read when writing. The documentation is not precise, but I suspect this race is unintentional.
The implementation of StdinPipe uses the internal closeOnce type to prevent concurrent calls to Close; see: http://golang.org/src/os/exec/exec.go#L436
Once wrapped, the file descriptor is added to the closeAfterWait slice, suggesting that concurrent calls to Close and Wait are to be expected. Yet, a concurrent Write will race as shown above. The partially concurrent API is odd.
I believe it is common for code to block on Wait in one goroutine while writing to StdinPipe from other goroutines. (Indeed, I found such a race in a library at Google, prompting my investigation.)
If this race is expected behavior, I suggest updating the package docs to mirror the warning for use of StdoutPipe, e.g., "It is incorrect to call Wait before all writes to the pipe have completed."
The text was updated successfully, but these errors were encountered: