redis/redis-py · error · OSError

Buffer is closed.

Error message

Buffer is closed.

What it means

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.

Source

Thrown at redis/_parsers/hiredis.py:325

            )
        except AttributeError:
            # hiredis < 3.2
            self._hiredis_PushNotificationType = None

    def on_disconnect(self):
        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.")
        # EOF means the connection is closed and not safe to reuse.
        if self._reader.has_data() or self._stream.at_eof():
            return True
        # asyncio.StreamReader has no public non-destructive API for checking
        # buffered bytes. Preserve dirty-connection detection for hiredis; tests
        # with a real StreamReader guard this private buffer API in CI.
        return bool(self._stream._buffer)

    async def read_from_socket(self):
        buffer = await self._stream.read(self._read_size)
        if not buffer or not isinstance(buffer, bytes):
            raise ConnectionError(SERVER_CLOSED_CONNECTION_ERROR) from None
        self._reader.feed(buffer)
        # data was read from the socket and added to the buffer.
        # return True to indicate that data was read.
        return True

    async def read_response(

View on GitHub (pinned to 6a6b581b48)

Solutions

  1. Treat a closed parser as 'no data'; catch OSError around can_read().
  2. Let the connection pool hand out fresh connections rather than probing dead ones.
  3. Guard pubsub loops with a flag set on disconnect/cancel.
  4. Check the connection's connected state before probing.

Example fix

# before
ready = await pubsub.connection.can_read()  # after disconnect -> OSError: Buffer is closed.

# after - guard the probe and treat closed as 'no data'
try:
    ready = await pubsub.connection.can_read()
except OSError:
    ready = False  # connection gone; reconnect
Defensive patterns

Strategy: try-catch

Validate before calling

# Track lifecycle so you don't probe a closed parser
async def safe_can_read(conn):
    if not getattr(conn, '_connected', False):
        return False
    try:
        return await conn.can_read()
    except OSError:
        return False

Type guard

def is_closed_buffer(e: BaseException) -> bool:
    return isinstance(e, OSError) and 'buffer is closed' in str(e).lower()

Try / catch

try:
    ready = await conn.can_read()
except OSError:
    ready = False

Prevention

When it happens

Trigger: 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.

Common situations: 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().

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/5e529c5e9fbbab90. Report an issue: GitHub.