Skip to content

Commit

Permalink
pythongh-111924: Use PyMutex for runtime global locks.
Browse files Browse the repository at this point in the history
This replaces some usages of PyThread_type_lock with PyMutex, which
does not require memory allocation to initialize.
  • Loading branch information
colesbury committed Nov 16, 2023
1 parent 974847b commit 628f6eb
Show file tree
Hide file tree
Showing 18 changed files with 91 additions and 251 deletions.
5 changes: 4 additions & 1 deletion Include/internal/pycore_atexit.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#ifndef Py_INTERNAL_ATEXIT_H
#define Py_INTERNAL_ATEXIT_H

#include "pycore_lock.h" // PyMutex

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -15,7 +18,7 @@ extern "C" {
typedef void (*atexit_callbackfunc)(void);

struct _atexit_runtime_state {
PyThread_type_lock mutex;
PyMutex mutex;
#define NEXITFUNCS 32
atexit_callbackfunc callbacks[NEXITFUNCS];
int ncallbacks;
Expand Down
3 changes: 1 addition & 2 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ PyAPI_FUNC(int) _PyEval_MakePendingCalls(PyThreadState *);
#endif

extern void _Py_FinishPendingCalls(PyThreadState *tstate);
extern void _PyEval_InitState(PyInterpreterState *, PyThread_type_lock);
extern void _PyEval_FiniState(struct _ceval_state *ceval);
extern void _PyEval_InitState(PyInterpreterState *);
extern void _PyEval_SignalReceived(PyInterpreterState *interp);

// bitwise flags:
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_ceval_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_lock.h" // PyMutex
#include "pycore_gil.h" // struct _gil_runtime_state


typedef int (*_Py_pending_call_func)(void *);

struct _pending_calls {
int busy;
PyThread_type_lock lock;
PyMutex mutex;
/* Request for running pending calls. */
int32_t calls_to_do;
#define NPENDINGCALLS 32
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_crossinterp.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_lock.h" // PyMutex
#include "pycore_pyerrors.h"


Expand Down Expand Up @@ -128,7 +129,7 @@ struct _xidregitem {
struct _xidregistry {
int global; /* builtin types or heap types */
int initialized;
PyThread_type_lock mutex;
PyMutex mutex;
struct _xidregitem *head;
};

Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_import.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_lock.h" // PyMutex
#include "pycore_hashtable.h" // _Py_hashtable_t
#include "pycore_time.h" // _PyTime_t

Expand Down Expand Up @@ -47,7 +48,7 @@ struct _import_runtime_state {
Py_ssize_t last_module_index;
struct {
/* A lock to guard the cache. */
PyThread_type_lock mutex;
PyMutex mutex;
/* The actual cache of (filename, name, PyModuleDef) for modules.
Only legacy (single-phase init) extension modules are added
and only if they support multiple initialization (m_size >- 0)
Expand Down
10 changes: 10 additions & 0 deletions Include/internal/pycore_lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ typedef enum _PyLockFlags {
extern PyLockStatus
_PyMutex_LockTimed(PyMutex *m, _PyTime_t timeout_ns, _PyLockFlags flags);

// Lock a mutex with aditional options. See _PyLockFlags for details.
static inline void
PyMutex_LockFlags(PyMutex *m, _PyLockFlags flags)
{
uint8_t expected = _Py_UNLOCKED;
if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) {
_PyMutex_LockTimed(m, -1, flags);
}
}

// Unlock a mutex, returns 0 if the mutex is not locked (used for improved
// error messages).
extern int _PyMutex_TryUnlock(PyMutex *m);
Expand Down
5 changes: 4 additions & 1 deletion Include/internal/pycore_pymem.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#ifndef Py_INTERNAL_PYMEM_H
#define Py_INTERNAL_PYMEM_H

#include "pycore_lock.h" // PyMutex

#ifdef __cplusplus
extern "C" {
#endif
Expand Down Expand Up @@ -30,7 +33,7 @@ typedef struct {
} debug_alloc_api_t;

struct _pymem_allocators {
PyThread_type_lock mutex;
PyMutex mutex;
struct {
PyMemAllocatorEx raw;
PyMemAllocatorEx mem;
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,9 +220,9 @@ PyAPI_FUNC(int) _PyState_AddModule(
extern int _PyOS_InterruptOccurred(PyThreadState *tstate);

#define HEAD_LOCK(runtime) \
PyThread_acquire_lock((runtime)->interpreters.mutex, WAIT_LOCK)
PyMutex_LockFlags(&(runtime)->interpreters.mutex, _Py_LOCK_DONT_DETACH)
#define HEAD_UNLOCK(runtime) \
PyThread_release_lock((runtime)->interpreters.mutex)
PyMutex_Unlock(&(runtime)->interpreters.mutex)

// Get the configuration of the current interpreter.
// The caller must hold the GIL.
Expand Down
4 changes: 2 additions & 2 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ typedef struct pyruntimestate {
unsigned long _finalizing_id;

struct pyinterpreters {
PyThread_type_lock mutex;
PyMutex mutex;
/* The linked list of interpreters, newest first. */
PyInterpreterState *head;
/* The runtime's initial interpreter, which has a special role
Expand Down Expand Up @@ -236,7 +236,7 @@ typedef struct pyruntimestate {
Py_OpenCodeHookFunction open_code_hook;
void *open_code_userdata;
struct {
PyThread_type_lock mutex;
PyMutex mutex;
_Py_AuditHookEntry *head;
} audit_hooks;

Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_unicodeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C" {
# error "this header requires Py_BUILD_CORE define"
#endif

#include "pycore_lock.h" // PyMutex
#include "pycore_fileutils.h" // _Py_error_handler
#include "pycore_identifier.h" // _Py_Identifier
#include "pycore_ucnhash.h" // _PyUnicode_Name_CAPI
Expand Down Expand Up @@ -277,7 +278,7 @@ extern PyTypeObject _PyUnicodeASCIIIter_Type;
/* --- Other API ---------------------------------------------------------- */

struct _Py_unicode_runtime_ids {
PyThread_type_lock lock;
PyMutex mutex;
// next_index value must be preserved when Py_Initialize()/Py_Finalize()
// is called multiple times: see _PyUnicode_FromId() implementation.
Py_ssize_t next_index;
Expand Down
61 changes: 16 additions & 45 deletions Objects/obmalloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,13 +329,9 @@ int
_PyMem_SetDefaultAllocator(PyMemAllocatorDomain domain,
PyMemAllocatorEx *old_alloc)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must be initializing. */
return set_default_allocator_unlocked(domain, pydebug, old_alloc);
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
int res = set_default_allocator_unlocked(domain, pydebug, old_alloc);
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
return res;
}

Expand Down Expand Up @@ -467,9 +463,9 @@ set_up_allocators_unlocked(PyMemAllocatorName allocator)
int
_PyMem_SetupAllocators(PyMemAllocatorName allocator)
{
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
int res = set_up_allocators_unlocked(allocator);
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
return res;
}

Expand Down Expand Up @@ -554,9 +550,9 @@ get_current_allocator_name_unlocked(void)
const char*
_PyMem_GetCurrentAllocatorName(void)
{
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
const char *name = get_current_allocator_name_unlocked();
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
return name;
}

Expand Down Expand Up @@ -653,14 +649,9 @@ set_up_debug_hooks_unlocked(void)
void
PyMem_SetupDebugHooks(void)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must not be completely initialized yet. */
set_up_debug_hooks_unlocked();
return;
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
set_up_debug_hooks_unlocked();
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
}

static void
Expand Down Expand Up @@ -696,53 +687,33 @@ set_allocator_unlocked(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
void
PyMem_GetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must not be completely initialized yet. */
get_allocator_unlocked(domain, allocator);
return;
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
get_allocator_unlocked(domain, allocator);
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
}

void
PyMem_SetAllocator(PyMemAllocatorDomain domain, PyMemAllocatorEx *allocator)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must not be completely initialized yet. */
set_allocator_unlocked(domain, allocator);
return;
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
set_allocator_unlocked(domain, allocator);
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
}

void
PyObject_GetArenaAllocator(PyObjectArenaAllocator *allocator)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must not be completely initialized yet. */
*allocator = _PyObject_Arena;
return;
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
*allocator = _PyObject_Arena;
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
}

void
PyObject_SetArenaAllocator(PyObjectArenaAllocator *allocator)
{
if (ALLOCATORS_MUTEX == NULL) {
/* The runtime must not be completely initialized yet. */
_PyObject_Arena = *allocator;
return;
}
PyThread_acquire_lock(ALLOCATORS_MUTEX, WAIT_LOCK);
PyMutex_Lock(&ALLOCATORS_MUTEX);
_PyObject_Arena = *allocator;
PyThread_release_lock(ALLOCATORS_MUTEX);
PyMutex_Unlock(&ALLOCATORS_MUTEX);
}


Expand Down
4 changes: 2 additions & 2 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1904,7 +1904,7 @@ _PyUnicode_FromId(_Py_Identifier *id)
if (index < 0) {
struct _Py_unicode_runtime_ids *rt_ids = &interp->runtime->unicode_state.ids;

PyThread_acquire_lock(rt_ids->lock, WAIT_LOCK);
PyMutex_Lock(&rt_ids->mutex);
// Check again to detect concurrent access. Another thread can have
// initialized the index while this thread waited for the lock.
index = _Py_atomic_load_ssize(&id->index);
Expand All @@ -1914,7 +1914,7 @@ _PyUnicode_FromId(_Py_Identifier *id)
rt_ids->next_index++;
_Py_atomic_store_ssize(&id->index, index);
}
PyThread_release_lock(rt_ids->lock);
PyMutex_Unlock(&rt_ids->mutex);
}
assert(index >= 0);

Expand Down
37 changes: 9 additions & 28 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -589,9 +589,7 @@ _PyEval_ReInitThreads(PyThreadState *tstate)
take_gil(tstate);

struct _pending_calls *pending = &tstate->interp->ceval.pending;
if (_PyThread_at_fork_reinit(&pending->lock) < 0) {
return _PyStatus_ERR("Can't reinitialize pending calls lock");
}
pending->mutex = (PyMutex){0};

/* Destroy all threads except the current one */
_PyThreadState_DeleteExcept(tstate);
Expand Down Expand Up @@ -720,13 +718,10 @@ _PyEval_AddPendingCall(PyInterpreterState *interp,
assert(_Py_IsMainInterpreter(interp));
pending = &_PyRuntime.ceval.pending_mainthread;
}
/* Ensure that _PyEval_InitState() was called
and that _PyEval_FiniState() is not called yet. */
assert(pending->lock != NULL);

PyThread_acquire_lock(pending->lock, WAIT_LOCK);
PyMutex_Lock(&pending->mutex);
int result = _push_pending_call(pending, func, arg, flags);
PyThread_release_lock(pending->lock);
PyMutex_Unlock(&pending->mutex);

/* signal main loop */
SIGNAL_PENDING_CALLS(interp);
Expand Down Expand Up @@ -768,9 +763,9 @@ _make_pending_calls(struct _pending_calls *pending)
int flags = 0;

/* pop one item off the queue while holding the lock */
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
PyMutex_Lock(&pending->mutex);
_pop_pending_call(pending, &func, &arg, &flags);
PyThread_release_lock(pending->lock);
PyMutex_Unlock(&pending->mutex);

/* having released the lock, perform the callback */
if (func == NULL) {
Expand All @@ -795,7 +790,7 @@ make_pending_calls(PyInterpreterState *interp)

/* Only one thread (per interpreter) may run the pending calls
at once. In the same way, we don't do recursive pending calls. */
PyThread_acquire_lock(pending->lock, WAIT_LOCK);
PyMutex_Lock(&pending->mutex);
if (pending->busy) {
/* A pending call was added after another thread was already
handling the pending calls (and had already "unsignaled").
Expand All @@ -807,11 +802,11 @@ make_pending_calls(PyInterpreterState *interp)
care of any remaining pending calls. Until then, though,
all the interpreter's threads will be tripping the eval
breaker every time it's checked. */
PyThread_release_lock(pending->lock);
PyMutex_Unlock(&pending->mutex);
return 0;
}
pending->busy = 1;
PyThread_release_lock(pending->lock);
PyMutex_Unlock(&pending->mutex);

/* unsignal before starting to call callbacks, so that any callback
added in-between re-signals */
Expand Down Expand Up @@ -892,23 +887,9 @@ Py_MakePendingCalls(void)
}

void
_PyEval_InitState(PyInterpreterState *interp, PyThread_type_lock pending_lock)
_PyEval_InitState(PyInterpreterState *interp)
{
_gil_initialize(&interp->_gil);

struct _pending_calls *pending = &interp->ceval.pending;
assert(pending->lock == NULL);
pending->lock = pending_lock;
}

void
_PyEval_FiniState(struct _ceval_state *ceval)
{
struct _pending_calls *pending = &ceval->pending;
if (pending->lock != NULL) {
PyThread_free_lock(pending->lock);
pending->lock = NULL;
}
}


Expand Down
Loading

0 comments on commit 628f6eb

Please sign in to comment.