python/cpython · error · ValueError

__getstate__ on closed file

Error message

__getstate__ on closed file

What it means

Unlike raw file objects, BytesIO is picklable, but its __getstate__ (Lib/_pyio.py:898) refuses to serialize when the stream is closed, raising ValueError('__getstate__ on closed file'). A closed BytesIO has had its buffer released to an empty bytearray, so pickling it would silently produce an empty stream — the guard makes that data loss explicit.

Source

Thrown at Lib/_pyio.py:898

    # Initialize _buffer as soon as possible since it's used by __del__()
    # which calls close()
    _buffer = None

    def __init__(self, initial_bytes=None):
        # Use to keep self._buffer and self._pos consistent.
        self._lock = Lock()

        buf = bytearray()
        if initial_bytes is not None:
            buf += initial_bytes

        with self._lock:
            self._buffer = buf
            self._pos = 0

    def __getstate__(self):
        if self.closed:
            raise ValueError("__getstate__ on closed file")
        with self._lock:
            state = self.__dict__.copy()
        del state['_lock']
        return state

    def __setstate__(self, state):
        self.__dict__.update(state)
        self._lock = Lock()

    def getvalue(self):
        """Return the bytes value (contents) of the buffer
        """
        if self.closed:
            raise ValueError("getvalue on closed file")
        return bytes(self._buffer)

    def getbuffer(self):
        """Return a readable and writable view of the buffer.

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Extract the value before closing and pickle that: `payload = buf.getvalue(); buf.close()`.
  2. Drop the explicit close() on BytesIO — it is pure memory and garbage collection reclaims it without close.
  3. If BytesIO lives in a larger picklable object, convert it to bytes in that object's __getstate__.

Example fix

# before
buf = io.BytesIO(b'payload')
buf.write(b'more')
buf.close()
pickle.dumps(buf)  # ValueError: __getstate__ on closed file

# after
buf = io.BytesIO(b'payload')
buf.write(b'more')
payload = buf.getvalue()  # bytes, freely picklable
buf.close()
Defensive patterns

Strategy: validation

Validate before calling

def pickleable_bytesio(buf: io.BytesIO) -> bytes:
    if buf.closed:
        raise ValueError('BytesIO closed; extract getvalue() before close')
    return buf.getvalue()  # pickle bytes instead of the stream

Type guard

def is_open_bytesio(v) -> bool:
    return isinstance(v, io.BytesIO) and not v.closed

Try / catch

try:
    blob = pickle.dumps(buf)
except ValueError as e:
    if 'closed file' in str(e):
        raise ValueError('BytesIO was closed before pickling; keep it open or pickle getvalue()') from e
    raise

Prevention

When it happens

Trigger: pickle.dumps(buf) or copy.deepcopy(buf) on a BytesIO after buf.close(); a request handler closes a BytesIO used for response assembly, then the framework attempts to pickle/cache it.

Common situations: Caching layers (beaker, job queues) that pickle payload objects containing BytesIO members closed by earlier cleanup; explicitly closing BytesIO for tidiness in memory-conscious code before serialization; multiprocessing pipelines over BytesIO chunks.

Related errors


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