{"record":{"id":"5e529c5e9fbbab90","repo":"redis/redis-py","slug":"buffer-is-closed-5e529c","errorCode":null,"errorMessage":"Buffer is closed.","messagePattern":"Buffer is closed\\.","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/hiredis.py","lineNumber":325,"sourceCode":"            )\n        except AttributeError:\n            # hiredis < 3.2\n            self._hiredis_PushNotificationType = None\n\n    def on_disconnect(self):\n        self._connected = False\n\n    @deprecated_function(\n        version=\"8.0.0\", reason=\"Use can_read() instead\", 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\n        # connection state, not only whether application data can be read.\n        if not self._connected:\n            raise OSError(\"Buffer is closed.\")\n        # EOF means the connection is closed and not safe to reuse.\n        if self._reader.has_data() or self._stream.at_eof():\n            return True\n        # asyncio.StreamReader has no public non-destructive API for checking\n        # buffered bytes. Preserve dirty-connection detection for hiredis; tests\n        # with a real StreamReader guard this private buffer API in CI.\n        return bool(self._stream._buffer)\n\n    async def read_from_socket(self):\n        buffer = await self._stream.read(self._read_size)\n        if not buffer or not isinstance(buffer, bytes):\n            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None\n        self._reader.feed(buffer)\n        # data was read from the socket and added to the buffer.\n        # return True to indicate that data was read.\n        return True\n\n    async def read_response(","sourceCodeStart":307,"sourceCodeEnd":343,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/hiredis.py#L307-L343","documentation":"Raised by the async hiredis parser's can_read() when self._connected is False: on_disconnect() ran (e.g. the asyncio task was cancelled or another task closed the connection), so the parser's buffer is closed. Notably this is raised as the builtin OSError, NOT redis.exceptions.ConnectionError - so a 'except ConnectionError' handler will miss it.","triggerScenarios":"Calling can_read() on an async connection after it was disconnected: pubsub readiness checks, health checks, or manual probing of a connection whose owning task was cancelled / whose client was closed, or after the pool disconnected it.","commonSituations":"Cancelling an asyncio pubsub task and then probing its connection; probing a connection after asyncio cancellation propagated through on_disconnect; a shared connection used after close().","solutions":["Treat a closed parser as 'no data'; catch OSError around can_read().","Let the connection pool hand out fresh connections rather than probing dead ones.","Guard pubsub loops with a flag set on disconnect/cancel.","Check the connection's connected state before probing."],"exampleFix":"# before\nready = await pubsub.connection.can_read()  # after disconnect -> OSError: Buffer is closed.\n\n# after - guard the probe and treat closed as 'no data'\ntry:\n    ready = await pubsub.connection.can_read()\nexcept OSError:\n    ready = False  # connection gone; reconnect","handlingStrategy":"try-catch","validationCode":"# Track lifecycle so you don't probe a closed parser\nasync def safe_can_read(conn):\n    if not getattr(conn, '_connected', False):\n        return False\n    try:\n        return await conn.can_read()\n    except OSError:\n        return False","typeGuard":"def is_closed_buffer(e: BaseException) -> bool:\n    return isinstance(e, OSError) and 'buffer is closed' in str(e).lower()","tryCatchPattern":"try:\n    ready = await conn.can_read()\nexcept OSError:\n    ready = False","preventionTips":["This surfaces as OSError, not redis.exceptions.ConnectionError - your 'except ConnectionError' will miss it.","Set a 'closing' event when cancelling pubsub/long-running tasks so readiness probes short-circuit."],"tags":["async","hiredis","lifecycle","oserror","buffer-closed"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}