{"record":{"id":"5a0704eed488ab67","repo":"redis/redis-py","slug":"connection-closed-by-server","errorCode":null,"errorMessage":"Connection closed by server.","messagePattern":"Connection closed by server\\.","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/base.py","lineNumber":528,"sourceCode":"\n    __slots__ = AsyncBaseParser.__slots__ + (\"encoder\", \"_buffer\", \"_pos\", \"_chunks\")\n\n    def __init__(self, socket_read_size: int):\n        super().__init__(socket_read_size)\n        self.encoder: Optional[Encoder] = None\n        self._buffer = b\"\"\n        self._chunks = []\n        self._pos = 0\n\n    def _clear(self):\n        self._buffer = b\"\"\n        self._chunks.clear()\n\n    def on_connect(self, connection):\n        \"\"\"Called when the stream connects\"\"\"\n        self._stream = connection._reader\n        if self._stream is None:\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n        self.encoder = connection.encoder\n        self._clear()\n        self._connected = True\n\n    def on_disconnect(self):\n        \"\"\"Called when the stream disconnects\"\"\"\n        self._connected = False\n\n    @deprecated_function(\n        version=\"8.0.0\",\n        reason=\"Use can_read() instead\",\n        name=\"can_read_destructive\",\n    )\n    async def can_read_destructive(self) -> bool:\n        return await self.can_read()\n\n    async def can_read(self) -> bool:\n        # TODO: Rename this API; it detects pending data or dirty/closed","sourceCodeStart":510,"sourceCodeEnd":546,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/base.py#L510-L546","documentation":"Raised in _AsyncRESPBase.on_connect() (redis/_parsers/base.py:528) when connection._reader (the asyncio StreamReader) is None at the moment the parser's connect hook runs. This means the underlying asyncio transport was torn down or never created before the parser tried to use it, so there is no stream to read responses from. It surfaces as a ConnectionError carrying the message 'Connection closed by server.'","triggerScenarios":"Calling an async Redis command on a connection whose asyncio transport/reader was never attached or was concurrently disconnected. Happens when the event loop shuts down mid-handshake, when an explicit disconnect()/close() races with connect, or when a failover/sentinel path nulls the reader before on_connect fires.","commonSituations":"Event loop closed while connections are still being established; reusing an async Redis client after awaiting client.disconnect(); reconnect logic that hands a half-torn-down connection back to the pool; 'Task was destroyed but it is pending!' scenarios leaving readers unattached.","solutions":["Ensure the asyncio event loop is running for the whole lifetime of the async Redis client (create it inside an async context / the running loop).","Do not issue commands after calling disconnect()/aclose(); create a fresh redis.asyncio.Redis instance instead of reusing a torn-down one.","Guard against concurrent disconnect during connect (e.g., avoid closing the client from another task while a command is in flight).","If using a connection pool, let it manage reconnection rather than manually disconnecting individual connections."],"exampleFix":"# before\nr = redis.asyncio.Redis(...)\nawait r.disconnect()\nawait r.get(\"x\")  # reader already gone -> ConnectionError\n\n# after\nr = redis.asyncio.Redis(...)\nawait r.get(\"x\")  # keep client alive for its lifetime","handlingStrategy":"validation","validationCode":"# Ensure the event loop is running and the client is not torn down before use\nimport asyncio, redis.asyncio as redis\n\nasync def safe_get(client, key):\n    if asyncio.get_event_loop().is_closed():\n        raise RuntimeError(\"event loop closed; create a new client in a running loop\")\n    # reconnect transparently if the pool permits it\n    return await client.get(key)","typeGuard":null,"tryCatchPattern":"try:\n    await r.get(\"k\")\nexcept redis.ConnectionError:\n    # reader was gone at connect; build a fresh client in a running loop\n    r = redis.Redis(...)\n    await r.get(\"k\")","preventionTips":["Create redis.asyncio.Redis inside the running event loop, not at import time in a different loop.","Never issue commands after await client.aclose()/disconnect(); instantiate a new client instead.","Keep the event loop alive for the entire lifetime of the async client."],"tags":["network","async","connection","event-loop"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}