mirror of
https://github.com/golang/go
synced 2024-11-19 05:44:40 -07:00
internal/poll: don't take read lock in SetBlocking
Taking a read lock in SetBlocking could cause SetBlocking to block waiting for a Read in another goroutine to complete. Since SetBlocking is called by os.(*File).Fd, that could lead to deadlock if the goroutine calling Fd is going to use it to unblock the Read. Use an atomic store instead. Updates #24481 Change-Id: I79413328e06ddf28b6d5b8af7a0e29d5b4e1e6ff Reviewed-on: https://go-review.googlesource.com/123176 Run-TryBot: Ian Lance Taylor <iant@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
e86bbc2e27
commit
8d6fc84986
2
src/cmd/dist/test.go
vendored
2
src/cmd/dist/test.go
vendored
@ -1354,7 +1354,7 @@ func (t *tester) runFlag(rx string) string {
|
|||||||
func (t *tester) raceTest(dt *distTest) error {
|
func (t *tester) raceTest(dt *distTest) error {
|
||||||
t.addCmd(dt, "src", t.goTest(), "-race", "-i", "runtime/race", "flag", "os", "os/exec")
|
t.addCmd(dt, "src", t.goTest(), "-race", "-i", "runtime/race", "flag", "os", "os/exec")
|
||||||
t.addCmd(dt, "src", t.goTest(), "-race", t.runFlag("Output"), "runtime/race")
|
t.addCmd(dt, "src", t.goTest(), "-race", t.runFlag("Output"), "runtime/race")
|
||||||
t.addCmd(dt, "src", t.goTest(), "-race", t.runFlag("TestParse|TestEcho|TestStdinCloseRace|TestClosedPipeRace|TestTypeRace|TestFdRace|TestFileCloseRace"), "flag", "net", "os", "os/exec", "encoding/gob")
|
t.addCmd(dt, "src", t.goTest(), "-race", t.runFlag("TestParse|TestEcho|TestStdinCloseRace|TestClosedPipeRace|TestTypeRace|TestFdRace|TestFdReadRace|TestFileCloseRace"), "flag", "net", "os", "os/exec", "encoding/gob")
|
||||||
// We don't want the following line, because it
|
// We don't want the following line, because it
|
||||||
// slows down all.bash (by 10 seconds on my laptop).
|
// slows down all.bash (by 10 seconds on my laptop).
|
||||||
// The race builder should catch any error here, but doesn't.
|
// The race builder should catch any error here, but doesn't.
|
||||||
|
@ -31,6 +31,9 @@ type FD struct {
|
|||||||
// Semaphore signaled when file is closed.
|
// Semaphore signaled when file is closed.
|
||||||
csema uint32
|
csema uint32
|
||||||
|
|
||||||
|
// Non-zero if this file has been set to blocking mode.
|
||||||
|
isBlocking uint32
|
||||||
|
|
||||||
// Whether this is a streaming descriptor, as opposed to a
|
// Whether this is a streaming descriptor, as opposed to a
|
||||||
// packet-based descriptor like a UDP socket. Immutable.
|
// packet-based descriptor like a UDP socket. Immutable.
|
||||||
IsStream bool
|
IsStream bool
|
||||||
@ -41,9 +44,6 @@ type FD struct {
|
|||||||
|
|
||||||
// Whether this is a file rather than a network socket.
|
// Whether this is a file rather than a network socket.
|
||||||
isFile bool
|
isFile bool
|
||||||
|
|
||||||
// Whether this file has been set to blocking mode.
|
|
||||||
isBlocking bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init initializes the FD. The Sysfd field should already be set.
|
// Init initializes the FD. The Sysfd field should already be set.
|
||||||
@ -57,14 +57,14 @@ func (fd *FD) Init(net string, pollable bool) error {
|
|||||||
fd.isFile = true
|
fd.isFile = true
|
||||||
}
|
}
|
||||||
if !pollable {
|
if !pollable {
|
||||||
fd.isBlocking = true
|
fd.isBlocking = 1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
err := fd.pd.init(fd)
|
err := fd.pd.init(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we could not initialize the runtime poller,
|
// If we could not initialize the runtime poller,
|
||||||
// assume we are using blocking mode.
|
// assume we are using blocking mode.
|
||||||
fd.isBlocking = true
|
fd.isBlocking = 1
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -103,9 +103,9 @@ func (fd *FD) Close() error {
|
|||||||
// reference, it is already closed. Only wait if the file has
|
// reference, it is already closed. Only wait if the file has
|
||||||
// not been set to blocking mode, as otherwise any current I/O
|
// not been set to blocking mode, as otherwise any current I/O
|
||||||
// may be blocking, and that would block the Close.
|
// may be blocking, and that would block the Close.
|
||||||
// No need for a lock to read isBlocking, increfAndClose means
|
// No need for an atomic read of isBlocking, increfAndClose means
|
||||||
// we have exclusive access to fd.
|
// we have exclusive access to fd.
|
||||||
if !fd.isBlocking {
|
if fd.isBlocking == 0 {
|
||||||
runtime_Semacquire(&fd.csema)
|
runtime_Semacquire(&fd.csema)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,13 +123,14 @@ func (fd *FD) Shutdown(how int) error {
|
|||||||
|
|
||||||
// SetBlocking puts the file into blocking mode.
|
// SetBlocking puts the file into blocking mode.
|
||||||
func (fd *FD) SetBlocking() error {
|
func (fd *FD) SetBlocking() error {
|
||||||
// Take an exclusive lock, rather than calling incref, so that
|
if err := fd.incref(); err != nil {
|
||||||
// we can safely modify isBlocking.
|
|
||||||
if err := fd.readLock(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fd.readUnlock()
|
defer fd.decref()
|
||||||
fd.isBlocking = true
|
// Atomic store so that concurrent calls to SetBlocking
|
||||||
|
// do not cause a race condition. isBlocking only ever goes
|
||||||
|
// from 0 to 1 so there is no real race here.
|
||||||
|
atomic.StoreUint32(&fd.isBlocking, 1)
|
||||||
return syscall.SetNonblock(fd.Sysfd, false)
|
return syscall.SetNonblock(fd.Sysfd, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,3 +395,45 @@ func TestFdRace(t *testing.T) {
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFdReadRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
c := make(chan bool)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
var buf [10]byte
|
||||||
|
r.SetReadDeadline(time.Now().Add(time.Second))
|
||||||
|
c <- true
|
||||||
|
if _, err := r.Read(buf[:]); os.IsTimeout(err) {
|
||||||
|
t.Error("read timed out")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
<-c
|
||||||
|
// Give the other goroutine a chance to enter the Read.
|
||||||
|
// It doesn't matter if this occasionally fails, the test
|
||||||
|
// will still pass, it just won't test anything.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
r.Fd()
|
||||||
|
|
||||||
|
// The bug was that Fd would hang until Read timed out.
|
||||||
|
// If the bug is fixed, then closing r here will cause
|
||||||
|
// the Read to exit before the timeout expires.
|
||||||
|
r.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user