Skip to content

Commit

Permalink
internal/poll: don't return from Close until descriptor is closed
Browse files Browse the repository at this point in the history
This permits the program to reliably know that when the Close method
returns, the descriptor has definitely been closed. This matters at
least for listeners.

Fixes #21856
Updates #7970

Change-Id: I1fd0cfd2333649e6e67c6ae956e19fdff3a35a83
Reviewed-on: https://go-review.googlesource.com/66150
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Joe Tsai <joetsai@google.com>
  • Loading branch information
ianlancetaylor committed Sep 26, 2017
1 parent 8e11cb3 commit 382d492
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 2 deletions.
10 changes: 9 additions & 1 deletion src/internal/poll/fd_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type FD struct {
// Writev cache.
iovecs *[]syscall.Iovec

// Semaphore signaled when file is closed.
csema uint32

// Whether this is a streaming descriptor, as opposed to a
// packet-based descriptor like a UDP socket. Immutable.
IsStream bool
Expand Down Expand Up @@ -62,6 +65,7 @@ func (fd *FD) destroy() error {
fd.pd.close()
err := CloseFunc(fd.Sysfd)
fd.Sysfd = -1
runtime_Semrelease(&fd.csema)
return err
}

Expand All @@ -79,7 +83,11 @@ func (fd *FD) Close() error {
fd.pd.evict()
// The call to decref will call destroy if there are no other
// references.
return fd.decref()
err := fd.decref()
// Wait until the descriptor is closed. If this was the only
// reference, it is already closed.
runtime_Semacquire(&fd.csema)
return err
}

// Shutdown wraps the shutdown network call.
Expand Down
10 changes: 9 additions & 1 deletion src/internal/poll/fd_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,9 @@ type FD struct {
readbyte []byte // buffer to hold decoding of readuint16 from utf16 to utf8
readbyteOffset int // readbyte[readOffset:] is yet to be consumed with file.Read

// Semaphore signaled when file is closed.
csema uint32

skipSyncNotif bool

// Whether this is a streaming descriptor, as opposed to a
Expand Down Expand Up @@ -399,6 +402,7 @@ func (fd *FD) destroy() error {
err = CloseFunc(fd.Sysfd)
}
fd.Sysfd = syscall.InvalidHandle
runtime_Semrelease(&fd.csema)
return err
}

Expand All @@ -410,7 +414,11 @@ func (fd *FD) Close() error {
}
// unblock pending reader and writer
fd.pd.evict()
return fd.decref()
err := fd.decref()
// Wait until the descriptor is closed. If this was the only
// reference, it is already closed.
runtime_Semacquire(&fd.csema)
return err
}

// Shutdown wraps the shutdown network call.
Expand Down
32 changes: 32 additions & 0 deletions src/net/listen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"runtime"
"syscall"
"testing"
"time"
)

func (ln *TCPListener) port() string {
Expand Down Expand Up @@ -696,3 +697,34 @@ func multicastRIBContains(ip IP) (bool, error) {
}
return false, nil
}

// Issue 21856.
func TestClosingListener(t *testing.T) {
listener, err := Listen("tcp", ":0")
if err != nil {
t.Fatal(err)
}
addr := listener.Addr()

go func() {
for {
c, err := listener.Accept()
if err != nil {
return
}
c.Close()
}
}()

// Let the goroutine start. We don't sleep long: if the
// goroutine doesn't start, the test will pass without really
// testing anything, which is OK.
time.Sleep(time.Millisecond)

listener.Close()

_, err = Listen("tcp", addr.String())
if err != nil {
t.Error(err)
}
}

0 comments on commit 382d492

Please sign in to comment.