{"record":{"id":"e52ab68fadd77563","repo":"redis/redis-py","slug":"connection-closed-by-server-e52ab6","errorCode":null,"errorMessage":"Connection closed by server.","messagePattern":"Connection closed by server\\.","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/resp3.py","lineNumber":67,"sourceCode":"        else:\n            if self._buffer is not None:\n                try:\n                    self._buffer.purge()\n                except AttributeError:\n                    # Buffer may have been set to None by another thread after\n                    # the check above; result is still valid so we don't raise\n                    pass\n            return result\n\n    def _read_response(\n        self,\n        disable_decoding=False,\n        push_request=False,\n        timeout: Union[float, object] = SENTINEL,\n    ):\n        raw = self._buffer.readline(timeout=timeout)\n        if not raw:\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n\n        byte, response = raw[:1], raw[1:]\n\n        # server returned an error\n        if byte in (b\"-\", b\"!\"):\n            if byte == b\"!\":\n                response = self._buffer.read(int(response), timeout=timeout)\n            response = response.decode(\"utf-8\", errors=\"replace\")\n            error = self.parse_error(response)\n            # if the error is a ConnectionError, raise immediately so the user\n            # is notified\n            if isinstance(error, ConnectionError):\n                raise error\n            # otherwise, we're dealing with a ResponseError that might belong\n            # inside a pipeline response. the connection's read_response()\n            # and/or the pipeline's execute() will raise this error if\n            # necessary, so just return the exception instance here.\n            return error","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/redis/redis-py/blob/43bf5ac31a5d7538fad3b1928e717ba757048041/redis/_parsers/resp3.py#L49-L85","documentation":"Raised by the sync RESP3 pure-Python parser when SocketBuffer.readline() returns empty — the server closed the connection before a complete reply. It is the RESP3 equivalent of the RESP2 server-close guard.","triggerScenarios":"Any sync command via the pure-Python RESP3 parser when the server half-closes the socket: restart, `timeout`, CLIENT KILL, failover, proxy idle drop.","commonSituations":"Managed Redis/proxies reaping idle flows, rolling deploys, failover, maxmemory-clients eviction.","solutions":["Enable health_check_interval and retry_on_error=[ConnectionError, TimeoutError].","Set socket_keepalive=True.","Tune Redis `timeout`.","Catch ConnectionError and reconnect."],"exampleFix":"# before\nr = redis.Redis(host='redis', protocol=3)\nr.get('k')\n# after\nr = redis.Redis(host='redis', protocol=3, health_check_interval=30,\n                socket_keepalive=True,\n                retry_on_error=[redis.ConnectionError, redis.TimeoutError])","handlingStrategy":"retry","validationCode":"try:\n    assert r.ping()\nexcept redis.ConnectionError:\n    r.close(); r = redis.Redis(...)\nr.get('k')","typeGuard":"def is_closed_error(exc: Exception) -> bool:\n    from redis.exceptions import ConnectionError\n    return isinstance(exc, ConnectionError) and 'closed by server' in str(exc)","tryCatchPattern":"from redis.exceptions import ConnectionError\ntry:\n    return r.get('k')\nexcept ConnectionError:\n    r = redis.Redis(...)\n    return r.get('k')","preventionTips":["health_check_interval on every prod client.","retry_on_error for ConnectionError/TimeoutError.","socket_keepalive across LB/NAT."],"tags":["network","connection","sync","resp3"],"analyzedSha":"43bf5ac31a5d7538fad3b1928e717ba757048041","analyzedAt":"2026-08-06T22:35:38.048Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}