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]

Reply via email to