Skip to content

Commit b83f379

Browse files
authored
pythongh-133467: Fix typeobject tp_base race in free threading (pythongh-140549)
1 parent 986bb0a commit b83f379

File tree

4 files changed

+30
-4
lines changed

4 files changed

+30
-4
lines changed

Lib/test/test_free_threading/test_type.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,25 @@ def reader():
141141

142142
self.run_one(writer, reader)
143143

144+
def test_bases_change(self):
145+
class BaseA:
146+
pass
147+
148+
class Derived(BaseA):
149+
pass
150+
151+
def writer():
152+
for _ in range(1000):
153+
class BaseB:
154+
pass
155+
Derived.__bases__ = (BaseB,)
156+
157+
def reader():
158+
for _ in range(1000):
159+
Derived.__base__
160+
161+
self.run_one(writer, reader)
162+
144163
def run_one(self, writer_func, reader_func):
145164
barrier = threading.Barrier(NTHREADS)
146165

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

Objects/typeobject.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ type_lock_allow_release(void)
189189
#define types_world_is_stopped() 1
190190
#define types_stop_world()
191191
#define types_start_world()
192+
#define type_lock_prevent_release()
193+
#define type_lock_allow_release()
192194

193195
#endif
194196

@@ -1920,8 +1922,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
19201922
assert(old_bases != NULL);
19211923
PyTypeObject *old_base = type->tp_base;
19221924

1925+
type_lock_prevent_release();
1926+
types_stop_world();
19231927
set_tp_bases(type, Py_NewRef(new_bases), 0);
19241928
type->tp_base = (PyTypeObject *)Py_NewRef(best_base);
1929+
types_start_world();
1930+
type_lock_allow_release();
19251931

19261932
PyObject *temp = PyList_New(0);
19271933
if (temp == NULL) {
@@ -1982,8 +1988,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
19821988
if (lookup_tp_bases(type) == new_bases) {
19831989
assert(type->tp_base == best_base);
19841990

1991+
type_lock_prevent_release();
1992+
types_stop_world();
19851993
set_tp_bases(type, old_bases, 0);
19861994
type->tp_base = old_base;
1995+
types_start_world();
1996+
type_lock_allow_release();
19871997

19881998
Py_DECREF(new_bases);
19891999
Py_DECREF(best_base);

Tools/tsan/suppressions_free_threading.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,3 @@ race:list_inplace_repeat_lock_held
4141
# PyObject_Realloc internally does memcpy which isn't atomic so can race
4242
# with non-locking reads. See #132070
4343
race:PyObject_Realloc
44-
45-
# gh-133467. Some of these could be hard to trigger.
46-
race_top:set_tp_bases
47-
race_top:type_set_bases_unlocked

0 commit comments

Comments
 (0)