gh-133467: Fix typeobject tp_base race in free threading (gh-140549)
This commit is contained in:
@@ -141,6 +141,25 @@ class TestType(TestCase):
|
|||||||
|
|
||||||
self.run_one(writer, 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):
|
def run_one(self, writer_func, reader_func):
|
||||||
barrier = threading.Barrier(NTHREADS)
|
barrier = threading.Barrier(NTHREADS)
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -189,6 +189,8 @@ type_lock_allow_release(void)
|
|||||||
#define types_world_is_stopped() 1
|
#define types_world_is_stopped() 1
|
||||||
#define types_stop_world()
|
#define types_stop_world()
|
||||||
#define types_start_world()
|
#define types_start_world()
|
||||||
|
#define type_lock_prevent_release()
|
||||||
|
#define type_lock_allow_release()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1920,8 +1922,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
|
|||||||
assert(old_bases != NULL);
|
assert(old_bases != NULL);
|
||||||
PyTypeObject *old_base = type->tp_base;
|
PyTypeObject *old_base = type->tp_base;
|
||||||
|
|
||||||
|
type_lock_prevent_release();
|
||||||
|
types_stop_world();
|
||||||
set_tp_bases(type, Py_NewRef(new_bases), 0);
|
set_tp_bases(type, Py_NewRef(new_bases), 0);
|
||||||
type->tp_base = (PyTypeObject *)Py_NewRef(best_base);
|
type->tp_base = (PyTypeObject *)Py_NewRef(best_base);
|
||||||
|
types_start_world();
|
||||||
|
type_lock_allow_release();
|
||||||
|
|
||||||
PyObject *temp = PyList_New(0);
|
PyObject *temp = PyList_New(0);
|
||||||
if (temp == NULL) {
|
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) {
|
if (lookup_tp_bases(type) == new_bases) {
|
||||||
assert(type->tp_base == best_base);
|
assert(type->tp_base == best_base);
|
||||||
|
|
||||||
|
type_lock_prevent_release();
|
||||||
|
types_stop_world();
|
||||||
set_tp_bases(type, old_bases, 0);
|
set_tp_bases(type, old_bases, 0);
|
||||||
type->tp_base = old_base;
|
type->tp_base = old_base;
|
||||||
|
types_start_world();
|
||||||
|
type_lock_allow_release();
|
||||||
|
|
||||||
Py_DECREF(new_bases);
|
Py_DECREF(new_bases);
|
||||||
Py_DECREF(best_base);
|
Py_DECREF(best_base);
|
||||||
|
|||||||
@@ -41,7 +41,3 @@ race:list_inplace_repeat_lock_held
|
|||||||
# PyObject_Realloc internally does memcpy which isn't atomic so can race
|
# PyObject_Realloc internally does memcpy which isn't atomic so can race
|
||||||
# with non-locking reads. See #132070
|
# with non-locking reads. See #132070
|
||||||
race:PyObject_Realloc
|
race:PyObject_Realloc
|
||||||
|
|
||||||
# gh-133467. Some of these could be hard to trigger.
|
|
||||||
race_top:set_tp_bases
|
|
||||||
race_top:type_set_bases_unlocked
|
|
||||||
|
|||||||
Reference in New Issue
Block a user