{"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/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/_parsers/base.py#L510-L546","documentation":"Raised by the async RESP parser's on_connect() when connection._reader (the asyncio.StreamReader) is None at the moment the parser is wired up. The parser cannot read any bytes without a stream, so it refuses to connect rather than fail later with an opaque AttributeError. It is a ConnectionError (error_type=NETWORK) so existing retry/backoff logic treats it as retryable.","triggerScenarios":"Triggered when an asyncio Redis connection initializes its parser before the StreamReader is set on the connection - e.g. the transport was closed/dropped between socket creation and parser.on_connect(), or a manually constructed Connection object was used without a live transport. Most commonly surfaces inside redis.asyncio.Connection.connect() if the transport callback fired with an error.","commonSituations":"Event loop being closed/replaced while a connection is mid-handshake; using redis.asyncio inside a forked process without reconnecting; SSL handshake failure that tears down the transport before on_connect runs; test fixtures that mock the connection but leave _reader unset.","solutions":["Ensure the asyncio event loop running the client is the same one that created it and stays alive for the connection's lifetime.","Do not share or fork Redis async connections across event loops; create a fresh redis.asyncio.Redis after fork/loop restart.","If using SSL/TLS, verify the certificate and endpoint are reachable so the handshake completes and a transport is installed.","Wrap connect in a retry (redis sets retry_on_error=[ConnectionError] / retry strategy) to re-establish a clean transport."],"exampleFix":"// before\nr = redis.asyncio.Redis(host=..., port=...)\nawait r.get(\"k\")  # loop was closed/replaced since construction\n\n// after\nr = redis.asyncio.Redis(host=..., port=..., retry=Retry(ExponentialBackoff(), 3),\n                       retry_on_error=[redis.exceptions.ConnectionError])\nawait r.get(\"k\")","handlingStrategy":"retry","validationCode":"# Verify an event loop is running and the connection can open before first use\nimport asyncio, redis.asyncio as redis\nassert asyncio.get_running_loop() is not None, \"call inside an async context\"\nasync def health(r):\n    try:\n        await r.ping()\n        return True\n    except (redis.ConnectionError, OSError):\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    await r.ping()\nexcept redis.exceptions.ConnectionError as e:\n    if \"Connection closed by server\" in str(e):\n        # transport not installed; recreate client on the current loop\n        await r.close()\n        r = redis.asyncio.Redis(...)\n        await r.ping()","preventionTips":["Create redis.asyncio.Redis inside the async context that will use it, never before the loop starts.","Do not reuse an async client across event loops or after loop restart; reconstruct it.","Verify TLS endpoints are reachable so the transport handshake installs a reader.","In forked children, discard the parent's async client and create a new one."],"tags":["network","async","connection","parser"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}