Closed
Description
@nhooyr I managed to condense it down to a minimal test case that reliably reproduces the bug. There's no read and write calls at all, just the server closing the connection right after accepting it.
func TestWebsocketImmediateClose(t *testing.T) {
closed := make(chan struct{})
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
defer close(closed)
c, err := ws.Accept(w, r, &ws.AcceptOptions{InsecureSkipVerify: true})
if err != nil {
return // The upgrader writes a response for us.
}
start := time.Now()
c.Close(ws.StatusUnsupportedData, "bye")
if took := time.Since(start); took > 2*time.Second {
t.Fatalf("closing took too long: %s", took)
}
})
server := &http.Server{Handler: mux, Addr: "localhost:8080"}
done := make(chan struct{})
go func() {
defer close(done)
server.ListenAndServe()
}()
time.Sleep(25 * time.Millisecond) // give the server some time to boot
if _, _, err := ws.Dial(context.Background(), "ws://localhost:8080", nil); err != nil {
t.Fatal(err)
}
<-closed // wait until the HTTP handler has returned
// test shutdown
server.Close()
<-done
}
I'm not very familiar with the code base (nor with the WebSocket protocol itself), but here's where the Close
call gets stuck: https://github.com/nhooyr/websocket/blob/14fb98eba64eeb5e9d06a88b98c47ae924ac82b4/close_notjs.go#L108
Maybe I'm thinking in the wrong direction here, but this is surprising to me. Why do we need to read anything when closing? I would've expected us to just write a Close frame and then return.
Originally posted by @marten-seemann in #355 (comment)