From c2ca7724af94df6e078a4b2e86d1be8c410d9940 Mon Sep 17 00:00:00 2001 From: Daniele Parmeggiani <8658291+dpdani@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:56:37 +0100 Subject: [PATCH] gh-150902: Optimize PyCriticalSection2 to skip locking the same locks held by the current CS2 This mimics an optimization already present for the single-mutex critical section. --- Include/internal/pycore_critical_section.h | 7 +++---- ...026-06-08-13-14-42.gh-issue-150902.-CWZ66.rst | 1 + Python/critical_section.c | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-08-13-14-42.gh-issue-150902.-CWZ66.rst diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index 2a2846b1296b..51d99d74ca15 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -196,10 +196,9 @@ _PyCriticalSection2_Begin(PyThreadState *tstate, PyCriticalSection2 *c, PyObject static inline void _PyCriticalSection2_End(PyThreadState *tstate, 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 mutex1 is NULL, we used the fast path in either + // _PyCriticalSection_BeginSlow or _PyCriticalSection2_BeginSlow for mutexes + // that are already held, and mutex2 should also be NULL. if (c->_cs_base._cs_mutex == NULL) { assert(c->_cs_mutex2 == NULL); return; diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-08-13-14-42.gh-issue-150902.-CWZ66.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-08-13-14-42.gh-issue-150902.-CWZ66.rst new file mode 100644 index 000000000000..e3b7cd387b43 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-08-13-14-42.gh-issue-150902.-CWZ66.rst @@ -0,0 +1 @@ +Apply an existing optimization of PyCriticalSection (single mutex) to PyCriticalSection2: avoid acquiring the same locks that the current CS has already acquired. diff --git a/Python/critical_section.c b/Python/critical_section.c index 98e23eda7cdd..dbee6f236a73 100644 --- a/Python/critical_section.c +++ b/Python/critical_section.c @@ -72,6 +72,22 @@ _PyCriticalSection2_BeginSlow(PyThreadState *tstate, PyCriticalSection2 *c, PyMu c->_cs_base._cs_prev = 0; return; } + // Same optimization as in _PyCriticalSection_BeginSlow: skip locking when + // recursively acquiring the same locks. + if (tstate->critical_section && + tstate->critical_section & _Py_CRITICAL_SECTION_TWO_MUTEXES) { + PyCriticalSection2 *prev2 = (PyCriticalSection2 *) + untag_critical_section(tstate->critical_section); + assert((uintptr_t)m1 < (uintptr_t)m2); + assert((uintptr_t)prev2->_cs_base._cs_mutex < + (uintptr_t)prev2->_cs_mutex2); + if (prev2->_cs_base._cs_mutex == m1 && prev2->_cs_mutex2 == m2) { + c->_cs_base._cs_mutex = NULL; + c->_cs_mutex2 = NULL; + c->_cs_base._cs_prev = 0; + return; + } + } c->_cs_base._cs_mutex = NULL; c->_cs_mutex2 = NULL; c->_cs_base._cs_prev = tstate->critical_section; -- 2.47.3