@@ -60,6 +60,13 @@ class object "PyObject *" "&PyBaseObject_Type"
6060#define NEXT_VERSION_TAG (interp ) \
6161 (interp)->types.next_version_tag
6262
63+ // Storage for the mutexes saved by type_lock_prevent_release(). Defined for
64+ // both builds so the call sites don't need to be conditionally compiled.
65+ typedef struct {
66+ PyMutex * mutex1 ;
67+ PyMutex * mutex2 ;
68+ } pinned_mutexes_t ;
69+
6370#ifdef Py_GIL_DISABLED
6471
6572// There's a global lock for types that ensures that tp_version_tag and
@@ -138,44 +145,54 @@ types_start_world(void)
138145 assert (!types_world_is_stopped ());
139146}
140147
141- // This is used to temporarily prevent the TYPE_LOCK from being suspended
142- // when held by the topmost critical section.
148+ // Temporarily prevent the mutexes held by the topmost critical section from
149+ // being released when the current thread blocks (blocking detaches the thread,
150+ // which suspends its critical sections and releases the mutexes they hold).
151+ //
152+ // All of the mutexes held by the critical section are pinned, not just
153+ // TYPE_LOCK. If only TYPE_LOCK was pinned then _PyCriticalSection_Resume()
154+ // would have to re-acquire the other mutex while TYPE_LOCK is held. That
155+ // deadlocks against a thread that holds that mutex and is waiting for
156+ // TYPE_LOCK, which is exactly what BEGIN_TYPE_DICT_LOCK() does: the type dict
157+ // mutex is on the heap and TYPE_LOCK is in _PyRuntime, so the address ordering
158+ // used by two-mutex critical sections usually acquires the dict mutex first.
159+ // By pinning both mutexes there is nothing to re-acquire on resume.
160+ //
161+ // Holding the mutexes while blocked does not prevent the world from being
162+ // stopped: a thread waiting on either of them parks with _PY_LOCK_DETACH and
163+ // so is detached while it waits.
143164static void
144- type_lock_prevent_release (void )
165+ type_lock_prevent_release (pinned_mutexes_t * pinned )
145166{
146167 PyThreadState * tstate = _PyThreadState_GET ();
147- uintptr_t * tagptr = & tstate -> critical_section ;
148- PyCriticalSection * c = (PyCriticalSection * )(* tagptr & ~_Py_CRITICAL_SECTION_MASK );
149- if (!(* tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES )) {
150- assert (c -> _cs_mutex == TYPE_LOCK );
151- c -> _cs_mutex = NULL ;
152- }
153- else {
168+ uintptr_t tag = tstate -> critical_section ;
169+ PyCriticalSection * c = (PyCriticalSection * )(tag & ~_Py_CRITICAL_SECTION_MASK );
170+ pinned -> mutex1 = c -> _cs_mutex ;
171+ pinned -> mutex2 = NULL ;
172+ c -> _cs_mutex = NULL ;
173+ if ((tag & _Py_CRITICAL_SECTION_TWO_MUTEXES ) != 0 ) {
154174 PyCriticalSection2 * c2 = (PyCriticalSection2 * )c ;
155- if (c -> _cs_mutex == TYPE_LOCK ) {
156- c -> _cs_mutex = c2 -> _cs_mutex2 ;
157- c2 -> _cs_mutex2 = NULL ;
158- } else {
159- assert (c2 -> _cs_mutex2 == TYPE_LOCK );
160- c2 -> _cs_mutex2 = NULL ;
161- }
175+ pinned -> mutex2 = c2 -> _cs_mutex2 ;
176+ c2 -> _cs_mutex2 = NULL ;
162177 }
178+ assert (pinned -> mutex1 == TYPE_LOCK || pinned -> mutex2 == TYPE_LOCK );
163179}
164180
165181static void
166- type_lock_allow_release (void )
182+ type_lock_allow_release (pinned_mutexes_t * pinned )
167183{
168184 PyThreadState * tstate = _PyThreadState_GET ();
169- uintptr_t * tagptr = & tstate -> critical_section ;
170- PyCriticalSection * c = (PyCriticalSection * )(* tagptr & ~_Py_CRITICAL_SECTION_MASK );
171- if (!(* tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES )) {
172- assert (c -> _cs_mutex == NULL );
173- c -> _cs_mutex = TYPE_LOCK ;
174- }
175- else {
185+ uintptr_t tag = tstate -> critical_section ;
186+ PyCriticalSection * c = (PyCriticalSection * )(tag & ~_Py_CRITICAL_SECTION_MASK );
187+ assert (c -> _cs_mutex == NULL );
188+ c -> _cs_mutex = pinned -> mutex1 ;
189+ if ((tag & _Py_CRITICAL_SECTION_TWO_MUTEXES ) != 0 ) {
176190 PyCriticalSection2 * c2 = (PyCriticalSection2 * )c ;
177191 assert (c2 -> _cs_mutex2 == NULL );
178- c2 -> _cs_mutex2 = TYPE_LOCK ;
192+ c2 -> _cs_mutex2 = pinned -> mutex2 ;
193+ }
194+ else {
195+ assert (pinned -> mutex2 == NULL );
179196 }
180197}
181198
@@ -192,8 +209,8 @@ type_lock_allow_release(void)
192209#define types_world_is_stopped () 1
193210#define types_stop_world ()
194211#define types_start_world ()
195- #define type_lock_prevent_release ()
196- #define type_lock_allow_release ()
212+ #define type_lock_prevent_release (pinned ) ((void)(pinned) )
213+ #define type_lock_allow_release (pinned ) ((void)(pinned) )
197214
198215#endif
199216
@@ -664,14 +681,15 @@ set_tp_mro(PyTypeObject *self, PyObject *mro, int initial)
664681 PyUnstable_Object_EnableDeferredRefcount (mro );
665682 }
666683 }
684+ pinned_mutexes_t pinned ;
667685 if (!initial ) {
668- type_lock_prevent_release ();
686+ type_lock_prevent_release (& pinned );
669687 types_stop_world ();
670688 }
671689 self -> tp_mro = mro ;
672690 if (!initial ) {
673691 types_start_world ();
674- type_lock_allow_release ();
692+ type_lock_allow_release (& pinned );
675693 }
676694}
677695
@@ -1934,13 +1952,14 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
19341952 PyObject * old_bases = lookup_tp_bases (type );
19351953 assert (old_bases != NULL );
19361954 PyTypeObject * old_base = type -> tp_base ;
1955+ pinned_mutexes_t pinned ;
19371956
1938- type_lock_prevent_release ();
1957+ type_lock_prevent_release (& pinned );
19391958 types_stop_world ();
19401959 set_tp_bases (type , Py_NewRef (new_bases ), 0 );
19411960 type -> tp_base = (PyTypeObject * )Py_NewRef (best_base );
19421961 types_start_world ();
1943- type_lock_allow_release ();
1962+ type_lock_allow_release (& pinned );
19441963
19451964 PyObject * temp = PyList_New (0 );
19461965 if (temp == NULL ) {
@@ -2001,12 +2020,12 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
20012020 if (lookup_tp_bases (type ) == new_bases ) {
20022021 assert (type -> tp_base == best_base );
20032022
2004- type_lock_prevent_release ();
2023+ type_lock_prevent_release (& pinned );
20052024 types_stop_world ();
20062025 set_tp_bases (type , old_bases , 0 );
20072026 type -> tp_base = old_base ;
20082027 types_start_world ();
2009- type_lock_allow_release ();
2028+ type_lock_allow_release (& pinned );
20102029
20112030 Py_DECREF (new_bases );
20122031 Py_DECREF (best_base );
@@ -3914,16 +3933,22 @@ apply_type_slot_updates(slot_update_t *updates)
39143933 // to update the dict. That's because TYPE_LOCK was acquired using a
39153934 // critical section.
39163935 //
3917- // The type_lock_prevent_release() call prevents the TYPE_LOCK mutex from
3918- // being released even if we block on the STM mutex. We need to take care
3919- // that we do not deadlock because of that. It is safe because we always
3920- // acquire locks in the same order: first the TYPE_LOCK mutex and then the
3921- // STM mutex.
3922- type_lock_prevent_release ();
3936+ // The type_lock_prevent_release() call prevents the mutexes held by the
3937+ // critical section (TYPE_LOCK and the type dict mutex) from being released
3938+ // even if we block on the STW mutex. We need to take care that we do not
3939+ // deadlock because of that. It is safe because a thread waiting for either
3940+ // of those mutexes detaches while it waits and so does not hold up the
3941+ // stop-the-world. Pinning both mutexes rather than only TYPE_LOCK is what
3942+ // makes this safe: otherwise the dict mutex would be released when we
3943+ // block and _PyCriticalSection_Resume() would have to re-acquire it while
3944+ // holding TYPE_LOCK, deadlocking with a thread that holds the dict mutex
3945+ // and is waiting for TYPE_LOCK.
3946+ pinned_mutexes_t pinned ;
3947+ type_lock_prevent_release (& pinned );
39233948 types_stop_world ();
39243949 apply_slot_updates (updates );
39253950 types_start_world ();
3926- type_lock_allow_release ();
3951+ type_lock_allow_release (& pinned );
39273952}
39283953
39293954#else
@@ -6518,11 +6543,12 @@ _PyType_SetFlagsRecursive(PyTypeObject *self, unsigned long mask, unsigned long
65186543 }
65196544 /* Keep TYPE_LOCK held while waiting for stop-the-world so no thread
65206545 can reassign a version tag before the flag update. */
6521- type_lock_prevent_release ();
6546+ pinned_mutexes_t pinned ;
6547+ type_lock_prevent_release (& pinned );
65226548 types_stop_world ();
65236549 set_flags_recursive (self , mask , flags );
65246550 types_start_world ();
6525- type_lock_allow_release ();
6551+ type_lock_allow_release (& pinned );
65266552 END_TYPE_LOCK ();
65276553}
65286554
0 commit comments