-
Notifications
You must be signed in to change notification settings - Fork 18.5k
Description
Go version: go1.4.2
OS: Linux 3.13 x86_64
The following test demonstrates what I consider a bug:
package foo
import (
"log"
"net"
"testing"
)
const addr = "127.0.0.1:12345"
func TestXxx(t *testing.T) {
for i := 0; i < 100000; i++ {
log.Printf("Iteration %d", i+1)
l, err := net.Listen("tcp4", addr)
if err != nil {
t.Fatalf("Failed to listen on %s: %v", addr, err)
}
go func(l net.Listener) {
for {
_, err := l.Accept()
if err != nil {
return
}
}
}(l)
if err := l.Close(); err != nil {
t.Fatalf("Failed to close server: %v", err)
}
}
}Briefly, within a loop it Listens on tcp port 12345, invokes a goroutine that calls Accept on the listener, and then Closes the listener. My expectation was that this would always pass regardless of scheduler behavior or number of loop iterations as I expected Close() would only return once the underlying socket is no longer in use.
Instead what I see is that it will sometimes pass, but often fails on the net.Listen call of subsequent loop iterations with an error that the address is already in use. I believe I understand the cause of the failure. netFD underlies the TCPListener and it's a reference counted object. A goroutine blocked on Accept will increment the reference count. Close() flips a flag on netFD to indicate the socket is considered closed which will make other netFD operations like Accept return errors indicating the socket is closed. The underlying OS socket is only closed once the reference count for the netFD has reached zero.
The end result is that an OS socket is only closed after TCPListener.Close() is invoked AND all goroutines that are blocked in TCPListener.Accept() are scheduled and return the expected error condition. It seems like more intuitive behavior would be for TCPListener.Close() to immediately close the underlying OS socket, and goroutines blocked in TCPListener.Accept() to return errors whenever they were next scheduled/run.