Skip to content

Commit 0f3ab14

Browse files
net, os: don't wait for Close in blocking mode
Updates #7970 Updates #21856 Updates #23111 Change-Id: I0cd0151fcca740c40c3c976f941b04e98e67b0bf Reviewed-on: https://go-review.googlesource.com/83715 Reviewed-by: Russ Cox <rsc@golang.org>
1 parent d0b2467 commit 0f3ab14

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

src/internal/poll/fd_unix.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ type FD struct {
4040

4141
// Whether this is a file rather than a network socket.
4242
isFile bool
43+
44+
// Whether this file has been set to blocking mode.
45+
isBlocking bool
4346
}
4447

4548
// Init initializes the FD. The Sysfd field should already be set.
@@ -76,18 +79,26 @@ func (fd *FD) Close() error {
7679
if !fd.fdmu.increfAndClose() {
7780
return errClosing(fd.isFile)
7881
}
82+
7983
// Unblock any I/O. Once it all unblocks and returns,
8084
// so that it cannot be referring to fd.sysfd anymore,
8185
// the final decref will close fd.sysfd. This should happen
8286
// fairly quickly, since all the I/O is non-blocking, and any
8387
// attempts to block in the pollDesc will return errClosing(fd.isFile).
8488
fd.pd.evict()
89+
8590
// The call to decref will call destroy if there are no other
8691
// references.
8792
err := fd.decref()
93+
8894
// Wait until the descriptor is closed. If this was the only
89-
// reference, it is already closed.
90-
runtime_Semacquire(&fd.csema)
95+
// reference, it is already closed. Only wait if the file has
96+
// not been set to blocking mode, as otherwise any current I/O
97+
// may be blocking, and that would block the Close.
98+
if !fd.isBlocking {
99+
runtime_Semacquire(&fd.csema)
100+
}
101+
91102
return err
92103
}
93104

@@ -100,6 +111,16 @@ func (fd *FD) Shutdown(how int) error {
100111
return syscall.Shutdown(fd.Sysfd, how)
101112
}
102113

114+
// SetBlocking puts the file into blocking mode.
115+
func (fd *FD) SetBlocking() error {
116+
if err := fd.incref(); err != nil {
117+
return err
118+
}
119+
defer fd.decref()
120+
fd.isBlocking = true
121+
return syscall.SetNonblock(fd.Sysfd, false)
122+
}
123+
103124
// Darwin and FreeBSD can't read or write 2GB+ files at a time,
104125
// even on 64-bit systems.
105126
// The same is true of socket implementations on many systems.

src/net/fd_unix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func (fd *netFD) dup() (f *os.File, err error) {
313313
// This also puts the old fd into blocking mode, meaning that
314314
// I/O will block the thread instead of letting us use the epoll server.
315315
// Everything will still work, just with more threads.
316-
if err = syscall.SetNonblock(ns, false); err != nil {
316+
if err = fd.pfd.SetBlocking(); err != nil {
317317
return nil, os.NewSyscallError("setnonblock", err)
318318
}
319319

src/os/file_unix.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func (f *File) Fd() uintptr {
6666
// opened in blocking mode. The File will continue to work,
6767
// but any blocking operation will tie up a thread.
6868
if f.nonblock {
69-
syscall.SetNonblock(f.pfd.Sysfd, false)
69+
f.pfd.SetBlocking()
7070
}
7171

7272
return uintptr(f.pfd.Sysfd)

src/os/pipe_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"runtime"
1818
"strconv"
1919
"strings"
20+
"sync"
2021
"syscall"
2122
"testing"
2223
"time"
@@ -220,3 +221,70 @@ func TestReadNonblockingFd(t *testing.T) {
220221
t.Errorf("child process failed: %v", err)
221222
}
222223
}
224+
225+
// Test that we don't let a blocking read prevent a close.
226+
func TestCloseWithBlockingRead(t *testing.T) {
227+
r, w, err := os.Pipe()
228+
if err != nil {
229+
t.Fatal(err)
230+
}
231+
defer r.Close()
232+
defer w.Close()
233+
234+
c1, c2 := make(chan bool), make(chan bool)
235+
var wg sync.WaitGroup
236+
237+
wg.Add(1)
238+
go func(c chan bool) {
239+
defer wg.Done()
240+
// Give the other goroutine a chance to enter the Read
241+
// or Write call. This is sloppy but the test will
242+
// pass even if we close before the read/write.
243+
time.Sleep(20 * time.Millisecond)
244+
245+
if err := r.Close(); err != nil {
246+
t.Error(err)
247+
}
248+
close(c)
249+
}(c1)
250+
251+
// Calling Fd will put the file into blocking mode.
252+
_ = r.Fd()
253+
254+
wg.Add(1)
255+
go func(c chan bool) {
256+
defer wg.Done()
257+
var b [1]byte
258+
_, err = r.Read(b[:])
259+
close(c)
260+
if err == nil {
261+
t.Error("I/O on closed pipe unexpectedly succeeded")
262+
}
263+
}(c2)
264+
265+
for c1 != nil || c2 != nil {
266+
select {
267+
case <-c1:
268+
c1 = nil
269+
// r.Close has completed, but the blocking Read
270+
// is hanging. Close the writer to unblock it.
271+
w.Close()
272+
case <-c2:
273+
c2 = nil
274+
case <-time.After(1 * time.Second):
275+
switch {
276+
case c1 != nil && c2 != nil:
277+
t.Error("timed out waiting for Read and Close")
278+
w.Close()
279+
case c1 != nil:
280+
t.Error("timed out waiting for Close")
281+
case c2 != nil:
282+
t.Error("timed out waiting for Read")
283+
default:
284+
t.Error("impossible case")
285+
}
286+
}
287+
}
288+
289+
wg.Wait()
290+
}

0 commit comments

Comments
 (0)