{"record":{"id":"a1b09dc13c03d8d8","repo":"redis/redis-py","slug":"connection-closed-by-server-a1b09d","errorCode":null,"errorMessage":"Connection closed by server.","messagePattern":"Connection closed by server\\.","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/hiredis.py","lineNumber":163,"sourceCode":"        if connection.encoder.decode_responses:\n            kwargs[\"encoding\"] = connection.encoder.encoding\n        self._reader = hiredis.Reader(**kwargs)\n\n        try:\n            self._hiredis_PushNotificationType = hiredis.PushNotification\n        except AttributeError:\n            # hiredis < 3.2\n            self._hiredis_PushNotificationType = None\n\n    def on_disconnect(self):\n        self._sock = None\n        self._reader = None\n\n    def can_read(self, timeout: float = 0) -> bool:\n        # TODO: Rename this API; it detects pending data or dirty/closed\n        # connection state, not only whether application data can be read.\n        if not self._reader:\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n\n        if self._reader.has_data():\n            return True\n        if not _socket_can_read(self._sock, timeout):\n            return False\n        # the socket reports readable but the reader has no buffered data. a\n        # server-closed socket also reads as ready (it yields EOF), so tell the\n        # two apart with a non-destructive poll: a peer-closed socket must not be\n        # reused, while a readable-but-open socket may just hold a pending push.\n        # this mirrors how the pure-Python parser (recv -> b\"\") and the async\n        # parser (StreamReader.at_eof()) already signal a closed connection.\n        if _socket_is_closed(self._sock):\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n        return True\n\n    def read_from_socket(self, timeout=SENTINEL, raise_on_timeout=True):\n        sock = self._sock\n        reader = self._reader","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/hiredis.py#L145-L181","documentation":"Raised in _HiredisParser.can_read() (redis/_parsers/hiredis.py:163) when self._reader is falsy — i.e., on_disconnect() has already cleared the hiredis reader (set it to None) but can_read() is still called. The parser has been torn down, so no readiness check is possible; it raises ConnectionError('Connection closed by server.').","triggerScenarios":"Probing a sync hiredis-backed connection's readability after it was disconnected: pubsub get_message loops, connection-pool liveness checks, or failover probes that touch a connection whose on_disconnect() already ran.","commonSituations":"A pubsub/health loop continuing after the server dropped the connection; reusing a connection after explicit disconnect; a shared client closed via 'with redis:' while another thread probes it.","solutions":["Catch ConnectionError from can_read() and treat it as 'discard and reconnect'.","Track liveness externally and stop probing once disconnected.","Let the connection pool hand out a fresh connection instead of probing a dead one.","Avoid closing a shared client from one thread while another inspects it."],"exampleFix":"# before\nif conn.can_read():  # raises on a disconnected hiredis conn\n    ...\n\n# after\ntry:\n    ready = conn.can_read()\nexcept redis.ConnectionError:\n    conn = pool.get_connection()\n    ready = False","handlingStrategy":"try-catch","validationCode":"# Avoid probing a parser whose reader is already torn down\nif not getattr(parser, \"_reader\", None):\n    # reader cleared by on_disconnect(); reconnect instead of probing\n    conn = pool.get_connection()","typeGuard":null,"tryCatchPattern":"try:\n    ready = conn.can_read()\nexcept redis.ConnectionError:\n    # hiredis reader is None (disconnected) -> discard and reconnect\n    conn.disconnect()\n    conn = pool.get_connection()","preventionTips":["Track connection liveness and stop probing once disconnected.","Let the connection pool hand out fresh connections rather than reusing dead ones.","Avoid closing a shared client from another thread while a probe is in flight."],"tags":["network","hiredis","connection","lifecycle"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}