Skip to content

os/exec: data race between StdinPipe and Wait #9307

@CSEMike

Description

@CSEMike

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."

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions