rpc: send WebSocket close frame on client disconnect

When rpc.Client.Close() is called, the TCP connection is torn down
without sending a WebSocket Close frame. The server sees
"websocket: close 1006 (abnormal closure): unexpected EOF" instead
of a clean 1000 (normal closure).

The root cause is that websocketCodec.close() delegates to
jsonCodec.close() which calls conn.Close() — gorilla/websocket's
Conn.Close explicitly "closes the underlying network connection
without sending or waiting for a close message" (per RFC 6455).

Send a Close control frame before closing, using the same encMu
mutex pattern used by pingLoop for write serialization.

Fixes ethereum/go-ethereum#30482
This commit is contained in:
YQ AltLayer 2026-03-01 12:20:19 +00:00
parent 723aae2b4e
commit 98ee3f68ff

View file

@ -324,6 +324,16 @@ func newWebsocketCodec(conn *websocket.Conn, host string, req http.Header, readL
}
func (wc *websocketCodec) close() {
// Send a WebSocket Close frame before closing the underlying connection,
// so the server sees a clean 1000 (normal closure) instead of 1006 (abnormal).
wc.jsonCodec.encMu.Lock()
wc.conn.WriteControl(
websocket.CloseMessage,
websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""),
time.Now().Add(wsPingWriteTimeout),
)
wc.jsonCodec.encMu.Unlock()
wc.jsonCodec.close()
wc.wg.Wait()
}