}
func (t *tester) raceTest(dt *distTest) error {
- t.addCmd(dt, "src", "go", "test", "-race", "-i", "runtime/race", "flag", "os/exec")
+ t.addCmd(dt, "src", "go", "test", "-race", "-i", "runtime/race", "flag", "os", "os/exec")
t.addCmd(dt, "src", "go", "test", "-race", t.runFlag("Output"), "runtime/race")
- t.addCmd(dt, "src", "go", "test", "-race", "-short", t.runFlag("TestParse|TestEcho|TestStdinCloseRace"), "flag", "os/exec")
+ t.addCmd(dt, "src", "go", "test", "-race", "-short", t.runFlag("TestParse|TestEcho|TestStdinCloseRace|TestClosedPipeRace"), "flag", "os", "os/exec")
// We don't want the following line, because it
// slows down all.bash (by 10 seconds on my laptop).
// The race builder should catch any error here, but doesn't.
if !file.isdir() {
return nil, &PathError{"Readdir", file.name, syscall.ENOTDIR}
}
- if !file.dirinfo.isempty && file.pfd.Sysfd == syscall.InvalidHandle {
- return nil, syscall.EINVAL
- }
wantAll := n <= 0
size := n
if wantAll {
import (
"errors"
+ "internal/poll"
"io"
"syscall"
)
}
n, e := f.read(b)
if e != nil {
+ if e == poll.ErrClosing {
+ e = ErrClosed
+ }
if e == io.EOF {
err = e
} else {
if f == nil {
return ErrInvalid
}
- if f.pfd.Sysfd == badFd {
- return &PathError{op, f.name, ErrClosed}
- }
return nil
}
}
func (file *file) close() error {
- if file == nil || file.pfd.Sysfd == badFd {
+ if file == nil {
return syscall.EINVAL
}
var err error
if e := file.pfd.Close(); e != nil {
err = &PathError{"close", file.name, e}
}
- file.pfd.Sysfd = badFd // so it can't be closed again
// no need for a finalizer anymore
runtime.SetFinalizer(file, nil)
}
func (file *file) close() error {
- if file == nil || file.pfd.Sysfd == badFd {
+ if file == nil {
return syscall.EINVAL
}
if file.isdir() && file.dirinfo.isempty {
if e := file.pfd.Close(); e != nil {
err = &PathError{"close", file.name, e}
}
- file.pfd.Sysfd = badFd // so it can't be closed again
// no need for a finalizer anymore
runtime.SetFinalizer(file, nil)
}
return nil
}
-
-const badFd = syscall.InvalidHandle
"os"
osexec "os/exec"
"os/signal"
+ "runtime"
"syscall"
"testing"
+ "time"
)
func TestEPIPE(t *testing.T) {
// For descriptor 3, a normal exit is expected.
os.Exit(0)
}
+
+func TestClosedPipeRace(t *testing.T) {
+ switch runtime.GOOS {
+ case "freebsd":
+ t.Skip("FreeBSD does not use the poller; issue 19093")
+ }
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer r.Close()
+ defer w.Close()
+
+ // Close the read end of the pipe in a goroutine while we are
+ // writing to the write end.
+ go func() {
+ // Give the main goroutine a chance to enter the Read call.
+ // This is sloppy but the test will pass even if we close
+ // before the read.
+ time.Sleep(20 * time.Millisecond)
+
+ if err := r.Close(); err != nil {
+ t.Error(err)
+ }
+ }()
+
+ if _, err := r.Read(make([]byte, 1)); err == nil {
+ t.Error("Read of closed pipe unexpectedly succeeded")
+ } else if pe, ok := err.(*os.PathError); !ok {
+ t.Errorf("Read of closed pipe returned unexpected error type %T; expected os.PathError", pe)
+ } else {
+ t.Logf("Read returned expected error %q", err)
+ }
+}
if file == nil {
return nil, ErrInvalid
}
- if file == nil || file.pfd.Sysfd < 0 {
+ if file == nil {
return nil, syscall.EINVAL
}
if file.isdir() {
func sameFile(fs1, fs2 *fileStat) bool {
return fs1.sys.Dev == fs2.sys.Dev && fs1.sys.Ino == fs2.sys.Ino
}
-
-const badFd = -1