From 64583edb77f8378c47aabcf1d6f53885d3df5936 Mon Sep 17 00:00:00 2001 From: edward_xu Date: Fri, 24 Oct 2025 20:36:00 +0800 Subject: [PATCH 1/5] fix `PyTypeObject->tp_base` race in free threading --- Lib/test/test_free_threading/test_type.py | 19 +++++++++++++++++++ ...-10-24-14-29-12.gh-issue-133467.A5d6TM.rst | 1 + Objects/typeobject.c | 4 ++++ 3 files changed, 24 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst diff --git a/Lib/test/test_free_threading/test_type.py b/Lib/test/test_free_threading/test_type.py index 2d995751005d71..1255d842dbff48 100644 --- a/Lib/test/test_free_threading/test_type.py +++ b/Lib/test/test_free_threading/test_type.py @@ -141,6 +141,25 @@ def reader(): 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 index 00000000000000..74d84e0f65ff06 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst @@ -0,0 +1 @@ +Fix race when updating ``type.__bases__`` that could allow a read of ``type.__base__`` to observe an inconsistent value on the free threaded build. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 326f4add896bab..70df80242af2d0 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1920,8 +1920,10 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b assert(old_bases != NULL); PyTypeObject *old_base = type->tp_base; + types_stop_world(); set_tp_bases(type, Py_NewRef(new_bases), 0); type->tp_base = (PyTypeObject *)Py_NewRef(best_base); + types_start_world(); PyObject *temp = PyList_New(0); if (temp == NULL) { @@ -1982,8 +1984,10 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b if (lookup_tp_bases(type) == new_bases) { assert(type->tp_base == best_base); + types_stop_world(); set_tp_bases(type, old_bases, 0); type->tp_base = old_base; + types_start_world(); Py_DECREF(new_bases); Py_DECREF(best_base); From f90b5de696d9b826b64fda53d813dafd4aded0cb Mon Sep 17 00:00:00 2001 From: edward_xu Date: Tue, 4 Nov 2025 13:31:28 +0000 Subject: [PATCH 2/5] prevent `TYPE_LOCK` release during stw --- Objects/typeobject.c | 12 ++++++++++++ Tools/tsan/suppressions_free_threading.txt | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 70df80242af2d0..b7554cbbdf5e91 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -1920,10 +1920,16 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b assert(old_bases != NULL); PyTypeObject *old_base = type->tp_base; +#ifdef Py_GIL_DISABLED + type_lock_prevent_release(); +#endif types_stop_world(); set_tp_bases(type, Py_NewRef(new_bases), 0); type->tp_base = (PyTypeObject *)Py_NewRef(best_base); types_start_world(); +#ifdef Py_GIL_DISABLED + type_lock_allow_release(); +#endif PyObject *temp = PyList_New(0); if (temp == NULL) { @@ -1984,10 +1990,16 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b if (lookup_tp_bases(type) == new_bases) { assert(type->tp_base == best_base); +#ifdef Py_GIL_DISABLED + type_lock_prevent_release(); +#endif types_stop_world(); set_tp_bases(type, old_bases, 0); type->tp_base = old_base; types_start_world(); +#ifdef Py_GIL_DISABLED + type_lock_allow_release(); +#endif Py_DECREF(new_bases); Py_DECREF(best_base); diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index 6bd31e8e6ecb9d..a1a637b02a30df 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -44,4 +44,3 @@ race:PyObject_Realloc # gh-133467. Some of these could be hard to trigger. race_top:set_tp_bases -race_top:type_set_bases_unlocked From d3fe2846cb32db350318beaa2a03a94ef8494fb7 Mon Sep 17 00:00:00 2001 From: edward_xu Date: Wed, 5 Nov 2025 12:07:49 +0000 Subject: [PATCH 3/5] add empty macro to remove `Py_GIL_DISABLED` macro guard --- Objects/typeobject.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b7554cbbdf5e91..58228d6248522e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -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,16 +1922,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b assert(old_bases != NULL); PyTypeObject *old_base = type->tp_base; -#ifdef Py_GIL_DISABLED type_lock_prevent_release(); -#endif types_stop_world(); set_tp_bases(type, Py_NewRef(new_bases), 0); type->tp_base = (PyTypeObject *)Py_NewRef(best_base); types_start_world(); -#ifdef Py_GIL_DISABLED type_lock_allow_release(); -#endif PyObject *temp = PyList_New(0); if (temp == NULL) { @@ -1990,16 +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); -#ifdef Py_GIL_DISABLED type_lock_prevent_release(); -#endif types_stop_world(); set_tp_bases(type, old_bases, 0); type->tp_base = old_base; types_start_world(); -#ifdef Py_GIL_DISABLED type_lock_allow_release(); -#endif Py_DECREF(new_bases); Py_DECREF(best_base); From 95532d7004147f84958b11eca4aa3155b78f367f Mon Sep 17 00:00:00 2001 From: edward_xu Date: Wed, 5 Nov 2025 15:11:13 +0000 Subject: [PATCH 4/5] remove `set_tp_bases` in race suppression --- Tools/tsan/suppressions_free_threading.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tools/tsan/suppressions_free_threading.txt b/Tools/tsan/suppressions_free_threading.txt index a1a637b02a30df..404c30157362aa 100644 --- a/Tools/tsan/suppressions_free_threading.txt +++ b/Tools/tsan/suppressions_free_threading.txt @@ -41,6 +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 From 169c840b957904390dfaac02bc047c346d9387e3 Mon Sep 17 00:00:00 2001 From: edward_xu Date: Wed, 5 Nov 2025 15:13:42 +0000 Subject: [PATCH 5/5] update news file --- .../2025-10-24-14-29-12.gh-issue-133467.A5d6TM.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 74d84e0f65ff06..f69786866e9878 100644 --- 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 @@ -1 +1 @@ -Fix race when updating ``type.__bases__`` that could allow a read of ``type.__base__`` to observe an inconsistent value on the free threaded build. +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.