gorilla/websocket · critical

repeated read on failed websocket connection

Error message

repeated read on failed websocket connection

What it means

To help developers who ignore read errors, the library counts consecutive reads on a connection that already returned an error; when readErrCount reaches 1000 it panics with this message. ReadMessage keeps returning the same stored error forever after failure, so a tight loop without error handling would otherwise spin indefinitely on a dead connection.

Source

Thrown at conn.go:1041

			break
		}

		if frameType == TextMessage || frameType == BinaryMessage {
			c.messageReader = &messageReader{c}
			c.reader = c.messageReader
			if c.readDecompress {
				c.reader = c.newDecompressionReader(c.reader)
			}
			return frameType, c.reader, nil
		}
	}

	// Applications that do handle the error returned from this method spin in
	// tight loop on connection failure. To help application developers detect
	// this error, panic on repeated reads to the failed connection.
	c.readErrCount++
	if c.readErrCount >= 1000 {
		panic("repeated read on failed websocket connection")
	}

	return noFrame, nil, c.readErr
}

type messageReader struct{ c *Conn }

func (r *messageReader) Read(b []byte) (int, error) {
	c := r.c
	if c.messageReader != r {
		return 0, io.EOF
	}

	for c.readErr == nil {

		if c.readRemaining > 0 {
			if int64(len(b)) > c.readRemaining {
				b = b[:c.readRemaining]

View on GitHub (pinned to e064f32e36)

Solutions

  1. Break out of the read loop as soon as ReadMessage returns an error and close the connection
  2. Always check and handle the read error; trigger reconnect logic outside the loop
  3. Ensure only one read goroutine exists per connection and that it exits on first error
  4. Optionally recover from this panic in the goroutine to prevent process crash, then clean up

Example fix

// before
for {
    _, msg, _ := conn.ReadMessage()
    process(msg)
}
// after
for {
    _, msg, err := conn.ReadMessage()
    if err != nil {
        conn.Close()
        break
    }
    process(msg)
}
Defensive patterns

Strategy: try-catch

Try / catch

for {
    _, msg, err := conn.ReadMessage()
    if err != nil {
        conn.Close()
        go reconnect()
        return
    }
    process(msg)
}

Prevention

When it happens

Trigger: Calling ReadMessage (or Reader.Read) in a loop ~1000+ times on a connection whose read already failed, typically ignoring the returned error and never closing the Conn.

Common situations: Read loops written as `for { _, _, _ = conn.ReadMessage() }` without checking err; goroutine leaks where a dead connection's reader is never shut down; hot loops that don't break on error.

Related errors


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