redis/redis-py · error · ConnectionError

Connection closed by server.

Error message

Connection closed by server.

What it means

Raised in _HiredisParser.can_read() (redis/_parsers/hiredis.py:163) when self._reader is falsy — i.e., on_disconnect() has already cleared the hiredis reader (set it to None) but can_read() is still called. The parser has been torn down, so no readiness check is possible; it raises ConnectionError('Connection closed by server.').

Solutions

  1. Catch ConnectionError from can_read() and treat it as 'discard and reconnect'.
  2. Track liveness externally and stop probing once disconnected.
  3. Let the connection pool hand out a fresh connection instead of probing a dead one.
  4. Avoid closing a shared client from one thread while another inspects it.

Example fix

# before
if conn.can_read():  # raises on a disconnected hiredis conn
    ...

# after
try:
    ready = conn.can_read()
except redis.ConnectionError:
    conn = pool.get_connection()
    ready = False
Defensive patterns

Strategy: try-catch

Validate before calling

# Avoid probing a parser whose reader is already torn down
if not getattr(parser, "_reader", None):
    # reader cleared by on_disconnect(); reconnect instead of probing
    conn = pool.get_connection()

Try / catch

try:
    ready = conn.can_read()
except redis.ConnectionError:
    # hiredis reader is None (disconnected) -> discard and reconnect
    conn.disconnect()
    conn = pool.get_connection()

Prevention

When it happens

Trigger: Probing a sync hiredis-backed connection's readability after it was disconnected: pubsub get_message loops, connection-pool liveness checks, or failover probes that touch a connection whose on_disconnect() already ran.

Common situations: A pubsub/health loop continuing after the server dropped the connection; reusing a connection after explicit disconnect; a shared client closed via 'with redis:' while another thread probes it.

Understand the failure class

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/a1b09dc13c03d8d8. Report an issue: GitHub.

Appendix: source

Thrown at redis/_parsers/hiredis.py:163

        if connection.encoder.decode_responses:
            kwargs["encoding"] = connection.encoder.encoding
        self._reader = hiredis.Reader(**kwargs)

        try:
            self._hiredis_PushNotificationType = hiredis.PushNotification
        except AttributeError:
            # hiredis < 3.2
            self._hiredis_PushNotificationType = None

    def on_disconnect(self):
        self._sock = None
        self._reader = None

    def can_read(self, timeout: float = 0) -> bool:
        # TODO: Rename this API; it detects pending data or dirty/closed
        # connection state, not only whether application data can be read.
        if not self._reader:
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)

        if self._reader.has_data():
            return True
        if not _socket_can_read(self._sock, timeout):
            return False
        # the socket reports readable but the reader has no buffered data. a
        # server-closed socket also reads as ready (it yields EOF), so tell the
        # two apart with a non-destructive poll: a peer-closed socket must not be
        # reused, while a readable-but-open socket may just hold a pending push.
        # this mirrors how the pure-Python parser (recv -> b"") and the async
        # parser (StreamReader.at_eof()) already signal a closed connection.
        if _socket_is_closed(self._sock):
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)
        return True

    def read_from_socket(self, timeout=SENTINEL, raise_on_timeout=True):
        sock = self._sock
        reader = self._reader

View on GitHub (pinned to 6a6b581b48)