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

  1. Let the pool self-heal: it disconnects and reconnects, so a retry of the operation usually succeeds.
  2. Avoid abandoning commands/pipelines mid-flight (let MULTI/EXEC and pipelines complete).
  3. 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

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


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)