{"record":{"id":"4b1bb8b9a9c8ff7e","repo":"redis/redis-py","slug":"buffer-is-closed","errorCode":null,"errorMessage":"Buffer is closed.","messagePattern":"Buffer is closed\\.","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"redis/_parsers/base.py","lineNumber":549,"sourceCode":"        self._connected = True\n\n    def on_disconnect(self):\n        \"\"\"Called when the stream disconnects\"\"\"\n        self._connected = False\n\n    @deprecated_function(\n        version=\"8.0.0\",\n        reason=\"Use can_read() instead\",\n        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        if self._buffer:\n            return True\n        # asyncio.StreamReader has no public non-destructive API for checking\n        # buffered bytes. Preserve dirty-connection detection for the Python\n        # parser and fail loudly if the private buffer API changes.\n        return bool(self._stream._buffer) or self._stream.at_eof()\n\n    async def _read(self, length: int) -> bytes:\n        \"\"\"\n        Read `length` bytes of data.  These are assumed to be followed\n        by a '\\r\\n' terminator which is subsequently discarded.\n        \"\"\"\n        want = length + 2\n        end = self._pos + want\n        if len(self._buffer) >= end:\n            result = self._buffer[self._pos : end - 2]\n        else:\n            tail = self._buffer[self._pos :]","sourceCodeStart":531,"sourceCodeEnd":567,"githubUrl":"https://github.com/redis/redis-py/blob/6a6b581b48225afa0b76912d1028c6035baee932/redis/_parsers/base.py#L531-L567","documentation":"Raised in _AsyncRESPBase.can_read() (redis/_parsers/base.py:549) and the async hiredis parser's can_read() when self._connected is False. It signals that the parser's internal buffer/stream has been torn down by on_disconnect(), so a readiness check cannot proceed. The literal raised is OSError('Buffer is closed.').","triggerScenarios":"Calling can_read() (directly or via pubsub/health-check/connection-pool readiness probing) on an async connection that already had on_disconnect() invoked. Typical in pubsub get_message loops, keepalive checks, or failover paths that probe a dropped connection.","commonSituations":"A pubsub loop continuing to poll after the server dropped the connection; reusing a connection object after an explicit disconnect; failover scenarios where the old node is probed after being marked dead.","solutions":["Catch OSError('Buffer is closed.') and treat it as 'connection must be re-established', then reconnect/resubscribe.","Track connection liveness yourself and stop polling once disconnected, instead of relying on can_read() to fail.","Pull a fresh connection from the pool rather than reusing the disconnected one.","For pubsub, use the high-level pubsub() API with get_message(ignore_subscribe_messages=True) and handle None/reconnect rather than probing raw connections."],"exampleFix":"# before\nwhile True:\n    msg = await pubsub.get_message()  # may hit 'Buffer is closed.' on a dead conn\n\n# after\ntry:\n    msg = await pubsub.get_message()\nexcept OSError:\n    await pubsub.close()\n    pubsub = r.pubsub()\n    await pubsub.subscribe(\"ch\")\n    continue","handlingStrategy":"try-catch","validationCode":"# Track pubsub/connection liveness yourself instead of probing a dead connection\nif not getattr(parser, \"_connected\", False):\n    # skip the probe; reconnect instead\n    pass","typeGuard":null,"tryCatchPattern":"try:\n    msg = await pubsub.get_message()\nexcept OSError:\n    # 'Buffer is closed.' -> connection torn down\n    await pubsub.close()\n    pubsub = r.pubsub()\n    await pubsub.subscribe(\"ch\")","preventionTips":["Track a 'connected' flag and stop polling once disconnected.","For pubsub, use the high-level pubsub() API and resubscribe on OSError rather than probing raw connections.","Pull fresh connections from the pool instead of reusing disconnected ones."],"tags":["async","connection","pubsub","lifecycle"],"backgroundTag":null,"analyzedSha":"6a6b581b48225afa0b76912d1028c6035baee932","analyzedAt":"2026-08-10T12:52:44.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}