{"id":"94dff8eb4da265ba","repo":"redis/redis-py","slug":"connection-not-ready","errorCode":null,"errorMessage":"Connection not ready","messagePattern":"Connection not ready","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"warning","filePath":"redis/asyncio/connection.py","lineNumber":2878,"sourceCode":"        # Note: We don't record IDLE here because async uses a sync make_connection\n        # but async record_connection_count. The recording is handled in get_connection.\n        return self.connection_class(**self.connection_kwargs)\n\n    async def ensure_connection(self, connection: AbstractConnection):\n        \"\"\"Ensure that the connection object is connected and valid\"\"\"\n        await connection.connect()\n        # connections that the pool provides should be ready to send\n        # a command. if not, the connection was either returned to the\n        # pool before all data has been read or the socket has been\n        # closed. either way, reconnect and verify everything is good.\n        try:\n            if await connection.can_read() and not self.maint_notifications_enabled():\n                raise ConnectionError(\"Connection has data\") from None\n        except (ConnectionError, TimeoutError, OSError):\n            await connection.disconnect()\n            await connection.connect()\n            if await connection.can_read() and not self.maint_notifications_enabled():\n                raise ConnectionError(\"Connection not ready\") from None\n\n    async def release(self, connection: AbstractConnection):\n        \"\"\"Releases the connection back to the pool\"\"\"\n        # Connections should always be returned to the correct pool,\n        # not doing so is an error that will cause an exception here.\n        async with self._lock:\n            self._in_use_connections.remove(connection)\n\n            if connection.should_reconnect():\n                await connection.disconnect()\n\n            self._available_connections.append(connection)\n\n        await self._event_dispatcher.dispatch_async(\n            AsyncAfterConnectionReleasedEvent(connection)\n        )\n\n        # Record state transition: USED -> IDLE","sourceCodeStart":2860,"sourceCodeEnd":2896,"githubUrl":"https://github.com/redis/redis-py/blob/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/asyncio/connection.py#L2860-L2896","documentation":"Raised by ensure_connection() after the pool already detected unread data ([128]), disconnected, reconnected, and STILL finds data on the socket. This means even a fresh connection is not clean, indicating a deeper socket/transport problem rather than a one-off half-read. The second check at connection.py:2877 trips.","triggerScenarios":"ensure_connection reconnects a dirty connection and can_read() is True again after reconnect. The disconnect+connect cycle did not yield a quiescent socket.","commonSituations":"Persistent protocol desync; a proxy or intermediary (e.g., stunnel, a buggy sidecar) injecting data; server pushing unsolicited data on a non-maintenance-notifications connection; kernel/loopback buffering oddities.","solutions":["Verify nothing is pushing unsolicited data on the connection (disable server-pushed features, or enable maintenance notifications if the server is a managed Redis sending MOVING notices).","Route around proxies/sidecars that may inject bytes and test against the Redis directly.","Report with repro details if it happens with a direct connection and standard usage."],"exampleFix":"// before\n# non-maintenance pool receiving server-pushed maintenance frames\nredis = Redis(host=..., port=...)\n// after\nredis = Redis(host=..., port=...,\n             maint_notifications_config=MaintNotificationsConfig(enabled=True))","handlingStrategy":"try-catch","validationCode":"# if the server is a managed Redis pushing maintenance frames, enable the feature\nif server_is_managed and not redis.connection_pool.maint_notifications_enabled():\n    await redis.connection_pool.update_maint_notifications_config(\n        MaintNotificationsConfig(enabled=True))","typeGuard":"def pool_accepts_pushed_data(pool) -> bool:\n    return pool.maint_notifications_enabled()","tryCatchPattern":"from redis.exceptions import ConnectionError\ntry:\n    await pool.get_connection('GET')\nexcept ConnectionError as e:\n    if 'not ready' in str(e):\n        # investigate unsolicited data source; test direct connection\n        await pool.disconnect()","preventionTips":["Enable maintenance notifications if the server pushes frames.","Remove proxies/sidecars that may inject bytes when isolating the issue.","Report reproducible cases with a direct-connection trace."],"tags":["pool","connection-health","protocol","asyncio"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}