Skip to content

os: race between os.(*File).Close() and os.(*File).Read() #10001

Closed
@tarm

Description

@tarm

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions