{"record":{"id":"6db4f9fbae128bc0","repo":"redis/redis-py","slug":"connection-closed-by-server-6db4f9","errorCode":null,"errorMessage":"Connection closed by server.","messagePattern":"Connection closed by server\\.","errorType":"exception","errorClass":"ConnectionError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/socket.py","lineNumber":67,"sourceCode":"        timeout: Union[float, object] = SENTINEL,\n        raise_on_timeout: Optional[bool] = True,\n    ) -> bool:\n        sock = self._sock\n        socket_read_size = self.socket_read_size\n        marker = 0\n        custom_timeout = timeout is not SENTINEL\n\n        buf = self._buffer\n        current_pos = buf.tell()\n        buf.seek(0, SEEK_END)\n        if custom_timeout:\n            sock.settimeout(timeout)\n        try:\n            while True:\n                data = sock.recv(socket_read_size)\n                # an empty string indicates the server shutdown the socket\n                if isinstance(data, bytes) and len(data) == 0:\n                    raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR)\n                buf.write(data)\n                data_length = len(data)\n                marker += data_length\n\n                if length is not None and length > marker:\n                    continue\n                return True\n        except socket.timeout:\n            if raise_on_timeout:\n                raise TimeoutError(\"Timeout reading from socket\")\n            return False\n        except NONBLOCKING_EXCEPTIONS as ex:\n            # if we're in nonblocking mode and the recv raises a\n            # blocking error, simply return False indicating that\n            # there's no data to be read. otherwise raise the\n            # original exception.\n            allowed = NONBLOCKING_EXCEPTION_ERROR_NUMBERS.get(ex.__class__, -1)\n            if ex.errno == allowed:","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/socket.py#L49-L85","documentation":"The lowest-level read: SocketBuffer._read_from_socket raises redis.exceptions.ConnectionError when sock.recv() returns empty bytes (b'') - the OS-level signal that the peer closed the TCP connection. This is where every 'Connection closed by server.' in the pure-Python parsers originates; the RESP2/RESP3 readline EOF (errors 28/32) ultimately traces here (socket.py:66-67).","triggerScenarios":"Any sync read path on a pure-Python (non-hiredis) connection whose peer has closed: restart, failover, CLIENT KILL, idle timeout, eviction, network drop, proxy reaper. recv() returning b'' is the precise trigger.","commonSituations":"Pure-Python parser (hiredis not installed/used) on a long-lived connection behind an idle reaper; Redis failover; server under memory pressure dropping clients; misconfigured 'timeout'.","solutions":["Configure retry_on_error=[ConnectionError, TimeoutError] + Retry/backoff.","Enable health_check_interval and socket_keepalive.","Set socket_timeout larger than any blocking-command block time.","Catch redis.exceptions.ConnectionError and reconnect/retry."],"exampleFix":"# before\nr = redis.Redis(parser_class=redis.connection.DefaultParser)  # pure-Python, no retry\nr.get('k')  # server closed -> ConnectionError: Connection closed by server.\n\n# after\nfrom redis.retry import Retry\nfrom redis.backoff import ExponentialWithJitterBackoff\nfrom redis.exceptions import ConnectionError, TimeoutError\nr = redis.Redis(\n    health_check_interval=30, socket_keepalive=True, socket_timeout=5,\n    retry_on_error=[ConnectionError, TimeoutError],\n    retry=Retry(ExponentialWithJitterBackoff(), 3),\n)\nr.get('k')","handlingStrategy":"retry","validationCode":"from redis.exceptions import ConnectionError\n\ndef alive(r):\n    try:\n        r.ping()\n        return True\n    except ConnectionError:\n        return False","typeGuard":"from redis.exceptions import ConnectionError as RCE\n\ndef is_socket_eof(e: BaseException) -> bool:\n    return isinstance(e, RCE) and 'closed by server' in str(e).lower()","tryCatchPattern":"from redis.exceptions import ConnectionError, TimeoutError\ntry:\n    r.get('k')\nexcept (ConnectionError, TimeoutError):\n    r.get('k')","preventionTips":["redis.exceptions.ConnectionError does not subclass the builtin - catch the redis class.","Health checks catch stale connections before your command does."],"tags":["network","connection","sync","socket","server-closed"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}