From 382d4928b8a758a91f06de9e6cb10b92bb882eff Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 25 Sep 2017 20:49:37 -0700 Subject: [PATCH] internal/poll: don't return from Close until descriptor is closed 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 TryBot-Result: Gobot Gobot Reviewed-by: Joe Tsai --- src/internal/poll/fd_unix.go | 10 +++++++++- src/internal/poll/fd_windows.go | 10 +++++++++- src/net/listen_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/internal/poll/fd_unix.go b/src/internal/poll/fd_unix.go index d9538e364b3a7..c51370a682470 100644 --- a/src/internal/poll/fd_unix.go +++ b/src/internal/poll/fd_unix.go @@ -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 @@ -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 } @@ -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. diff --git a/src/internal/poll/fd_windows.go b/src/internal/poll/fd_windows.go index b0991a29f2377..5118e3f7690d2 100644 --- a/src/internal/poll/fd_windows.go +++ b/src/internal/poll/fd_windows.go @@ -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 @@ -399,6 +402,7 @@ func (fd *FD) destroy() error { err = CloseFunc(fd.Sysfd) } fd.Sysfd = syscall.InvalidHandle + runtime_Semrelease(&fd.csema) return err } @@ -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. diff --git a/src/net/listen_test.go b/src/net/listen_test.go index 21ad4462f68f1..63fb144fdc16b 100644 --- a/src/net/listen_test.go +++ b/src/net/listen_test.go @@ -13,6 +13,7 @@ import ( "runtime" "syscall" "testing" + "time" ) func (ln *TCPListener) port() string { @@ -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) + } +}