{"record":{"id":"6136a30f5bf20a7f","repo":"python/cpython","slug":"read-from-closed-file","errorCode":null,"errorMessage":"read from closed file","messagePattern":"read from closed file","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/_pyio.py","lineNumber":929,"sourceCode":"        if self.closed:\n            raise ValueError(\"getvalue on closed file\")\n        return bytes(self._buffer)\n\n    def getbuffer(self):\n        \"\"\"Return a readable and writable view of the buffer.\n        \"\"\"\n        if self.closed:\n            raise ValueError(\"getbuffer on closed file\")\n        return memoryview(self._buffer)\n\n    def close(self):\n        if self._buffer is not None:\n            self._buffer = bytearray()\n        super().close()\n\n    def read(self, size=-1):\n        if self.closed:\n            raise ValueError(\"read from closed file\")\n        if size is None:\n            size = -1\n        else:\n            try:\n                size_index = size.__index__\n            except AttributeError:\n                raise TypeError(f\"{size!r} is not an integer\")\n            else:\n                size = size_index()\n\n        with self._lock:\n            if size < 0:\n                size = len(self._buffer)\n            if len(self._buffer) <= self._pos:\n                return b\"\"\n            newpos = min(len(self._buffer), self._pos + size)\n            b = self._buffer[self._pos : newpos]\n            self._pos = newpos","sourceCodeStart":911,"sourceCodeEnd":947,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Lib/_pyio.py#L911-L947","documentation":"BytesIO.read (Lib/_pyio.py:929) checks the closed flag before touching the (already-released) internal buffer and raises ValueError('read from closed file'). Because close() empties the bytearray, reading after close could silently return b'' — the guard converts that silent data loss into an explicit error.","triggerScenarios":"buf.read() / buf.read(n) after buf.close(); calling read() on a BytesIO returned from a `with` block after exit; a consumer reading a stream that a producer's error handler already closed.","commonSituations":"Response-body assembly where cleanup closes the buffer before the sender reads it; chaining processors where each stage closes its input; helper that closes on EOF while the caller loops once more.","solutions":["Remove or relocate the close() so it runs after the final read.","Use `with io.BytesIO(...) as buf:` and keep every read inside the block.","Guard with `if not buf.closed:` when ownership of the stream is shared across functions."],"exampleFix":"# before\nbuf = io.BytesIO(data)\nfirst = buf.read(4)\nbuf.close()\nrest = buf.read()  # ValueError: read from closed file\n\n# after\nbuf = io.BytesIO(data)\nfirst = buf.read(4)\nrest = buf.read()   # all reads first\nbuf.close()         # close last (or omit)","handlingStrategy":"validation","validationCode":"def read_if_open(buf: io.BytesIO, n: int = -1) -> bytes:\n    if buf.closed:\n        raise ValueError('BytesIO closed; reads must happen before close')\n    return buf.read(n)","typeGuard":"def is_open_bytesio(v) -> bool:\n    return isinstance(v, io.BytesIO) and not v.closed","tryCatchPattern":"try:\n    chunk = buf.read(n)\nexcept ValueError as e:\n    if 'closed file' in str(e):\n        raise RuntimeError('consumer raced with producer close') from e\n    raise","preventionTips":["Keep all reads before close(); reorder cleanup to the end of the scope.","Use a with-block for BytesIO scopes and read inside it.","In pipelines, give close() to the final consumer only."],"tags":["io","bytesio","read","lifecycle","valueerror"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}