gorilla/websocket · warning
websocket: write closed
Error message
websocket: write closed
What it means
errWriteClosed is returned by Conn.Write (and Close paths) when a write is attempted after the write side of the connection has been closed. Once the connection's write state is marked closed, further writes fail with this sentinel.
Source
Thrown at conn.go:179
// IsUnexpectedCloseError returns boolean indicating whether the error is a
// *CloseError with a code not in the list of expected codes.
func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
if e, ok := err.(*CloseError); ok {
for _, code := range expectedCodes {
if e.Code == code {
return false
}
}
return true
}
return false
}
var (
errWriteTimeout = &netError{msg: "websocket: write timeout", timeout: true, temporary: true}
errUnexpectedEOF = &CloseError{Code: CloseAbnormalClosure, Text: io.ErrUnexpectedEOF.Error()}
errBadWriteOpCode = errors.New("websocket: bad write message type")
errWriteClosed = errors.New("websocket: write closed")
errInvalidControlFrame = errors.New("websocket: invalid control frame")
)
// maskRand is an io.Reader for generating mask bytes. The reader is initialized
// to crypto/rand Reader. Tests swap the reader to a math/rand reader for
// reproducible results.
var maskRand = rand.Reader
// newMaskKey returns a new 32 bit value for masking client frames.
func newMaskKey() [4]byte {
var k [4]byte
_, _ = io.ReadFull(maskRand, k[:])
return k
}
func isControl(frameType int) bool {
return frameType == CloseMessage || frameType == PingMessage || frameType == PongMessage
}View on GitHub (pinned to e064f32e36)
Solutions
- Stop all writers before/when closing: use a done channel or context cancellation coordinated with conn.Close()
- Treat this error as benign during shutdown — check with errors.Is and return quietly
- Use a single writer goroutine (send messages over a channel) so close ordering is deterministic
- Guard writes with a sync.Once or state flag that prevents writes after Close is invoked
Example fix
// before
conn.Close()
conn.WriteMessage(websocket.TextMessage, []byte("bye")) // errors: write closed
// after
err := conn.WriteMessage(websocket.TextMessage, []byte("bye"))
if err == nil {
conn.Close()
} Defensive patterns
Strategy: try-catch
Type guard
func isWriteClosed(err error) bool {
return errors.Is(err, websocket.ErrCloseSent) || errors.Is(err, errWriteClosedEquivalent(err))
}
// in practice: match by message since errWriteClosed is unexported
func isWriteClosedMsg(err error) bool {
return err != nil && strings.Contains(err.Error(), "websocket: write closed")
} Try / catch
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
if strings.Contains(err.Error(), "websocket: write closed") ||
errors.Is(err, websocket.ErrCloseSent) {
return nil // connection already shutting down
}
return err
} Prevention
- Signal all writer goroutines via a closed channel or context before conn.Close()
- Route every write through one owner goroutine to make close ordering deterministic
- Ignore benign shutdown-write errors instead of retrying them
When it happens
Trigger: Calling WriteMessage/NextWriter-based writes after conn.Close() has been called, or after the internal write close has occurred (e.g. following a close handshake completion or prior fatal write error).
Common situations: Background goroutines still writing after the main flow closed the connection; write pump continuing after read pump detected an error and closed; double-close of a connection followed by an attempted write.
Related errors
- websocket: close sent
- concurrent write to websocket connection
- websocket: bad handshake
- websocket: invalid compression negotiation
- malformed ws or wss URL
AI-assisted analysis of gorilla/websocket@e064f32e36 (2026-08-31).
Data as JSON: /api/errors/17116ea8f5cdc6aa.
Report an issue: GitHub.