mirror of
https://github.com/golang/go
synced 2024-11-23 16:20:04 -07:00
os, net: avoid races between dup, set-blocking-mode, and closing
Fixes #24481 Fixes #24483 Change-Id: Id7da498425a440c91582aa5480c253ae7a9c932c Reviewed-on: https://go-review.googlesource.com/119955 Run-TryBot: Ian Lance Taylor <iant@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
parent
75fdeaa801
commit
00eac8921e
2
src/cmd/dist/test.go
vendored
2
src/cmd/dist/test.go
vendored
@ -1346,7 +1346,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"), "flag", "os", "os/exec", "encoding/gob")
|
t.addCmd(dt, "src", t.goTest(), "-race", t.runFlag("TestParse|TestEcho|TestStdinCloseRace|TestClosedPipeRace|TestTypeRace|TestFdRace|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.
|
||||||
|
@ -9,6 +9,7 @@ package poll
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -102,6 +103,8 @@ 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
|
||||||
|
// we have exclusive access to fd.
|
||||||
if !fd.isBlocking {
|
if !fd.isBlocking {
|
||||||
runtime_Semacquire(&fd.csema)
|
runtime_Semacquire(&fd.csema)
|
||||||
}
|
}
|
||||||
@ -120,10 +123,12 @@ 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 {
|
||||||
if err := fd.incref(); err != nil {
|
// Take an exclusive lock, rather than calling incref, so that
|
||||||
|
// we can safely modify isBlocking.
|
||||||
|
if err := fd.readLock(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer fd.decref()
|
defer fd.readUnlock()
|
||||||
fd.isBlocking = true
|
fd.isBlocking = true
|
||||||
return syscall.SetNonblock(fd.Sysfd, false)
|
return syscall.SetNonblock(fd.Sysfd, false)
|
||||||
}
|
}
|
||||||
@ -439,6 +444,50 @@ func (fd *FD) Fstat(s *syscall.Stat_t) error {
|
|||||||
return syscall.Fstat(fd.Sysfd, s)
|
return syscall.Fstat(fd.Sysfd, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tryDupCloexec indicates whether F_DUPFD_CLOEXEC should be used.
|
||||||
|
// If the kernel doesn't support it, this is set to 0.
|
||||||
|
var tryDupCloexec = int32(1)
|
||||||
|
|
||||||
|
// DupCloseOnExec dups fd and marks it close-on-exec.
|
||||||
|
func DupCloseOnExec(fd int) (int, string, error) {
|
||||||
|
if atomic.LoadInt32(&tryDupCloexec) == 1 {
|
||||||
|
r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_DUPFD_CLOEXEC, 0)
|
||||||
|
switch e1 {
|
||||||
|
case 0:
|
||||||
|
return int(r0), "", nil
|
||||||
|
case syscall.EINVAL:
|
||||||
|
// Old kernel. Fall back to the portable way
|
||||||
|
// from now on.
|
||||||
|
atomic.StoreInt32(&tryDupCloexec, 0)
|
||||||
|
default:
|
||||||
|
return -1, "fcntl", e1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dupCloseOnExecOld(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dupCloseOnExecUnixOld is the traditional way to dup an fd and
|
||||||
|
// set its O_CLOEXEC bit, using two system calls.
|
||||||
|
func dupCloseOnExecOld(fd int) (int, string, error) {
|
||||||
|
syscall.ForkLock.RLock()
|
||||||
|
defer syscall.ForkLock.RUnlock()
|
||||||
|
newfd, err := syscall.Dup(fd)
|
||||||
|
if err != nil {
|
||||||
|
return -1, "dup", err
|
||||||
|
}
|
||||||
|
syscall.CloseOnExec(newfd)
|
||||||
|
return newfd, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dup duplicates the file descriptor.
|
||||||
|
func (fd *FD) Dup() (int, string, error) {
|
||||||
|
if err := fd.incref(); err != nil {
|
||||||
|
return -1, "", err
|
||||||
|
}
|
||||||
|
defer fd.decref()
|
||||||
|
return DupCloseOnExec(fd.Sysfd)
|
||||||
|
}
|
||||||
|
|
||||||
// On Unix variants only, expose the IO event for the net code.
|
// On Unix variants only, expose the IO event for the net code.
|
||||||
|
|
||||||
// WaitWrite waits until data can be read from fd.
|
// WaitWrite waits until data can be read from fd.
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"internal/poll"
|
"internal/poll"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -257,43 +256,12 @@ func (fd *netFD) accept() (netfd *netFD, err error) {
|
|||||||
return netfd, nil
|
return netfd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// tryDupCloexec indicates whether F_DUPFD_CLOEXEC should be used.
|
|
||||||
// If the kernel doesn't support it, this is set to 0.
|
|
||||||
var tryDupCloexec = int32(1)
|
|
||||||
|
|
||||||
func dupCloseOnExec(fd int) (newfd int, err error) {
|
|
||||||
if atomic.LoadInt32(&tryDupCloexec) == 1 {
|
|
||||||
r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_DUPFD_CLOEXEC, 0)
|
|
||||||
switch e1 {
|
|
||||||
case 0:
|
|
||||||
return int(r0), nil
|
|
||||||
case syscall.EINVAL:
|
|
||||||
// Old kernel. Fall back to the portable way
|
|
||||||
// from now on.
|
|
||||||
atomic.StoreInt32(&tryDupCloexec, 0)
|
|
||||||
default:
|
|
||||||
return -1, os.NewSyscallError("fcntl", e1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dupCloseOnExecOld(fd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dupCloseOnExecUnixOld is the traditional way to dup an fd and
|
|
||||||
// set its O_CLOEXEC bit, using two system calls.
|
|
||||||
func dupCloseOnExecOld(fd int) (newfd int, err error) {
|
|
||||||
syscall.ForkLock.RLock()
|
|
||||||
defer syscall.ForkLock.RUnlock()
|
|
||||||
newfd, err = syscall.Dup(fd)
|
|
||||||
if err != nil {
|
|
||||||
return -1, os.NewSyscallError("dup", err)
|
|
||||||
}
|
|
||||||
syscall.CloseOnExec(newfd)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fd *netFD) dup() (f *os.File, err error) {
|
func (fd *netFD) dup() (f *os.File, err error) {
|
||||||
ns, err := dupCloseOnExec(fd.pfd.Sysfd)
|
ns, call, err := fd.pfd.Dup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if call != "" {
|
||||||
|
err = os.NewSyscallError(call, err)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,3 +293,57 @@ func TestFilePacketConn(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 24483.
|
||||||
|
func TestFileCloseRace(t *testing.T) {
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "nacl", "plan9", "windows":
|
||||||
|
t.Skipf("not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
if !testableNetwork("tcp") {
|
||||||
|
t.Skip("tcp not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := func(ls *localServer, ln Listener) {
|
||||||
|
c, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
var b [1]byte
|
||||||
|
c.Read(b[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
ls, err := newLocalServer("tcp")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer ls.teardown()
|
||||||
|
if err := ls.buildup(handler); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tries = 100
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
c1, err := Dial(ls.Listener.Addr().Network(), ls.Listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tc := c1.(*TCPConn)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
f, err := tc.File()
|
||||||
|
if err == nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
c1.Close()
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -13,8 +13,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func dupSocket(f *os.File) (int, error) {
|
func dupSocket(f *os.File) (int, error) {
|
||||||
s, err := dupCloseOnExec(int(f.Fd()))
|
s, call, err := poll.DupCloseOnExec(int(f.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if call != "" {
|
||||||
|
err = os.NewSyscallError(call, err)
|
||||||
|
}
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
if err := syscall.SetNonblock(s, true); err != nil {
|
if err := syscall.SetNonblock(s, true); err != nil {
|
||||||
|
@ -372,3 +372,26 @@ func TestPipeEOF(t *testing.T) {
|
|||||||
r.Close()
|
r.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue 24481.
|
||||||
|
func TestFdRace(t *testing.T) {
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
call := func() {
|
||||||
|
defer wg.Done()
|
||||||
|
w.Fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
const tries = 100
|
||||||
|
for i := 0; i < tries; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go call()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user