gh-153290: Fix data race in BytesIO.__setstate__ installing __dict__ (#153376)
kumaraditya303 <[email protected]> Sat, 25 Jul 2026 01:56:47 -0400 (EDT)
| Newsgroups | gmane.comp.python.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://github.com/python/cpython/commit/6c026ad6c49cb7e9fa4a0867f2d0b4071a37a483 commit: 6c026ad6c49cb7e9fa4a0867f2d0b4071a37a483 branch: main author: Bhuvi <[email protected]> committer: kumaraditya303 <[email protected]> date: 2026-07-25T11:26:33+05:30 summary: gh-153290: Fix data race in BytesIO.__setstate__ installing __dict__ (#153376) files: A Misc/NEWS.d/next/Library/2026-07-09-08-40-00.gh-issue-153290.GTzSEa.rst M Lib/test/test_free_threading/test_io.py M Modules/_io/bytesio.c diff --git a/Lib/test/test_free_threading/test_io.py b/Lib/test/test_free_threading/test_io.py index e0bd7e211e73024..057e0adf3b42bc4 100644 --- a/Lib/test/test_free_threading/test_io.py +++ b/Lib/test/test_free_threading/test_io.py @@ -122,6 +122,34 @@ def sizeof(barrier, b, *ignore): class CBytesIOTest(ThreadSafetyMixin, TestCase): ioclass = io.BytesIO + @threading_helper.requires_working_threading() + @threading_helper.reap_threads + def test_concurrent_setstate_and_method_call(self): + # gh-153290: __setstate__() installed the instance __dict__ with a + # plain store, racing the lock-free LOAD_ATTR method fast path that + # reads the dict slot with an atomic acquire load. + states = [(b"A" * 64, 0, {}), (b"B" * 128, 32, {}), (b"C" * 256, 0, {})] + nreaders = 4 + for _ in range(25): + shared = self.ioclass(b"initial payload") + barrier = threading.Barrier(1 + nreaders) + + def setter(): + barrier.wait() + for state in states: + shared.__setstate__(state) + + def reader(): + barrier.wait() + for _ in range(100): + shared.read(8) + + threads = [threading.Thread(target=setter)] + threads += [threading.Thread(target=reader) + for _ in range(nreaders)] + with threading_helper.start_threads(threads): + pass + @threading_helper.requires_working_threading() @threading_helper.reap_threads def test_concurrent_whole_buffer_read_and_resize(self): diff --git a/Misc/NEWS.d/next/Library/2026-07-09-08-40-00.gh-issue-153290.GTzSEa.rst b/Misc/NEWS.d/next/Library/2026-07-09-08-40-00.gh-issue-153290.GTzSEa.rst new file mode 100644 index 000000000000000..deea3fe66b9b5af --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-09-08-40-00.gh-issue-153290.GTzSEa.rst @@ -0,0 +1,3 @@ +Fix a data race on the free-threaded build when :meth:`!io.BytesIO.__setstate__` +installs the instance dictionary while another thread concurrently calls a +method on the same object. diff --git a/Modules/_io/bytesio.c b/Modules/_io/bytesio.c index 3d14ec3f8f94a92..7d6053d85cd9e4a 100644 --- a/Modules/_io/bytesio.c +++ b/Modules/_io/bytesio.c @@ -1054,7 +1054,9 @@ bytesio_setstate_lock_held(PyObject *op, PyObject *state) return NULL; } else { - self->dict = Py_NewRef(dict); + /* The LOAD_ATTR specializations read the dict slot lock-free + with an acquire load, so pair it with a release store. */ + FT_ATOMIC_STORE_PTR_RELEASE(self->dict, Py_NewRef(dict)); } } _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]