gh-111569: Implement Python critical section API (gh-111571)

Critical sections are helpers to replace the global interpreter lock
with finer grained locking.  They provide similar guarantees to the GIL
and avoid the deadlock risk that plain locking involves.  Critical
sections are implicitly ended whenever the GIL would be released.  They
are resumed when the GIL would be acquired.  Nested critical sections
behave as if the sections were interleaved.
This commit is contained in:
Sam Gross 2023-11-08 17:39:29 -05:00 committed by GitHub
parent 0b718e6407
commit 31c90d5838
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 630 additions and 7 deletions

100
Python/critical_section.c Normal file
View file

@ -0,0 +1,100 @@
#include "Python.h"
#include "pycore_lock.h"
#include "pycore_critical_section.h"
static_assert(_Alignof(_PyCriticalSection) >= 4,
"critical section must be aligned to at least 4 bytes");
void
_PyCriticalSection_BeginSlow(_PyCriticalSection *c, PyMutex *m)
{
PyThreadState *tstate = _PyThreadState_GET();
c->mutex = NULL;
c->prev = (uintptr_t)tstate->critical_section;
tstate->critical_section = (uintptr_t)c;
_PyMutex_LockSlow(m);
c->mutex = m;
}
void
_PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2,
int is_m1_locked)
{
PyThreadState *tstate = _PyThreadState_GET();
c->base.mutex = NULL;
c->mutex2 = NULL;
c->base.prev = tstate->critical_section;
tstate->critical_section = (uintptr_t)c | _Py_CRITICAL_SECTION_TWO_MUTEXES;
if (!is_m1_locked) {
PyMutex_Lock(m1);
}
PyMutex_Lock(m2);
c->base.mutex = m1;
c->mutex2 = m2;
}
static _PyCriticalSection *
untag_critical_section(uintptr_t tag)
{
return (_PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
}
// Release all locks held by critical sections. This is called by
// _PyThreadState_Detach.
void
_PyCriticalSection_SuspendAll(PyThreadState *tstate)
{
uintptr_t *tagptr = &tstate->critical_section;
while (_PyCriticalSection_IsActive(*tagptr)) {
_PyCriticalSection *c = untag_critical_section(*tagptr);
if (c->mutex) {
PyMutex_Unlock(c->mutex);
if ((*tagptr & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
_PyCriticalSection2 *c2 = (_PyCriticalSection2 *)c;
if (c2->mutex2) {
PyMutex_Unlock(c2->mutex2);
}
}
}
*tagptr |= _Py_CRITICAL_SECTION_INACTIVE;
tagptr = &c->prev;
}
}
void
_PyCriticalSection_Resume(PyThreadState *tstate)
{
uintptr_t p = tstate->critical_section;
_PyCriticalSection *c = untag_critical_section(p);
assert(!_PyCriticalSection_IsActive(p));
PyMutex *m1 = c->mutex;
c->mutex = NULL;
PyMutex *m2 = NULL;
_PyCriticalSection2 *c2 = NULL;
if ((p & _Py_CRITICAL_SECTION_TWO_MUTEXES)) {
c2 = (_PyCriticalSection2 *)c;
m2 = c2->mutex2;
c2->mutex2 = NULL;
}
if (m1) {
PyMutex_Lock(m1);
}
if (m2) {
PyMutex_Lock(m2);
}
c->mutex = m1;
if (m2) {
c2->mutex2 = m2;
}
tstate->critical_section &= ~_Py_CRITICAL_SECTION_INACTIVE;
}

View file

@ -4,6 +4,7 @@
#include "Python.h"
#include "pycore_ceval.h"
#include "pycore_code.h" // stats
#include "pycore_critical_section.h" // _PyCriticalSection_Resume()
#include "pycore_dtoa.h" // _dtoa_state_INIT()
#include "pycore_emscripten_trampoline.h" // _Py_EmscriptenTrampoline_Init()
#include "pycore_frame.h"
@ -1911,6 +1912,12 @@ _PyThreadState_Attach(PyThreadState *tstate)
Py_FatalError("thread attach failed");
}
// Resume previous critical section. This acquires the lock(s) from the
// top-most critical section.
if (tstate->critical_section != 0) {
_PyCriticalSection_Resume(tstate);
}
#if defined(Py_DEBUG)
errno = err;
#endif
@ -1922,6 +1929,9 @@ _PyThreadState_Detach(PyThreadState *tstate)
// XXX assert(tstate_is_alive(tstate) && tstate_is_bound(tstate));
assert(tstate->state == _Py_THREAD_ATTACHED);
assert(tstate == current_fast_get(&_PyRuntime));
if (tstate->critical_section != 0) {
_PyCriticalSection_SuspendAll(tstate);
}
tstate_set_detached(tstate);
tstate_deactivate(tstate);
current_fast_clear(&_PyRuntime);