python/cpython · error · RuntimeError

self.raw should implement RawIOBase: it should not raise Blo

Error message

self.raw should implement RawIOBase: it should not raise BlockingIOError

What it means

Raised by BufferedWriter._flush_unlocked when the underlying raw stream's write() raises BlockingIOError. The buffered layer's contract (RawIOBase) is that raw.write either writes or returns None to indicate blocking — it must not raise BlockingIOError itself. A raw object violating that contract is a programming error in the raw class, so it is surfaced as RuntimeError with the quoted expectation.

Source

Thrown at Lib/_pyio.py:1314

    def truncate(self, pos=None):
        with self._write_lock:
            self._flush_unlocked()
            if pos is None:
                pos = self.raw.tell()
            return self.raw.truncate(pos)

    def flush(self):
        with self._write_lock:
            self._flush_unlocked()

    def _flush_unlocked(self):
        if self.closed:
            raise ValueError("flush on closed file")
        while self._write_buf:
            try:
                n = self.raw.write(self._write_buf)
            except BlockingIOError:
                raise RuntimeError("self.raw should implement RawIOBase: it "
                                   "should not raise BlockingIOError")
            if n is None:
                raise BlockingIOError(
                    errno.EAGAIN,
                    "write could not complete without blocking", 0)
            if n > len(self._write_buf) or n < 0:
                raise OSError("write() returned incorrect number of bytes")
            del self._write_buf[:n]

    def tell(self):
        return _BufferedIOMixin.tell(self) + len(self._write_buf)

    def seek(self, pos, whence=0):
        if whence not in valid_seek_flags:
            raise ValueError("invalid whence value")
        with self._write_lock:
            self._flush_unlocked()
            return _BufferedIOMixin.seek(self, pos, whence)

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. In the custom raw class, catch the blocking condition in write() and return None instead of raising BlockingIOError.
  2. Subclass io.RawIOBase (not IOBase) so the return-value contract is explicit.
  3. If blocking writes are fine, make the fd blocking so os.write never signals EAGAIN.

Example fix

# before
class MyRaw(io.RawIOBase):
    def write(self, b):
        return os.write(self.fd, b)  # may raise BlockingIOError -> RuntimeError upstream

# after
class MyRaw(io.RawIOBase):
    def write(self, b):
        try:
            return os.write(self.fd, b)
        except BlockingIOError:
            return None  # RawIOBase protocol: None means 'would block'
Defensive patterns

Strategy: type-guard

Validate before calling

class ContractCheckingRaw(io.RawIOBase):
    def write(self, b):
        try:
            return super().write(b)
        except BlockingIOError:
            return None  # satisfy RawIOBase protocol

Type guard

def honors_rawio_contract(raw) -> bool:
    """Can't fully verify statically; check the class contract by inspection."""
    import inspect
    src = inspect.getsource(type(raw).write)
    return "BlockingIOError" not in src or "return None" in src

Try / catch

try:
    writer.flush()
except RuntimeError as e:
    if "RawIOBase" in str(e):
        raise TypeError("custom raw stream violates RawIOBase: return None on would-block") from e
    raise

Prevention

When it happens

Trigger: Supplying a custom raw stream whose write() raises BlockingIOError('EAGAIN') instead of returning None; wrapping a non-blocking object without honoring the RawIOBase return-None protocol.

Common situations: Hand-rolled raw wrappers around sockets/subprocess pipes that propagate BlockingIOError from os.write/send; porting code from a layer (e.g. socketserver) that expects raising semantics into io wrappers.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/e83fe9fe6a63a50c. Report an issue: GitHub.