redis/redis-py · error · OSError
Buffer is closed.
Error message
Buffer is closed.
What it means
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.').
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.
Example fix
# before
while True:
msg = await pubsub.get_message() # may hit 'Buffer is closed.' on a dead conn
# after
try:
msg = await pubsub.get_message()
except OSError:
await pubsub.close()
pubsub = r.pubsub()
await pubsub.subscribe("ch")
continue Defensive patterns
Strategy: try-catch
Validate before calling
# Track pubsub/connection liveness yourself instead of probing a dead connection
if not getattr(parser, "_connected", False):
# skip the probe; reconnect instead
pass Try / catch
try:
msg = await pubsub.get_message()
except OSError:
# 'Buffer is closed.' -> connection torn down
await pubsub.close()
pubsub = r.pubsub()
await pubsub.subscribe("ch") Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- pubsub connection not set: did you forget to call…
- A non health check response was cleaned by execute_command
- Bad response from PING health check
- Buffer is closed.
- Cannot enable maintenance notifications for connection…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/4b1bb8b9a9c8ff7e.
Report an issue: GitHub.
Appendix: source
Thrown at redis/_parsers/base.py:549
self._connected = True
def on_disconnect(self):
"""Called when the stream disconnects"""
self._connected = False
@deprecated_function(
version="8.0.0",
reason="Use can_read() instead",
name="can_read_destructive",
)
async def can_read_destructive(self) -> bool:
return await self.can_read()
async def can_read(self) -> bool:
# TODO: Rename this API; it detects pending data or dirty/closed
# connection state, not only whether application data can be read.
if not self._connected:
raise OSError("Buffer is closed.")
if self._buffer:
return True
# asyncio.StreamReader has no public non-destructive API for checking
# buffered bytes. Preserve dirty-connection detection for the Python
# parser and fail loudly if the private buffer API changes.
return bool(self._stream._buffer) or self._stream.at_eof()
async def _read(self, length: int) -> bytes:
"""
Read `length` bytes of data. These are assumed to be followed
by a '\r\n' terminator which is subsequently discarded.
"""
want = length + 2
end = self._pos + want
if len(self._buffer) >= end:
result = self._buffer[self._pos : end - 2]
else:
tail = self._buffer[self._pos :]View on GitHub (pinned to 6a6b581b48)