]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153290: Fix data race in BytesIO.__setstate__ installing __dict__ (#153376)
authorBhuvi <b.chouksey27@gmail.com>
Sat, 25 Jul 2026 05:56:33 +0000 (11:26 +0530)
committerGitHub <noreply@github.com>
Sat, 25 Jul 2026 05:56:33 +0000 (11:26 +0530)
Lib/test/test_free_threading/test_io.py
Misc/NEWS.d/next/Library/2026-07-09-08-40-00.gh-issue-153290.GTzSEa.rst [new file with mode: 0644]
Modules/_io/bytesio.c

index e0bd7e211e73024d8623acba5f9d11753eebdd3c..057e0adf3b42bc45cba69c3f0f8d0ed11e843663 100644 (file)
@@ -122,6 +122,34 @@ class ThreadSafetyMixin:
 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 (file)
index 0000000..deea3fe
--- /dev/null
@@ -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.
index 3d14ec3f8f94a92baac620f03ebf723b662bf716..7d6053d85cd9e4a6f6ec52213473203885a20df2 100644 (file)
@@ -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));
         }
     }