gorilla/websocket · critical

concurrent write to websocket connection

Error message

concurrent write to websocket connection

What it means

This panic fires when a write is started on a connection whose isWriting flag is already true, meaning two goroutines attempted to write concurrently. gorilla/websocket only supports one concurrent writer; this best-effort detection protects the connection from interleaved, corrupted frames.

Source

Thrown at conn.go:626

		c.writeBuf[framePos] = b0
		c.writeBuf[framePos+1] = b1 | byte(length)
	}

	if !c.isServer {
		key := newMaskKey()
		copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
		maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
		if len(extra) > 0 {
			return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
		}
	}

	// Write the buffers to the connection with best-effort detection of
	// concurrent writes. See the concurrency section in the package
	// documentation for more info.

	if c.isWriting {
		panic("concurrent write to websocket connection")
	}
	c.isWriting = true

	err := c.write(w.frameType, c.writeDeadline, c.writeBuf[framePos:w.pos], extra)

	if !c.isWriting {
		panic("concurrent write to websocket connection")
	}
	c.isWriting = false

	if err != nil {
		return w.endMessage(err)
	}

	if final {
		_ = w.endMessage(errWriteClosed)
		return nil
	}

View on GitHub (pinned to e064f32e36)

Solutions

  1. Serialize all writes through a single goroutine with a channel of outbound messages
  2. Guard writes with a sync.Mutex held around every WriteMessage/WriteControl call on that Conn
  3. Move keepalive pings into the same writer goroutine instead of a separate timer goroutine
  4. Recover from the panic per-connection and close/reconnect, since state may be corrupted

Example fix

// before
go func(){ conn.WriteMessage(TextMessage, msg) }()
go func(){ conn.WriteMessage(TextMessage, other) }()
// after
var mu sync.Mutex
func safeWrite(conn *websocket.Conn, data []byte) {
    mu.Lock()
    defer mu.Unlock()
    conn.WriteMessage(websocket.TextMessage, data)
}
Defensive patterns

Strategy: try-catch

Try / catch

func safeWrite(conn *websocket.Conn, mt int, data []byte) (err error) {
    defer func() { recover() }() // guard against concurrent-write panic
    mu.Lock()
    defer mu.Unlock()
    return conn.WriteMessage(mt, data)
}

Prevention

When it happens

Trigger: Two goroutines simultaneously calling WriteMessage, WriteControl paths via prepareWrite/flushFrame, or NextWriter-based writes on the same Conn; the second write observes isWriting==true at conn.go:626.

Common situations: Broadcasting messages to a connection from multiple goroutines (e.g. a hub fan-out plus a direct write), ping keepalive goroutines writing while the main handler writes, or sending from websocket event callbacks and background timers at the same time.

Related errors


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