[3.14] gh-145685: Stop the world when updating MRO of existing types (gh-145707) (#145715)

We already have a stop-the-world pause elsewhere in this code path
(type_set_bases) and this makes will make it easier to avoid contention
on the TYPE_LOCK when looking up names in the MRO hierarchy.

Also use deferred reference counting for non-immortal MROs.
(cherry picked from commit 0b65c88c2a)
This commit is contained in:
Sam Gross 2026-03-10 09:31:52 -04:00 committed by GitHub
parent a5ed66df8a
commit 54024655ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -76,6 +76,62 @@ class object "PyObject *" "&PyBaseObject_Type"
#define ASSERT_TYPE_LOCK_HELD() \
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(TYPE_LOCK)
static void
types_stop_world(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyEval_StopTheWorld(interp);
}
static void
types_start_world(void)
{
PyInterpreterState *interp = _PyInterpreterState_GET();
_PyEval_StartTheWorld(interp);
}
// This is used to temporarily prevent the TYPE_LOCK from being suspended
// when held by the topmost critical section.
static void
type_lock_prevent_release(void)
{
PyThreadState *tstate = _PyThreadState_GET();
uintptr_t *tagptr = &tstate->critical_section;
PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
assert(c->_cs_mutex == TYPE_LOCK);
c->_cs_mutex = NULL;
}
else {
PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
if (c->_cs_mutex == TYPE_LOCK) {
c->_cs_mutex = c2->_cs_mutex2;
c2->_cs_mutex2 = NULL;
}
else {
assert(c2->_cs_mutex2 == TYPE_LOCK);
c2->_cs_mutex2 = NULL;
}
}
}
static void
type_lock_allow_release(void)
{
PyThreadState *tstate = _PyThreadState_GET();
uintptr_t *tagptr = &tstate->critical_section;
PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
assert(c->_cs_mutex == NULL);
c->_cs_mutex = TYPE_LOCK;
}
else {
PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
assert(c2->_cs_mutex2 == NULL);
c2->_cs_mutex2 = TYPE_LOCK;
}
}
#else
#define BEGIN_TYPE_LOCK()
@ -83,6 +139,10 @@ class object "PyObject *" "&PyBaseObject_Type"
#define BEGIN_TYPE_DICT_LOCK(d)
#define END_TYPE_DICT_LOCK()
#define ASSERT_TYPE_LOCK_HELD()
#define types_stop_world()
#define types_start_world()
#define type_lock_prevent_release()
#define type_lock_allow_release()
#endif
@ -541,7 +601,6 @@ clear_tp_bases(PyTypeObject *self, int final)
static inline PyObject *
lookup_tp_mro(PyTypeObject *self)
{
ASSERT_TYPE_LOCK_HELD();
return self->tp_mro;
}
@ -580,8 +639,19 @@ set_tp_mro(PyTypeObject *self, PyObject *mro, int initial)
/* Other checks are done via set_tp_bases. */
_Py_SetImmortal(mro);
}
else {
PyUnstable_Object_EnableDeferredRefcount(mro);
}
}
if (!initial) {
type_lock_prevent_release();
types_stop_world();
}
self->tp_mro = mro;
if (!initial) {
types_start_world();
type_lock_allow_release();
}
}
static inline void
@ -1627,18 +1697,11 @@ static PyObject *
type_get_mro(PyObject *tp, void *Py_UNUSED(closure))
{
PyTypeObject *type = PyTypeObject_CAST(tp);
PyObject *mro;
BEGIN_TYPE_LOCK();
mro = lookup_tp_mro(type);
PyObject *mro = lookup_tp_mro(type);
if (mro == NULL) {
mro = Py_None;
} else {
Py_INCREF(mro);
Py_RETURN_NONE;
}
END_TYPE_LOCK();
return mro;
return Py_NewRef(mro);
}
static PyTypeObject *best_base(PyObject *);