Skip to content

Commit 5402465

Browse files
authored
[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 0b65c88)
1 parent a5ed66d commit 5402465

File tree

1 file changed

+74
-11
lines changed

1 file changed

+74
-11
lines changed

Objects/typeobject.c

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,73 @@ class object "PyObject *" "&PyBaseObject_Type"
7676
#define ASSERT_TYPE_LOCK_HELD() \
7777
_Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(TYPE_LOCK)
7878

79+
static void
80+
types_stop_world(void)
81+
{
82+
PyInterpreterState *interp = _PyInterpreterState_GET();
83+
_PyEval_StopTheWorld(interp);
84+
}
85+
86+
static void
87+
types_start_world(void)
88+
{
89+
PyInterpreterState *interp = _PyInterpreterState_GET();
90+
_PyEval_StartTheWorld(interp);
91+
}
92+
93+
// This is used to temporarily prevent the TYPE_LOCK from being suspended
94+
// when held by the topmost critical section.
95+
static void
96+
type_lock_prevent_release(void)
97+
{
98+
PyThreadState *tstate = _PyThreadState_GET();
99+
uintptr_t *tagptr = &tstate->critical_section;
100+
PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
101+
if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
102+
assert(c->_cs_mutex == TYPE_LOCK);
103+
c->_cs_mutex = NULL;
104+
}
105+
else {
106+
PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
107+
if (c->_cs_mutex == TYPE_LOCK) {
108+
c->_cs_mutex = c2->_cs_mutex2;
109+
c2->_cs_mutex2 = NULL;
110+
}
111+
else {
112+
assert(c2->_cs_mutex2 == TYPE_LOCK);
113+
c2->_cs_mutex2 = NULL;
114+
}
115+
}
116+
}
117+
118+
static void
119+
type_lock_allow_release(void)
120+
{
121+
PyThreadState *tstate = _PyThreadState_GET();
122+
uintptr_t *tagptr = &tstate->critical_section;
123+
PyCriticalSection *c = (PyCriticalSection *)(*tagptr & ~_Py_CRITICAL_SECTION_MASK);
124+
if (!(*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
125+
assert(c->_cs_mutex == NULL);
126+
c->_cs_mutex = TYPE_LOCK;
127+
}
128+
else {
129+
PyCriticalSection2 *c2 = (PyCriticalSection2 *)c;
130+
assert(c2->_cs_mutex2 == NULL);
131+
c2->_cs_mutex2 = TYPE_LOCK;
132+
}
133+
}
134+
79135
#else
80136

81137
#define BEGIN_TYPE_LOCK()
82138
#define END_TYPE_LOCK()
83139
#define BEGIN_TYPE_DICT_LOCK(d)
84140
#define END_TYPE_DICT_LOCK()
85141
#define ASSERT_TYPE_LOCK_HELD()
142+
#define types_stop_world()
143+
#define types_start_world()
144+
#define type_lock_prevent_release()
145+
#define type_lock_allow_release()
86146

87147
#endif
88148

@@ -541,7 +601,6 @@ clear_tp_bases(PyTypeObject *self, int final)
541601
static inline PyObject *
542602
lookup_tp_mro(PyTypeObject *self)
543603
{
544-
ASSERT_TYPE_LOCK_HELD();
545604
return self->tp_mro;
546605
}
547606

@@ -580,8 +639,19 @@ set_tp_mro(PyTypeObject *self, PyObject *mro, int initial)
580639
/* Other checks are done via set_tp_bases. */
581640
_Py_SetImmortal(mro);
582641
}
642+
else {
643+
PyUnstable_Object_EnableDeferredRefcount(mro);
644+
}
645+
}
646+
if (!initial) {
647+
type_lock_prevent_release();
648+
types_stop_world();
583649
}
584650
self->tp_mro = mro;
651+
if (!initial) {
652+
types_start_world();
653+
type_lock_allow_release();
654+
}
585655
}
586656

587657
static inline void
@@ -1627,18 +1697,11 @@ static PyObject *
16271697
type_get_mro(PyObject *tp, void *Py_UNUSED(closure))
16281698
{
16291699
PyTypeObject *type = PyTypeObject_CAST(tp);
1630-
PyObject *mro;
1631-
1632-
BEGIN_TYPE_LOCK();
1633-
mro = lookup_tp_mro(type);
1700+
PyObject *mro = lookup_tp_mro(type);
16341701
if (mro == NULL) {
1635-
mro = Py_None;
1636-
} else {
1637-
Py_INCREF(mro);
1702+
Py_RETURN_NONE;
16381703
}
1639-
1640-
END_TYPE_LOCK();
1641-
return mro;
1704+
return Py_NewRef(mro);
16421705
}
16431706

16441707
static PyTypeObject *best_base(PyObject *);

0 commit comments

Comments
 (0)