Skip to content

os/exec: possible data race when using same writer for Stdout and Stderr #19804

@nussjustin

Description

@nussjustin

What did you do?

Wrote a simple program that concurrently writes to stdout and stderr: https://play.golang.org/p/S-t6xqdZl7

Wrote a test that runs the program with the same io.Writer for Cmd.Stdout and Cmd.Stderr: https://play.golang.org/p/BYWBCHPA5l

What did you expect to see?

No data race. The documentation of Stdout and Stderr say:

// If Stdout and Stderr are the same writer, at most one
// goroutine at a time will call Write.

What did you see instead?

A data race.

==================
WARNING: DATA RACE
Write at 0x0000012ba190 by goroutine 8:
  _/Users/justin.nuss/Workspace_test.glob..func1()
      /Users/justin.nuss/Workspace/race_test.go:17 +0x41
  _/Users/justin.nuss/Workspace_test.WriterFunc.Write()
      /Users/justin.nuss/Workspace/race_test.go:11 +0x55
  io.copyBuffer()
      /usr/local/Cellar/go/1.8/libexec/src/io/io.go:392 +0x27d
  io.Copy()
      /usr/local/Cellar/go/1.8/libexec/src/io/io.go:360 +0x7e
  os/exec.(*Cmd).writerDescriptor.func1()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:254 +0x68
  os/exec.(*Cmd).Start.func1()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:371 +0x38

Previous write at 0x0000012ba190 by goroutine 7:
  _/Users/justin.nuss/Workspace_test.glob..func1()
      /Users/justin.nuss/Workspace/race_test.go:17 +0x41
  _/Users/justin.nuss/Workspace_test.WriterFunc.Write()
      /Users/justin.nuss/Workspace/race_test.go:11 +0x55
  io.copyBuffer()
      /usr/local/Cellar/go/1.8/libexec/src/io/io.go:392 +0x27d
  io.Copy()
      /usr/local/Cellar/go/1.8/libexec/src/io/io.go:360 +0x7e
  os/exec.(*Cmd).writerDescriptor.func1()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:254 +0x68
  os/exec.(*Cmd).Start.func1()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:371 +0x38

Goroutine 8 (running) created at:
  os/exec.(*Cmd).Start()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:372 +0x924
  os/exec.(*Cmd).Run()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:277 +0x3c
  _/Users/justin.nuss/Workspace_test.TestRace()
      /Users/justin.nuss/Workspace/race_test.go:27 +0x122
  testing.tRunner()
      /usr/local/Cellar/go/1.8/libexec/src/testing/testing.go:657 +0x107

Goroutine 7 (running) created at:
  os/exec.(*Cmd).Start()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:372 +0x924
  os/exec.(*Cmd).Run()
      /usr/local/Cellar/go/1.8/libexec/src/os/exec/exec.go:277 +0x3c
  _/Users/justin.nuss/Workspace_test.TestRace()
      /Users/justin.nuss/Workspace/race_test.go:27 +0x122
  testing.tRunner()
      /usr/local/Cellar/go/1.8/libexec/src/testing/testing.go:657 +0x107
==================
--- FAIL: TestRace (8.15s)
        testing.go:610: race detected during execution of test
FAIL
exit status 1
FAIL    _/Users/justin.nuss/Workspace   8.171s

System details

go version go1.8 darwin/amd64
GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/justin.nuss/Workspace/go"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.8/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.8/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/xf/87dlggns3qlbtpz8dw5f9nbjns0xpd/T/go-build904089654=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"
PKG_CONFIG="pkg-config"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
GOROOT/bin/go version: go version go1.8 darwin/amd64
GOROOT/bin/go tool compile -V: compile version go1.8 X:framepointer
uname -v: Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64
ProductName:	Mac OS X
ProductVersion:	10.12.3
BuildVersion:	16D32
lldb --version: lldb-360.1.70

Explanation

Cmd.stderr calls interfaceEqual to check whether both Stdout and Stderr are equal. interfaceEqual returns false for non comparable types, which results in two goroutines being spawned, both writing (possibly) concurrently to the same io.Writer.

Although probably a rather rare case, I still think the documentation should still be updated to explain how uncomparable types are handled.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions