-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
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.
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("/bin/echo")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatalf("StdinPipe: %v", err)
}
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")
}()
if err := cmd.Wait(); err != nil {
log.Fatalf("Wait: %v", err)
}
<-wrote
}
To observe the race, save this program to racedemo.go, and run:
go run -race racedemo.go
The result:
WARNING: DATA RACE
Read by goroutine 5:
os.(*File).write()
/usr/lib/google-golang/src/pkg/os/file_unix.go:211 +0xc5
os.(*File).Write()
/usr/lib/google-golang/src/pkg/os/file.go:139 +0xcb
os/exec.(*closeOnce).Write()
<autogenerated>:42 +0x81
fmt.Fprint()
/usr/lib/google-golang/src/pkg/fmt/print.go:223 +0xc3
main.func·001()
/usr/local/google/home/piatek/racedemo.go:21 +0x159
Previous write by main goroutine:
os.(*file).close()
/usr/lib/google-golang/src/pkg/os/file_unix.go:108 +0x19b
os.(*File).Close()
/usr/lib/google-golang/src/pkg/os/file_unix.go:97 +0x94
os/exec.(*closeOnce).close()
/usr/lib/google-golang/src/pkg/os/exec/exec.go:442 +0x45
os/exec.*closeOnce.(os/exec.close)·fm()
/usr/lib/google-golang/src/pkg/os/exec/exec.go:437 +0x33
sync.(*Once).Do()
/usr/lib/google-golang/src/pkg/sync/once.go:40 +0xb1
os/exec.(*closeOnce).Close()
/usr/lib/google-golang/src/pkg/os/exec/exec.go:437 +0x8e
os/exec.(*Cmd).closeDescriptors()
/usr/lib/google-golang/src/pkg/os/exec/exec.go:222 +0xa2
os/exec.(*Cmd).Wait()
/usr/lib/google-golang/src/pkg/os/exec/exec.go:367 +0x45c
main.main()
/usr/local/google/home/piatek/racedemo.go:23 +0x316
Goroutine 5 (running) created at:
main.main()
/usr/local/google/home/piatek/racedemo.go:22 +0x307
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."