From aff39e2c665126268224549004e065debf501543 Mon Sep 17 00:00:00 2001 From: pedramkarimii Date: Thu, 18 Jun 2026 15:48:37 +0330 Subject: [PATCH 1/3] gh-151640: avoid sharing BytesIO buffer in free-threaded builds --- Lib/test/test_free_threading/test_io.py | 27 +++++++++++++++++++++++++ Modules/_io/bytesio.c | 7 +++++++ 2 files changed, 34 insertions(+) diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index 8a0ad30c4bc770..f59a3a2e972043 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -117,6 +117,33 @@ def sizeof(barrier, b, *ignore): class CBytesIOTest(ThreadSafetyMixin, TestCase): ioclass = io.BytesIO + @threading_helper.requires_working_threading() + @threading_helper.reap_threads + def test_concurrent_whole_buffer_read_and_resize(self): + shared = self.ioclass(b"x" * 64) + writers = 2 + readers = 8 + loops = 2000 + barrier = threading.Barrier(writers + readers) + + def writer(): + barrier.wait() + for i in range(loops): + shared.seek(0) + shared.write(b"a" * (64 + (i & 63))) + + def reader(): + barrier.wait() + for _ in range(loops): + shared.seek(0) + shared.read() + shared.getvalue() + + threads = [threading.Thread(target=writer) for _ in range(writers)] + threads += [threading.Thread(target=reader) for _ in range(readers)] + with threading_helper.start_threads(threads): + pass + class PyBytesIOTest(ThreadSafetyMixin, TestCase): ioclass = pyio.BytesIO diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 8cdcbd0d89c718..908552c4f14c6f 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -371,6 +371,10 @@ _io_BytesIO_getvalue_impl(bytesio *self) /*[clinic end generated code: output=b3f6a3233c8fd628 input=c91bff398df0c352]*/ { CHECK_CLOSED(self); +#ifdef Py_GIL_DISABLED + return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf), + self->string_size); +#else if (self->string_size <= 1 || FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) > 0) return PyBytes_FromStringAndSize(PyBytes_AS_STRING(self->buf), self->string_size); @@ -386,6 +390,7 @@ _io_BytesIO_getvalue_impl(bytesio *self) } } return Py_NewRef(self->buf); +#endif } /*[clinic input] @@ -429,12 +434,14 @@ read_bytes_lock_held(bytesio *self, Py_ssize_t size) assert(self->buf != NULL); assert(size <= self->string_size); +#ifndef Py_GIL_DISABLED if (size > 1 && self->pos == 0 && size == PyBytes_GET_SIZE(self->buf) && FT_ATOMIC_LOAD_SSIZE_RELAXED(self->exports) == 0) { self->pos += size; return Py_NewRef(self->buf); } +#endif /* gh-141311: Avoid undefined behavior when self->pos (limit PY_SSIZE_T_MAX) is beyond the size of self->buf. Assert above validates size is always in From 96dfcb75ddc5323787348713e2950101453da706 Mon Sep 17 00:00:00 2001 From: pedramkarimii Date: Thu, 18 Jun 2026 16:23:03 +0330 Subject: [PATCH 2/3] gh-151640: add NEWS entry --- .../next/Library/2026-06-18-12-48-03.gh-issue-151640.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst diff --git a/Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst b/Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst new file mode 100644 index 00000000000000..7fb6e9aee6cfd5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst @@ -0,0 +1,3 @@ +Fix a data race in :class:`io.BytesIO` in free-threaded builds when +whole-buffer reads or :meth:`~io.BytesIO.getvalue` share the internal buffer +with concurrent writes. From 918fb1ce832a74e792b3ad0ef036ab051746173b Mon Sep 17 00:00:00 2001 From: pedramkarimii Date: Thu, 18 Jun 2026 16:36:51 +0330 Subject: [PATCH 3/3] gh-151640: fix NEWS entry filename --- ...-151640.rst => 2026-06-18-12-48-03.gh-issue-151640.R4c3Fx.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2026-06-18-12-48-03.gh-issue-151640.rst => 2026-06-18-12-48-03.gh-issue-151640.R4c3Fx.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst b/Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.R4c3Fx.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.rst rename to Misc/NEWS.d/next/Library/2026-06-18-12-48-03.gh-issue-151640.R4c3Fx.rst