@@ -46,6 +46,13 @@ class object "PyObject *" "&PyBaseObject_Type"
4646#define NEXT_VERSION_TAG (interp ) \
4747 (interp)->types.next_version_tag
4848
49+ // Storage for the mutexes saved by type_lock_prevent_release(). Defined for
50+ // both builds so the call sites don't need to be conditionally compiled.
51+ typedef struct {
52+ PyMutex * mutex1 ;
53+ PyMutex * mutex2 ;
54+ } pinned_mutexes_t ;
55+
4956#ifdef Py_GIL_DISABLED
5057
5158// There's a global lock for types that ensures that tp_version_tag and
@@ -124,44 +131,54 @@ types_start_world(void)
124131 assert (!types_world_is_stopped ());
125132}
126133
127- // This is used to temporarily prevent the TYPE_LOCK from being suspended
128- // when held by the topmost critical section.
134+ // Temporarily prevent the mutexes held by the topmost critical section from
135+ // being released when the current thread blocks (blocking detaches the thread,
136+ // which suspends its critical sections and releases the mutexes they hold).
137+ //
138+ // All of the mutexes held by the critical section are pinned, not just
139+ // TYPE_LOCK. If only TYPE_LOCK was pinned then _PyCriticalSection_Resume()
140+ // would have to re-acquire the other mutex while TYPE_LOCK is held. That
141+ // deadlocks against a thread that holds that mutex and is waiting for
142+ // TYPE_LOCK, which is exactly what BEGIN_TYPE_DICT_LOCK() does: the type dict
143+ // mutex is on the heap and TYPE_LOCK is in _PyRuntime, so the address ordering
144+ // used by two-mutex critical sections usually acquires the dict mutex first.
145+ // By pinning both mutexes there is nothing to re-acquire on resume.
146+ //
147+ // Holding the mutexes while blocked does not prevent the world from being
148+ // stopped: a thread waiting on either of them parks with _PY_LOCK_DETACH and
149+ // so is detached while it waits.
129150static void
130- type_lock_prevent_release (void )
151+ type_lock_prevent_release (pinned_mutexes_t * pinned )
131152{
132153 PyThreadState * tstate = _PyThreadState_GET ();
133- uintptr_t * tagptr = & tstate -> critical_section ;
134- PyCriticalSection * c = (PyCriticalSection * )(* tagptr & ~_Py_CRITICAL_SECTION_MASK );
135- if (!(* tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES )) {
136- assert (c -> _cs_mutex == TYPE_LOCK );
137- c -> _cs_mutex = NULL ;
138- }
139- else {
154+ uintptr_t tag = tstate -> critical_section ;
155+ PyCriticalSection * c = (PyCriticalSection * )(tag & ~_Py_CRITICAL_SECTION_MASK );
156+ pinned -> mutex1 = c -> _cs_mutex ;
157+ pinned -> mutex2 = NULL ;
158+ c -> _cs_mutex = NULL ;
159+ if ((tag & _Py_CRITICAL_SECTION_TWO_MUTEXES ) != 0 ) {
140160 PyCriticalSection2 * c2 = (PyCriticalSection2 * )c ;
141- if (c -> _cs_mutex == TYPE_LOCK ) {
142- c -> _cs_mutex = c2 -> _cs_mutex2 ;
143- c2 -> _cs_mutex2 = NULL ;
144- } else {
145- assert (c2 -> _cs_mutex2 == TYPE_LOCK );
146- c2 -> _cs_mutex2 = NULL ;
147- }
161+ pinned -> mutex2 = c2 -> _cs_mutex2 ;
162+ c2 -> _cs_mutex2 = NULL ;
148163 }
164+ assert (pinned -> mutex1 == TYPE_LOCK || pinned -> mutex2 == TYPE_LOCK );
149165}
150166
151167static void
152- type_lock_allow_release (void )
168+ type_lock_allow_release (pinned_mutexes_t * pinned )
153169{
154170 PyThreadState * tstate = _PyThreadState_GET ();
155- uintptr_t * tagptr = & tstate -> critical_section ;
156- PyCriticalSection * c = (PyCriticalSection * )(* tagptr & ~_Py_CRITICAL_SECTION_MASK );
157- if (!(* tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES )) {
158- assert (c -> _cs_mutex == NULL );
159- c -> _cs_mutex = TYPE_LOCK ;
160- }
161- else {
171+ uintptr_t tag = tstate -> critical_section ;
172+ PyCriticalSection * c = (PyCriticalSection * )(tag & ~_Py_CRITICAL_SECTION_MASK );
173+ assert (c -> _cs_mutex == NULL );
174+ c -> _cs_mutex = pinned -> mutex1 ;
175+ if ((tag & _Py_CRITICAL_SECTION_TWO_MUTEXES ) != 0 ) {
162176 PyCriticalSection2 * c2 = (PyCriticalSection2 * )c ;
163177 assert (c2 -> _cs_mutex2 == NULL );
164- c2 -> _cs_mutex2 = TYPE_LOCK ;
178+ c2 -> _cs_mutex2 = pinned -> mutex2 ;
179+ }
180+ else {
181+ assert (pinned -> mutex2 == NULL );
165182 }
166183}
167184
@@ -178,8 +195,8 @@ type_lock_allow_release(void)
178195#define types_world_is_stopped () 1
179196#define types_stop_world ()
180197#define types_start_world ()
181- #define type_lock_prevent_release ()
182- #define type_lock_allow_release ()
198+ #define type_lock_prevent_release (pinned ) ((void)(pinned) )
199+ #define type_lock_allow_release (pinned ) ((void)(pinned) )
183200
184201#endif
185202
@@ -650,14 +667,15 @@ set_tp_mro(PyTypeObject *self, PyObject *mro, int initial)
650667 PyUnstable_Object_EnableDeferredRefcount (mro );
651668 }
652669 }
670+ pinned_mutexes_t pinned ;
653671 if (!initial ) {
654- type_lock_prevent_release ();
672+ type_lock_prevent_release (& pinned );
655673 types_stop_world ();
656674 }
657675 self -> tp_mro = mro ;
658676 if (!initial ) {
659677 types_start_world ();
660- type_lock_allow_release ();
678+ type_lock_allow_release (& pinned );
661679 }
662680}
663681
@@ -1864,13 +1882,14 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
18641882 PyObject * old_bases = lookup_tp_bases (type );
18651883 assert (old_bases != NULL );
18661884 PyTypeObject * old_base = type -> tp_base ;
1885+ pinned_mutexes_t pinned ;
18671886
1868- type_lock_prevent_release ();
1887+ type_lock_prevent_release (& pinned );
18691888 types_stop_world ();
18701889 set_tp_bases (type , Py_NewRef (new_bases ), 0 );
18711890 type -> tp_base = (PyTypeObject * )Py_NewRef (best_base );
18721891 types_start_world ();
1873- type_lock_allow_release ();
1892+ type_lock_allow_release (& pinned );
18741893
18751894 PyObject * temp = PyList_New (0 );
18761895 if (temp == NULL ) {
@@ -1931,12 +1950,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
19311950 if (lookup_tp_bases (type ) == new_bases ) {
19321951 assert (type -> tp_base == best_base );
19331952
1934- type_lock_prevent_release ();
1953+ type_lock_prevent_release (& pinned );
19351954 types_stop_world ();
19361955 set_tp_bases (type , old_bases , 0 );
19371956 type -> tp_base = old_base ;
19381957 types_start_world ();
1939- type_lock_allow_release ();
1958+ type_lock_allow_release (& pinned );
19401959
19411960 Py_DECREF (new_bases );
19421961 Py_DECREF (best_base );
@@ -3844,16 +3863,22 @@ apply_type_slot_updates(slot_update_t *updates)
38443863 // to update the dict. That's because TYPE_LOCK was acquired using a
38453864 // critical section.
38463865 //
3847- // The type_lock_prevent_release() call prevents the TYPE_LOCK mutex from
3848- // being released even if we block on the STM mutex. We need to take care
3849- // that we do not deadlock because of that. It is safe because we always
3850- // acquire locks in the same order: first the TYPE_LOCK mutex and then the
3851- // STM mutex.
3852- type_lock_prevent_release ();
3866+ // The type_lock_prevent_release() call prevents the mutexes held by the
3867+ // critical section (TYPE_LOCK and the type dict mutex) from being released
3868+ // even if we block on the STW mutex. We need to take care that we do not
3869+ // deadlock because of that. It is safe because a thread waiting for either
3870+ // of those mutexes detaches while it waits and so does not hold up the
3871+ // stop-the-world. Pinning both mutexes rather than only TYPE_LOCK is what
3872+ // makes this safe: otherwise the dict mutex would be released when we
3873+ // block and _PyCriticalSection_Resume() would have to re-acquire it while
3874+ // holding TYPE_LOCK, deadlocking with a thread that holds the dict mutex
3875+ // and is waiting for TYPE_LOCK.
3876+ pinned_mutexes_t pinned ;
3877+ type_lock_prevent_release (& pinned );
38533878 types_stop_world ();
38543879 apply_slot_updates (updates );
38553880 types_start_world ();
3856- type_lock_allow_release ();
3881+ type_lock_allow_release (& pinned );
38573882}
38583883
38593884#else
@@ -6356,11 +6381,12 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long
63566381 }
63576382 /* Keep TYPE_LOCK held while waiting for stop-the-world so no thread
63586383 can reassign a version tag before the flag update. */
6359- type_lock_prevent_release ();
6384+ pinned_mutexes_t pinned ;
6385+ type_lock_prevent_release (& pinned );
63606386 types_stop_world ();
63616387 set_flags_recursive (self , mask , flags );
63626388 types_start_world ();
6363- type_lock_allow_release ();
6389+ type_lock_allow_release (& pinned );
63646390 END_TYPE_LOCK ();
63656391}
63666392
0 commit comments