{"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/da03cdc7e8731092b13e395605c3c1fb2de25de1/redis/_parsers/hiredis.py#L145-L181","documentation":"Raised by _HiredisParser.can_read() at the very first check: 'if not self._reader'. The reader is set in on_connect() and cleared in on_disconnect(); a falsy reader means the connection is not live. can_read() is called by PubSub and the connection-pool health check, so this surfaces when polling a disconnected connection. ConnectionError, error_type=NETWORK.","triggerScenarios":"Calling pubsub.get_message()/connection.can_read() on a hiredis-backed connection whose on_disconnect() already set self._reader=None - server closed, client.disconnect(), pool evicted it, or 'with client:' exited. Sibling of error 1 (which is the same check for the pure-Python async parser).","commonSituations":"Sync PubSub reused after a drop; pool health-check pinging a connection that another thread just disconnected; server idle-timeout killing the TCP flow; forking a process and reusing the parent's connection.","solutions":["Do not reuse a PubSub/connection after disconnect; obtain a new one from the pool/client.","Configure retry_on_error=[ConnectionError] with a backoff so transient drops reconnect transparently.","Raise server tcp-keepalive/timeout or send periodic PINGs to keep the flow alive.","Ensure forked children create their own Redis client."],"exampleFix":"// before\nps = r.pubsub(ignore_subscribe_messages=True)\nps.subscribe(\"ch\")\n# ... server restarts ...\nps.get_message()  # ConnectionError: reader is None\n\n// after\ntry:\n    ps.get_message(timeout=1)\nexcept redis.exceptions.ConnectionError:\n    ps.close()\n    ps = r.pubsub(ignore_subscribe_messages=True)\n    ps.subscribe(\"ch\")","handlingStrategy":"try-catch","validationCode":"# Check the parser/reader is live before polling a sync pubsub\ndef pubsub_ready(ps):\n    return ps.connection is not None and ps.connection._parser is not None","typeGuard":null,"tryCatchPattern":"try:\n    ps.get_message(timeout=1)\nexcept redis.exceptions.ConnectionError as e:\n    if \"Connection closed by server\" in str(e):\n        ps.close()\n        ps = r.pubsub(); ps.subscribe(*channels)","preventionTips":["Never poll a PubSub/connection after disconnect(); acquire a new one.","Configure retry_on_error=[ConnectionError] for transparent reconnects.","Use health_check_interval so dead connections are detected early.","Keep connections warm with PING during long idle periods."],"tags":["hiredis","connection","pubsub","sync"],"analyzedSha":"da03cdc7e8731092b13e395605c3c1fb2de25de1","analyzedAt":"2026-08-04T20:26:47.563Z","schemaVersion":2}