static inline void
_PyCriticalSection_End(PyCriticalSection *c)
{
+ // If the mutex is NULL, we used the fast path in
+ // _PyCriticalSection_BeginSlow for locks already held in the top-most
+ // critical section, and we shouldn't unlock or pop this critical section.
+ if (c->_cs_mutex == NULL) {
+ return;
+ }
PyMutex_Unlock(c->_cs_mutex);
_PyCriticalSection_Pop(c);
}
static inline void
_PyCriticalSection2_End(PyCriticalSection2 *c)
{
+ // if mutex1 is NULL, we used the fast path in
+ // _PyCriticalSection_BeginSlow for mutexes that are already held,
+ // which should only happen when mutex1 and mutex2 were the same mutex,
+ // and mutex2 should also be NULL.
+ if (c->_cs_base._cs_mutex == NULL) {
+ assert(c->_cs_mutex2 == NULL);
+ return;
+ }
if (c->_cs_mutex2) {
PyMutex_Unlock(c->_cs_mutex2);
}
"critical section must be aligned to at least 4 bytes");
#endif
+#ifdef Py_GIL_DISABLED
+static PyCriticalSection *
+untag_critical_section(uintptr_t tag)
+{
+ return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
+}
+#endif
+
void
_PyCriticalSection_BeginSlow(PyCriticalSection *c, PyMutex *m)
{
#ifdef Py_GIL_DISABLED
PyThreadState *tstate = _PyThreadState_GET();
+ // As an optimisation for locking the same object recursively, skip
+ // locking if the mutex is currently locked by the top-most critical
+ // section.
+ if (tstate->critical_section &&
+ untag_critical_section(tstate->critical_section)->_cs_mutex == m) {
+ c->_cs_mutex = NULL;
+ c->_cs_prev = 0;
+ return;
+ }
c->_cs_mutex = NULL;
c->_cs_prev = (uintptr_t)tstate->critical_section;
tstate->critical_section = (uintptr_t)c;
#endif
}
-#ifdef Py_GIL_DISABLED
-static PyCriticalSection *
-untag_critical_section(uintptr_t tag)
-{
- return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
-}
-#endif
// Release all locks held by critical sections. This is called by
// _PyThreadState_Detach.