redis/redis-py · error · ConnectionError
Connection has data
Error message
Connection has data
What it means
When the pool hands out a connection, ensure_connection checks can_read(); if unread bytes are already on the socket and maintenance notifications are not enabled, it raises ConnectionError('Connection has data') to avoid a protocol desync. This signals a previous command left the socket dirty. The pool then disconnects/reconnects and re-checks, raising 'Connection not ready' only if data persists.
Solutions
- Let the pool self-heal: it disconnects and reconnects, so a retry of the operation usually succeeds.
- Avoid abandoning commands/pipelines mid-flight (let MULTI/EXEC and pipelines complete).
- If it recurs, investigate a protocol desync, a misbehaving proxy, or a buggy custom parser.
Defensive patterns
Strategy: retry
Try / catch
from redis.exceptions import ConnectionError
for attempt in range(retries):
try:
return await client.get(key)
except ConnectionError as e:
if 'Connection has data' not in str(e):
raise
await asyncio.sleep(backoff(attempt))
raise Prevention
- Let commands/pipelines/MULTI complete instead of cancelling mid-flight.
- Rely on the pool's automatic reconnect; transient 'has data' usually clears on retry.
When it happens
Trigger: A connection returned to the pool with unread bytes (a cancelled asyncio task mid-command, an abandoned pipeline/MULTI, or unparsed server data); the next ensure_connection sees data on the reused socket.
Common situations: Cancelled tasks that abort a command mid-flight; partial transactions; custom parser bugs; server pushing data the client never read.
Related errors
- Connection not ready
- Bad response from PING health check
- A non health check response was cleaned by execute_command
- Buffer is closed.
- Cannot enable maintenance notifications for connection…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/cd7147f89f16e38d.
Report an issue: GitHub.
Appendix: source
Thrown at redis/asyncio/connection.py:2876
decode_responses=kwargs.get("decode_responses", False),
)
def make_connection(self):
"""Create a new connection. Can be overridden by child classes."""
# Note: We don't record IDLE here because async uses a sync make_connection
# but async record_connection_count. The recording is handled in get_connection.
return self.connection_class(**self.connection_kwargs)
async def ensure_connection(self, connection: AbstractConnection):
"""Ensure that the connection object is connected and valid"""
await connection.connect()
# connections that the pool provides should be ready to send
# a command. if not, the connection was either returned to the
# pool before all data has been read or the socket has been
# closed. either way, reconnect and verify everything is good.
try:
if await connection.can_read() and not self.maint_notifications_enabled():
raise ConnectionError("Connection has data") from None
except (ConnectionError, TimeoutError, OSError):
await connection.disconnect()
await connection.connect()
if await connection.can_read() and not self.maint_notifications_enabled():
raise ConnectionError("Connection not ready") from None
async def release(self, connection: AbstractConnection):
"""Releases the connection back to the pool"""
# Connections should always be returned to the correct pool,
# not doing so is an error that will cause an exception here.
async with self._lock:
self._in_use_connections.remove(connection)
if connection.should_reconnect():
await connection.disconnect()
self._available_connections.append(connection)
View on GitHub (pinned to 6a6b581b48)