redis/redis-py · error · OSError
Buffer is closed.
Error message
Buffer is closed.
What it means
Raised by _AsyncRESPBase.can_read() (the async Python parser) when self._connected is False. can_read() is called by PubSub/health checks to detect pending data on a connection; calling it after on_disconnect() set _connected=False is undefined, so the parser raises OSError('Buffer is closed.') Note this is an OSError, not a ConnectionError - the connection object is in a terminal state and should not be reused.
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 da03cdc7e8)
Solutions
- Check pubsub.connection or the client's connection state before polling, or catch OSError and resubscribe on a fresh connection.
- Use the high-level pubsub context manager / reconnect helpers so a new connection is acquired automatically.
- Raise server tcp-keepalive/timeout values or send periodic PING commands to keep the connection alive.
- Do not reuse a PubSub or connection object after an explicit disconnect() or context-manager exit.
Example fix
// before
ps = r.pubsub()
await ps.subscribe("ch")
# ... connection drops, then:
msg = await ps.get_message() # OSError: Buffer is closed.
// after
try:
msg = await ps.get_message(timeout=1)
except OSError:
await ps.close()
ps = r.pubsub()
await ps.subscribe("ch")
msg = await ps.get_message(timeout=1) Defensive patterns
Strategy: try-catch
Validate before calling
# Before polling pubsub, check connection liveness
async def pubsub_alive(ps):
conn = getattr(ps, "connection", None)
return conn is not None and getattr(conn, "_reader", None) is not None Try / catch
try:
msg = await ps.get_message(timeout=1)
except OSError as e:
if "Buffer is closed" in str(e):
await ps.close()
ps = r.pubsub()
await ps.subscribe(*channels) Prevention
- Never call pubsub.get_message() after explicit disconnect() or context-manager exit.
- Resubscribe on a fresh PubSub after any ConnectionError.
- Keep the connection alive with periodic PINGs if idle periods are long.
- Treat OSError('Buffer is closed.') as terminal - do not retry on the same object.
When it happens
Trigger: Calling pubsub.get_message(), connection.can_read(), or a pipeline health check on a connection that was already disconnected (server-initiated close, client.disconnect(), pool eviction, or 'with client:' context exit). The reader/writer were torn down and _connected flipped to False before can_read ran.
Common situations: Holding a PubSub object across a reconnect and calling get_message() before re-subscribing; using 'async with redis.Redis()' then awaiting a method after the block exited; server-side idle timeout (tcp-keepalive/tcp-timeout) dropping the connection between polls.
Related errors
- Connection closed by server.
- Connection closed by server.
- Error setting client name
- Invalid Database
- Timed out closing connection after {self.socket_connect_time
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/4b1bb8b9a9c8ff7e.json.
Report an issue: GitHub.