Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Lib/test/test_free_threading/test_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
10 changes: 10 additions & 0 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 0 additions & 4 deletions Tools/tsan/suppressions_free_threading.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading