Skip to content

Commit f26ed45

Browse files
pythongh-114203: skip locking if object is already locked by two-mutex critical section (python#141476)
1 parent da7f4e4 commit f26ed45

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Skip locking if object is already locked by two-mutex critical section.

Modules/_testinternalcapi/test_critical_sections.c

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,111 @@ test_critical_sections_gc(PyObject *self, PyObject *Py_UNUSED(args))
284284

285285
#endif
286286

287+
#ifdef Py_GIL_DISABLED
288+
289+
static PyObject *
290+
test_critical_section1_reacquisition(PyObject *self, PyObject *Py_UNUSED(args))
291+
{
292+
PyObject *a = PyDict_New();
293+
assert(a != NULL);
294+
295+
PyCriticalSection cs1, cs2;
296+
// First acquisition of critical section on object locks it
297+
PyCriticalSection_Begin(&cs1, a);
298+
assert(PyMutex_IsLocked(&a->ob_mutex));
299+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
300+
assert(_PyThreadState_GET()->critical_section == (uintptr_t)&cs1);
301+
// Attempting to re-acquire critical section on same object which
302+
// is already locked by top-most critical section is a no-op.
303+
PyCriticalSection_Begin(&cs2, a);
304+
assert(PyMutex_IsLocked(&a->ob_mutex));
305+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
306+
assert(_PyThreadState_GET()->critical_section == (uintptr_t)&cs1);
307+
// Releasing second critical section is a no-op.
308+
PyCriticalSection_End(&cs2);
309+
assert(PyMutex_IsLocked(&a->ob_mutex));
310+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
311+
assert(_PyThreadState_GET()->critical_section == (uintptr_t)&cs1);
312+
// Releasing first critical section unlocks the object
313+
PyCriticalSection_End(&cs1);
314+
assert(!PyMutex_IsLocked(&a->ob_mutex));
315+
316+
Py_DECREF(a);
317+
Py_RETURN_NONE;
318+
}
319+
320+
static PyObject *
321+
test_critical_section2_reacquisition(PyObject *self, PyObject *Py_UNUSED(args))
322+
{
323+
PyObject *a = PyDict_New();
324+
assert(a != NULL);
325+
PyObject *b = PyDict_New();
326+
assert(b != NULL);
327+
328+
PyCriticalSection2 cs;
329+
// First acquisition of critical section on objects locks them
330+
PyCriticalSection2_Begin(&cs, a, b);
331+
assert(PyMutex_IsLocked(&a->ob_mutex));
332+
assert(PyMutex_IsLocked(&b->ob_mutex));
333+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
334+
assert((_PyThreadState_GET()->critical_section &
335+
~_Py_CRITICAL_SECTION_MASK) == (uintptr_t)&cs);
336+
337+
// Attempting to re-acquire critical section on either of two
338+
// objects already locked by top-most critical section is a no-op.
339+
340+
// Check re-acquiring on first object
341+
PyCriticalSection a_cs;
342+
PyCriticalSection_Begin(&a_cs, a);
343+
assert(PyMutex_IsLocked(&a->ob_mutex));
344+
assert(PyMutex_IsLocked(&b->ob_mutex));
345+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
346+
assert((_PyThreadState_GET()->critical_section &
347+
~_Py_CRITICAL_SECTION_MASK) == (uintptr_t)&cs);
348+
// Releasing critical section on either object is a no-op.
349+
PyCriticalSection_End(&a_cs);
350+
assert(PyMutex_IsLocked(&a->ob_mutex));
351+
assert(PyMutex_IsLocked(&b->ob_mutex));
352+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
353+
assert((_PyThreadState_GET()->critical_section &
354+
~_Py_CRITICAL_SECTION_MASK) == (uintptr_t)&cs);
355+
356+
// Check re-acquiring on second object
357+
PyCriticalSection b_cs;
358+
PyCriticalSection_Begin(&b_cs, b);
359+
assert(PyMutex_IsLocked(&a->ob_mutex));
360+
assert(PyMutex_IsLocked(&b->ob_mutex));
361+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
362+
assert((_PyThreadState_GET()->critical_section &
363+
~_Py_CRITICAL_SECTION_MASK) == (uintptr_t)&cs);
364+
// Releasing critical section on either object is a no-op.
365+
PyCriticalSection_End(&b_cs);
366+
assert(PyMutex_IsLocked(&a->ob_mutex));
367+
assert(PyMutex_IsLocked(&b->ob_mutex));
368+
assert(_PyCriticalSection_IsActive(PyThreadState_GET()->critical_section));
369+
assert((_PyThreadState_GET()->critical_section &
370+
~_Py_CRITICAL_SECTION_MASK) == (uintptr_t)&cs);
371+
372+
// Releasing critical section on both objects unlocks them
373+
PyCriticalSection2_End(&cs);
374+
assert(!PyMutex_IsLocked(&a->ob_mutex));
375+
assert(!PyMutex_IsLocked(&b->ob_mutex));
376+
377+
Py_DECREF(a);
378+
Py_DECREF(b);
379+
Py_RETURN_NONE;
380+
}
381+
382+
#endif // Py_GIL_DISABLED
383+
287384
static PyMethodDef test_methods[] = {
288385
{"test_critical_sections", test_critical_sections, METH_NOARGS},
289386
{"test_critical_sections_nest", test_critical_sections_nest, METH_NOARGS},
290387
{"test_critical_sections_suspend", test_critical_sections_suspend, METH_NOARGS},
388+
#ifdef Py_GIL_DISABLED
389+
{"test_critical_section1_reacquisition", test_critical_section1_reacquisition, METH_NOARGS},
390+
{"test_critical_section2_reacquisition", test_critical_section2_reacquisition, METH_NOARGS},
391+
#endif
291392
#ifdef Py_CAN_START_THREADS
292393
{"test_critical_sections_threads", test_critical_sections_threads, METH_NOARGS},
293394
{"test_critical_sections_gc", test_critical_sections_gc, METH_NOARGS},

Python/critical_section.c

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,24 @@ _PyCriticalSection_BeginSlow(PyCriticalSection *c, PyMutex *m)
2424
// As an optimisation for locking the same object recursively, skip
2525
// locking if the mutex is currently locked by the top-most critical
2626
// section.
27-
if (tstate->critical_section &&
28-
untag_critical_section(tstate->critical_section)->_cs_mutex == m) {
29-
c->_cs_mutex = NULL;
30-
c->_cs_prev = 0;
31-
return;
27+
// If the top-most critical section is a two-mutex critical section,
28+
// then locking is skipped if either mutex is m.
29+
if (tstate->critical_section) {
30+
PyCriticalSection *prev = untag_critical_section(tstate->critical_section);
31+
if (prev->_cs_mutex == m) {
32+
c->_cs_mutex = NULL;
33+
c->_cs_prev = 0;
34+
return;
35+
}
36+
if (tstate->critical_section & _Py_CRITICAL_SECTION_TWO_MUTEXES) {
37+
PyCriticalSection2 *prev2 = (PyCriticalSection2 *)
38+
untag_critical_section(tstate->critical_section);
39+
if (prev2->_cs_mutex2 == m) {
40+
c->_cs_mutex = NULL;
41+
c->_cs_prev = 0;
42+
return;
43+
}
44+
}
3245
}
3346
c->_cs_mutex = NULL;
3447
c->_cs_prev = (uintptr_t)tstate->critical_section;

0 commit comments

Comments
 (0)