diff --git a/src/internal/poll/fd_unix.go b/src/internal/poll/fd_unix.go index d9538e364b..c51370a682 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 b0991a29f2..5118e3f769 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 21ad4462f6..63fb144fdc 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) + } +}