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

  1. Stop all writers before/when closing: use a done channel or context cancellation coordinated with conn.Close()
  2. Treat this error as benign during shutdown — check with errors.Is and return quietly
  3. Use a single writer goroutine (send messages over a channel) so close ordering is deterministic
  4. 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

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


AI-assisted analysis of gorilla/websocket@e064f32e36 (2026-08-31). Data as JSON: /api/errors/17116ea8f5cdc6aa. Report an issue: GitHub.