What version of Go are you using (go version)?
$ go version
go version go1.15.6 linux/amd64
Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (go env)?
go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/jaq/.cache/go-build"
GOENV="/home/jaq/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/jaq/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/jaq/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go-1.15"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go-1.15/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build328231033=/tmp/go-build -gno-record-gcc-switches"
What did you do?
Create a fifo with os.Mkfifo, then open the reader in nonblocking, RDONLY mode. Start a goroutine to read the pipe, with a SetReadDeadline,. in a loop until EOF is received.
Open the writer end and write a single line, then close the file.
A race condition exists in such code where the read is scheduled before the write, and in just the right conditions, the reader returns 0 bytes read and EOF before the write succeeds.
https://play.golang.org/p/cZ9A_FCur43 has a demonstration that uses wg2 to force the race condition. With the race disabled (the race is still there, just harder to trigger) the writer opens the file before the read goroutine is scheduled and the reader gets all the data.
In the errant case (race enabled), the reader gets scheduled first. The write end of the pipe has not been opened yet, and according to man pipe(7) and read(2) the reader should get an EAGAIN, not EOF. EOF is to signal to the reader that the writer has closed the pipe.
What did you expect to see?
When reading from a nonblocking named pipe on Linux before the writer has opened the pipe, one expects to get an EAGAIN error. https://man7.org/linux/man-pages/man2/read.2.html
I think that the correct behaviour for Go, having handled EAGAIN, is to return n = 0, err = nil from fd.Read when a read deadline is set, which would align with pipe(7) in as much as EOF remains reserved as a signal that the writer has closed the pipe.
Otherwise (as is presently the case) there is no way to correctly shut down a pipe reader and handle this race correctly.
What did you see instead?
The reader gets EOF too early. This looks like a problem with how fd.eofError() in internal/poll/fd_posix.go is implemented, and/or how ZeroReadIsEOF is set for nonblocking reads in os/file_unix.go.
What version of Go are you using (
go version)?Does this issue reproduce with the latest release?
Yes
What operating system and processor architecture are you using (
go env)?go envOutputWhat did you do?
Create a fifo with os.Mkfifo, then open the reader in nonblocking, RDONLY mode. Start a goroutine to read the pipe, with a SetReadDeadline,. in a loop until EOF is received.
Open the writer end and write a single line, then close the file.
A race condition exists in such code where the read is scheduled before the write, and in just the right conditions, the reader returns 0 bytes read and EOF before the write succeeds.
https://play.golang.org/p/cZ9A_FCur43 has a demonstration that uses wg2 to force the race condition. With the race disabled (the race is still there, just harder to trigger) the writer opens the file before the read goroutine is scheduled and the reader gets all the data.
In the errant case (race enabled), the reader gets scheduled first. The write end of the pipe has not been opened yet, and according to
man pipe(7)andread(2)the reader should get an EAGAIN, not EOF. EOF is to signal to the reader that the writer has closed the pipe.What did you expect to see?
When reading from a nonblocking named pipe on Linux before the writer has opened the pipe, one expects to get an EAGAIN error. https://man7.org/linux/man-pages/man2/read.2.html
I think that the correct behaviour for Go, having handled
EAGAIN, is to return n = 0, err = nil from fd.Read when a read deadline is set, which would align withpipe(7)in as much as EOF remains reserved as a signal that the writer has closed the pipe.Otherwise (as is presently the case) there is no way to correctly shut down a pipe reader and handle this race correctly.
What did you see instead?
The reader gets EOF too early. This looks like a problem with how
fd.eofError()ininternal/poll/fd_posix.gois implemented, and/or how ZeroReadIsEOF is set for nonblocking reads inos/file_unix.go.