]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-133467: Fix typeobject `tp_base` race in free threading (gh-140549)
authorEdward Xu <xuxiangad@foxmail.com>
Wed, 5 Nov 2025 21:20:40 +0000 (05:20 +0800)
committerGitHub <noreply@github.com>
Wed, 5 Nov 2025 21:20:40 +0000 (16:20 -0500)
Lib/test/test_free_threading/test_type.py
Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst [new file with mode: 0644]
Objects/typeobject.c
Tools/tsan/suppressions_free_threading.txt

index 2d995751005d710fac5b5c5f86a8bd1e1a8238f3..1255d842dbff48f7997eea2a90785f6f5aefa012 100644 (file)
@@ -141,6 +141,25 @@ class TestType(TestCase):
 
         self.run_one(writer, reader)
 
+    def test_bases_change(self):
+        class BaseA:
+            pass
+
+        class Derived(BaseA):
+            pass
+
+        def writer():
+            for _ in range(1000):
+                class BaseB:
+                    pass
+                Derived.__bases__ = (BaseB,)
+
+        def reader():
+            for _ in range(1000):
+                Derived.__base__
+
+        self.run_one(writer, reader)
+
     def run_one(self, writer_func, reader_func):
         barrier = threading.Barrier(NTHREADS)
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst
new file mode 100644 (file)
index 0000000..f697868
--- /dev/null
@@ -0,0 +1 @@
+Fix race when updating :attr:`!type.__bases__` that could allow a read of :attr:`!type.__base__` to observe an inconsistent value on the free threaded build.
index 326f4add896bab6295a9eb143b7b3f4ec441522c..58228d6248522e65f96427367bff489fb134bebf 100644 (file)
@@ -189,6 +189,8 @@ type_lock_allow_release(void)
 #define types_world_is_stopped() 1
 #define types_stop_world()
 #define types_start_world()
+#define type_lock_prevent_release()
+#define type_lock_allow_release()
 
 #endif
 
@@ -1920,8 +1922,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
     assert(old_bases != NULL);
     PyTypeObject *old_base = type->tp_base;
 
+    type_lock_prevent_release();
+    types_stop_world();
     set_tp_bases(type, Py_NewRef(new_bases), 0);
     type->tp_base = (PyTypeObject *)Py_NewRef(best_base);
+    types_start_world();
+    type_lock_allow_release();
 
     PyObject *temp = PyList_New(0);
     if (temp == NULL) {
@@ -1982,8 +1988,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
     if (lookup_tp_bases(type) == new_bases) {
         assert(type->tp_base == best_base);
 
+        type_lock_prevent_release();
+        types_stop_world();
         set_tp_bases(type, old_bases, 0);
         type->tp_base = old_base;
+        types_start_world();
+        type_lock_allow_release();
 
         Py_DECREF(new_bases);
         Py_DECREF(best_base);
index 6bd31e8e6ecb9d68fca90ca28108ce4f18ba872c..404c30157362aad4a292e43a957eabcbe8f40ea2 100644 (file)
@@ -41,7 +41,3 @@ race:list_inplace_repeat_lock_held
 # PyObject_Realloc internally does memcpy which isn't atomic so can race
 # with non-locking reads. See #132070
 race:PyObject_Realloc
-
-# gh-133467.  Some of these could be hard to trigger.
-race_top:set_tp_bases
-race_top:type_set_bases_unlocked