Closed
Description
If Conn.Close is called when there's on-going read/write, read/write may return I/O error instead of wrapping a CloseError, and there is no trivial way to examine whether this I/O error is caused by calling Conn.Close.
Of course I can record the code and reason before I call Conn.Close, but there is still a race between my call to Conn.Close and something like WebSocket protocol error. My recorded code and reason may not reflect what actually sent to remote peer in the close frame.
How to reproduce
package main
import (
"context"
"log"
"time"
"nhooyr.io/websocket"
)
func work(ctx context.Context) (err error) {
client, _, err := websocket.Dial(ctx, "ws://demos.kaazing.com/echo", nil)
if err != nil { return }
log.Print("connected")
// write something in background
errCh := make(chan error)
go func() {
for {
err := client.Write(ctx, websocket.MessageText, []byte("hello"))
if err != nil {
errCh <- err
return
}
time.Sleep(50 * time.Millisecond)
}
}()
// close after some time
time.Sleep(3 * time.Second)
log.Print("closing")
err = client.Close(websocket.StatusNormalClosure, "bye")
if err != nil { return err }
log.Print("closed")
return <-errCh
}
func main() {
err := work(context.Background())
if err != nil && websocket.CloseStatus(err) != websocket.StatusNormalClosure {
log.Fatal(err)
}
}
Expected output
2020/03/15 14:14:00 connected
2020/03/15 14:14:03 closing
2020/03/15 14:14:03 closed
Actual output (sometimes, due to race)
2020/03/15 14:13:54 connected
2020/03/15 14:13:57 closing
2020/03/15 14:13:58 failed to close WebSocket: failed to read frame header: read tcp 192.168.1.2:57362->34.209.17.111:80: read: connection reset by peer