Description
Here's is a simple reproduction case operating on a regular file (in this case "race.go"). This is with go 1.4.2
package main
import (
"log"
"os"
)
func main() {
f, err := os.Open("race.go")
if err != nil {
log.Fatal(err)
}
ch := make(chan struct{})
go func() {
f.Close()
close(ch)
}()
b := make([]byte, 512)
n, err := f.Read(b)
log.Printf("Read %v, %s, %v", n, b[:n], err)
<-ch
}
In this case, this would obviously be a silly thing to do because there is also a larger race condition in terms of how much data is actually read from the Read() call. The real case this came up was with a serial port device (https://github.com/tarm/goserial) where the Read() and Close() might happen in different goroutines because the Read() can take a long time (serial ports can be configured to block forever until the next character). A syscall.Close() will allow the Read() to return, which is why os.(*File).Close() and Read() are useful to be able to run in different goroutines.
The problematic line in file_unix.go:close() is
file.fd = -1 // so it can't be closed again
Conceptually, does it make sense that Close() and Read() should be allowed to run at the same time? The cost of a mutex in Close() is not important, but putting a mutex in Read() might have a performance impact. Would a fix be appropriate for the std library? Should I try to work around this in the goserial library by not using os.File?
tarm@linux ~ $ go run -race race.go
2015/02/25 10:55:52 Read 287, package main
import (
"log"
"os"
)
func main() {
f, err := os.Open("race.go")
if err != nil {
log.Fatal(err)
}
ch := make(chan struct{})
go func() {
f.Close()
close(ch)
}()
b := make([]byte, 512)
n, err := f.Read(b)
log.Printf("Read %v, %s, %v", n, b[:n], err)
<-ch
}, <nil>
==================
WARNING: DATA RACE
Write by goroutine 5:
os.(*file).close()
/home/tarm/gosrc/src/os/file_unix.go:109 +0x1d7
os.(*File).Close()
/home/tarm/gosrc/src/os/file_unix.go:98 +0x91
main.func·001()
/home/tarm/race.go:15 +0x53
Previous read by main goroutine:
os.(*File).read()
/home/tarm/gosrc/src/os/file_unix.go:191 +0x5f
os.(*File).Read()
/home/tarm/gosrc/src/os/file.go:95 +0xc3
main.main()
/home/tarm/race.go:19 +0x32a
Goroutine 5 (running) created at:
main.main()
/home/tarm/race.go:17 +0x29a
==================
Found 1 data race(s)
exit status 66