From be39a67f27558f24e21aedb14336186e17379900 Mon Sep 17 00:00:00 2001 From: Jiseok CHOI Date: Sun, 1 Mar 2026 10:44:13 +0900 Subject: [PATCH] Handle reentrant detach in TextIOWrapper.close() (#7277) When a custom `closed` property on the underlying buffer calls `detach()` during the `file_closed()` check in `close()`, the wrapper's internal buffer becomes None. Subsequent flush/close operations then fail with AttributeError on NoneType. Add a guard after the `file_closed()` check to detect if the buffer was detached reentrantly, and return early in that case (detach has already flushed the stream). This mirrors the fix applied in CPython (https://github.com/python/cpython/issues/142594\). --- Lib/test/test_io.py | 1 - crates/vm/src/stdlib/io.rs | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index 2324c447f..497c4e6e2 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -4211,7 +4211,6 @@ class CTextIOWrapperTest(TextIOWrapperTest): self.assertEqual([b"abcdef", b"middle", b"g"*chunk_size], buf._write_stack) - @unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'NoneType' object has no attribute 'closed' def test_issue142594(self): wrapper = None detached = False diff --git a/crates/vm/src/stdlib/io.rs b/crates/vm/src/stdlib/io.rs index 26d4a67c8..a22642dbf 100644 --- a/crates/vm/src/stdlib/io.rs +++ b/crates/vm/src/stdlib/io.rs @@ -3793,6 +3793,13 @@ mod _io { if file_closed(&buffer, vm)? { return Ok(()); } + // https://github.com/python/cpython/issues/142594 + // The file_closed() check above may have triggered a reentrant + // call to detach() via a custom `closed` property. + // If so, the buffer is now detached and we should return early. + if vm.is_none(&zelf.lock(vm)?.buffer) { + return Ok(()); + } if zelf.finalizing.load(Ordering::Relaxed) { // _dealloc_warn: delegate to buffer._dealloc_warn(source) let _ = vm.call_method(&buffer, "_dealloc_warn", (zelf.as_object().to_owned(),));