diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index e0bd7e211e73024..8214ae330e25fe5 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -204,3 +204,20 @@ def reset_worker(): decoder.reset() run_concurrently([decode_worker] * 2 + [reset_worker] * 2) + + +class TextIOWrapperTest(TestCase): + def test_buffer_detach_race(self): + make = lambda: io.TextIOWrapper(io.BytesIO()) + slot = [make()] + + def reader(): + for _ in range(1000): + slot[0].buffer + + def detacher(): + for _ in range(1000): + slot[0] = make() + slot[0].detach() + + run_concurrently([reader, detacher]) diff --git a/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst b/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst new file mode 100644 index 000000000000000..23b25a26effb642 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-23-18-53-48.gh-issue-154523.XUSRBO.rst @@ -0,0 +1,2 @@ +Fixed data-race when calling :meth:`io.TextIOBase.detach` in +:term:`free-threaded build`. diff --git a/Modules/_io/textio.c b/Modules/_io/textio.c index 5b2a20a30c28cb2..135904c3ab34b95 100644 --- a/Modules/_io/textio.c +++ b/Modules/_io/textio.c @@ -1639,7 +1639,7 @@ _io_TextIOWrapper_detach_impl(textio *self) if (buffer == NULL) { return NULL; } - self->buffer = NULL; + FT_ATOMIC_STORE_PTR_RELAXED(self->buffer, NULL); self->detached = 1; return buffer; }