redis/redis-py · error · ConnectionError
Connection closed by server.
Error message
Connection closed by server.
What it means
Raised by _HiredisParser.can_read() at the very first check: 'if not self._reader'. The reader is set in on_connect() and cleared in on_disconnect(); a falsy reader means the connection is not live. can_read() is called by PubSub and the connection-pool health check, so this surfaces when polling a disconnected connection. ConnectionError, error_type=NETWORK.
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._readerView on GitHub (pinned to da03cdc7e8)
Solutions
- Do not reuse a PubSub/connection after disconnect; obtain a new one from the pool/client.
- Configure retry_on_error=[ConnectionError] with a backoff so transient drops reconnect transparently.
- Raise server tcp-keepalive/timeout or send periodic PINGs to keep the flow alive.
- Ensure forked children create their own Redis client.
Example fix
// before
ps = r.pubsub(ignore_subscribe_messages=True)
ps.subscribe("ch")
# ... server restarts ...
ps.get_message() # ConnectionError: reader is None
// after
try:
ps.get_message(timeout=1)
except redis.exceptions.ConnectionError:
ps.close()
ps = r.pubsub(ignore_subscribe_messages=True)
ps.subscribe("ch") Defensive patterns
Strategy: try-catch
Validate before calling
# Check the parser/reader is live before polling a sync pubsub
def pubsub_ready(ps):
return ps.connection is not None and ps.connection._parser is not None Try / catch
try:
ps.get_message(timeout=1)
except redis.exceptions.ConnectionError as e:
if "Connection closed by server" in str(e):
ps.close()
ps = r.pubsub(); ps.subscribe(*channels) Prevention
- Never poll a PubSub/connection after disconnect(); acquire a new one.
- Configure retry_on_error=[ConnectionError] for transparent reconnects.
- Use health_check_interval so dead connections are detected early.
- Keep connections warm with PING during long idle periods.
When it happens
Trigger: Calling pubsub.get_message()/connection.can_read() on a hiredis-backed connection whose on_disconnect() already set self._reader=None - server closed, client.disconnect(), pool evicted it, or 'with client:' exited. Sibling of error 1 (which is the same check for the pure-Python async parser).
Common situations: Sync PubSub reused after a drop; pool health-check pinging a connection that another thread just disconnected; server idle-timeout killing the TCP flow; forking a process and reusing the parent's connection.
Related errors
- Buffer is closed.
- Hiredis is not installed
- A non health check response was cleaned by execute_command:
- Maintenance notifications are only supported with hiredis an
- Connection closed by server.
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/a1b09dc13c03d8d8.json.
Report an issue: GitHub.