https://github.com/python/cpython/commit/082f370cdd0ec1484b033c70ec81b4b7a972ee2c
commit: 082f370cdd0ec1484b033c70ec81b4b7a972ee2c
branch: main
author: Peter Bierma <zintensity...@gmail.com>
committer: ZeroIntensity <zintensity...@gmail.com>
date: 2025-08-07T11:24:50-04:00
summary:

gh-137514: Add a free-threading wrapper for mutexes (GH-137515)

Add `FT_MUTEX_LOCK`/`FT_MUTEX_UNLOCK`, which call `PyMutex_Lock` and 
`PyMutex_Unlock` on the free-threaded build, and no-op otherwise.

files:
M Include/internal/pycore_pyatomic_ft_wrappers.h
M Modules/_ctypes/malloc_closure.c
M Objects/codeobject.c
M Objects/unicodeobject.c
M Parser/pegen.c
M Python/ceval_gil.c
M Python/codecs.c
M Python/legacy_tracing.c
M Python/pystate.c

diff --git a/Include/internal/pycore_pyatomic_ft_wrappers.h 
b/Include/internal/pycore_pyatomic_ft_wrappers.h
index 3e41e2fd1569ca..c31c33657002ec 100644
--- a/Include/internal/pycore_pyatomic_ft_wrappers.h
+++ b/Include/internal/pycore_pyatomic_ft_wrappers.h
@@ -111,6 +111,8 @@ extern "C" {
     _Py_atomic_load_ullong_relaxed(&value)
 #define FT_ATOMIC_ADD_SSIZE(value, new_value) \
     (void)_Py_atomic_add_ssize(&value, new_value)
+#define FT_MUTEX_LOCK(lock) PyMutex_Lock(lock)
+#define FT_MUTEX_UNLOCK(lock) PyMutex_Unlock(lock)
 
 #else
 #define FT_ATOMIC_LOAD_PTR(value) value
@@ -159,6 +161,8 @@ extern "C" {
 #define FT_ATOMIC_LOAD_ULLONG_RELAXED(value) value
 #define FT_ATOMIC_STORE_ULLONG_RELAXED(value, new_value) value = new_value
 #define FT_ATOMIC_ADD_SSIZE(value, new_value) (void)(value += new_value)
+#define FT_MUTEX_LOCK(lock) do {} while (0)
+#define FT_MUTEX_UNLOCK(lock) do {} while (0)
 
 #endif
 
diff --git a/Modules/_ctypes/malloc_closure.c b/Modules/_ctypes/malloc_closure.c
index 80ba96614bff79..db405acf8727b5 100644
--- a/Modules/_ctypes/malloc_closure.c
+++ b/Modules/_ctypes/malloc_closure.c
@@ -30,11 +30,6 @@
 
 #ifdef Py_GIL_DISABLED
 static PyMutex malloc_closure_lock;
-# define MALLOC_CLOSURE_LOCK()   PyMutex_Lock(&malloc_closure_lock)
-# define MALLOC_CLOSURE_UNLOCK() PyMutex_Unlock(&malloc_closure_lock)
-#else
-# define MALLOC_CLOSURE_LOCK()   ((void)0)
-# define MALLOC_CLOSURE_UNLOCK() ((void)0)
 #endif
 
 typedef union _tagITEM {
@@ -120,11 +115,11 @@ void Py_ffi_closure_free(void *p)
     }
 #endif
 #endif
-    MALLOC_CLOSURE_LOCK();
+    FT_MUTEX_LOCK(&malloc_closure_lock);
     ITEM *item = (ITEM *)p;
     item->next = free_list;
     free_list = item;
-    MALLOC_CLOSURE_UNLOCK();
+    FT_MUTEX_UNLOCK(&malloc_closure_lock);
 }
 
 /* return one item from the free list, allocating more if needed */
@@ -143,13 +138,13 @@ void *Py_ffi_closure_alloc(size_t size, void** codeloc)
     }
 #endif
 #endif
-    MALLOC_CLOSURE_LOCK();
+    FT_MUTEX_LOCK(&malloc_closure_lock);
     ITEM *item;
     if (!free_list) {
         more_core();
     }
     if (!free_list) {
-        MALLOC_CLOSURE_UNLOCK();
+        FT_MUTEX_UNLOCK(&malloc_closure_lock);
         return NULL;
     }
     item = free_list;
@@ -160,6 +155,6 @@ void *Py_ffi_closure_alloc(size_t size, void** codeloc)
 #else
     *codeloc = (void *)item;
 #endif
-    MALLOC_CLOSURE_UNLOCK();
+    FT_MUTEX_UNLOCK(&malloc_closure_lock);
     return (void *)item;
 }
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 42e021679b583f..478c571345cd03 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -550,16 +550,12 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor 
*con)
     co->co_framesize = nlocalsplus + con->stacksize + FRAME_SPECIALS_SIZE;
     co->co_ncellvars = ncellvars;
     co->co_nfreevars = nfreevars;
-#ifdef Py_GIL_DISABLED
-    PyMutex_Lock(&interp->func_state.mutex);
-#endif
+    FT_MUTEX_LOCK(&interp->func_state.mutex);
     co->co_version = interp->func_state.next_version;
     if (interp->func_state.next_version != 0) {
         interp->func_state.next_version++;
     }
-#ifdef Py_GIL_DISABLED
-    PyMutex_Unlock(&interp->func_state.mutex);
-#endif
+    FT_MUTEX_UNLOCK(&interp->func_state.mutex);
     co->_co_monitoring = NULL;
     co->_co_instrumentation_version = 0;
     /* not set */
@@ -689,7 +685,7 @@ intern_code_constants(struct _PyCodeConstructor *con)
 #ifdef Py_GIL_DISABLED
     PyInterpreterState *interp = _PyInterpreterState_GET();
     struct _py_code_state *state = &interp->code_state;
-    PyMutex_Lock(&state->mutex);
+    FT_MUTEX_LOCK(&state->mutex);
 #endif
     if (intern_strings(con->names) < 0) {
         goto error;
@@ -700,15 +696,11 @@ intern_code_constants(struct _PyCodeConstructor *con)
     if (intern_strings(con->localsplusnames) < 0) {
         goto error;
     }
-#ifdef Py_GIL_DISABLED
-    PyMutex_Unlock(&state->mutex);
-#endif
+    FT_MUTEX_UNLOCK(&state->mutex);
     return 0;
 
 error:
-#ifdef Py_GIL_DISABLED
-    PyMutex_Unlock(&state->mutex);
-#endif
+    FT_MUTEX_UNLOCK(&state->mutex);
     return -1;
 }
 
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 425e4681f0a4dc..47802c5e915be2 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -114,14 +114,6 @@ NOTE: In the interpreter's initialization phase, some 
globals are currently
 #  define _PyUnicode_CHECK(op) PyUnicode_Check(op)
 #endif
 
-#ifdef Py_GIL_DISABLED
-#  define LOCK_INTERNED(interp) PyMutex_Lock(&_Py_INTERP_CACHED_OBJECT(interp, 
interned_mutex))
-#  define UNLOCK_INTERNED(interp) 
PyMutex_Unlock(&_Py_INTERP_CACHED_OBJECT(interp, interned_mutex))
-#else
-#  define LOCK_INTERNED(interp)
-#  define UNLOCK_INTERNED(interp)
-#endif
-
 static inline char* _PyUnicode_UTF8(PyObject *op)
 {
     return FT_ATOMIC_LOAD_PTR_ACQUIRE(_PyCompactUnicodeObject_CAST(op)->utf8);
@@ -15988,14 +15980,16 @@ intern_common(PyInterpreterState *interp, PyObject *s 
/* stolen */,
     /* Do a setdefault on the per-interpreter cache. */
     PyObject *interned = get_interned_dict(interp);
     assert(interned != NULL);
-
-    LOCK_INTERNED(interp);
+#ifdef Py_GIL_DISABLED
+#  define INTERN_MUTEX &_Py_INTERP_CACHED_OBJECT(interp, interned_mutex)
+#endif
+    FT_MUTEX_LOCK(INTERN_MUTEX);
     PyObject *t;
     {
         int res = PyDict_SetDefaultRef(interned, s, s, &t);
         if (res < 0) {
             PyErr_Clear();
-            UNLOCK_INTERNED(interp);
+            FT_MUTEX_UNLOCK(INTERN_MUTEX);
             return s;
         }
         else if (res == 1) {
@@ -16005,7 +15999,7 @@ intern_common(PyInterpreterState *interp, PyObject *s 
/* stolen */,
                     PyUnicode_CHECK_INTERNED(t) == SSTATE_INTERNED_MORTAL) {
                 immortalize_interned(t);
             }
-            UNLOCK_INTERNED(interp);
+            FT_MUTEX_UNLOCK(INTERN_MUTEX);
             return t;
         }
         else {
@@ -16038,7 +16032,7 @@ intern_common(PyInterpreterState *interp, PyObject *s 
/* stolen */,
         immortalize_interned(s);
     }
 
-    UNLOCK_INTERNED(interp);
+    FT_MUTEX_UNLOCK(INTERN_MUTEX);
     return s;
 }
 
diff --git a/Parser/pegen.c b/Parser/pegen.c
index 50641de27d37fd..70493031656028 100644
--- a/Parser/pegen.c
+++ b/Parser/pegen.c
@@ -5,6 +5,7 @@
 #include "pycore_pyerrors.h"      // PyExc_IncompleteInputError
 #include "pycore_runtime.h"     // _PyRuntime
 #include "pycore_unicodeobject.h" // _PyUnicode_InternImmortal
+#include "pycore_pyatomic_ft_wrappers.h"
 #include <errcode.h>
 
 #include "lexer/lexer.h"
@@ -299,22 +300,14 @@ _PyPegen_fill_token(Parser *p)
 #define NSTATISTICS _PYPEGEN_NSTATISTICS
 #define memo_statistics _PyRuntime.parser.memo_statistics
 
-#ifdef Py_GIL_DISABLED
-#define MUTEX_LOCK() PyMutex_Lock(&_PyRuntime.parser.mutex)
-#define MUTEX_UNLOCK() PyMutex_Unlock(&_PyRuntime.parser.mutex)
-#else
-#define MUTEX_LOCK()
-#define MUTEX_UNLOCK()
-#endif
-
 void
 _PyPegen_clear_memo_statistics(void)
 {
-    MUTEX_LOCK();
+    FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
     for (int i = 0; i < NSTATISTICS; i++) {
         memo_statistics[i] = 0;
     }
-    MUTEX_UNLOCK();
+    FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
 }
 
 PyObject *
@@ -325,22 +318,22 @@ _PyPegen_get_memo_statistics(void)
         return NULL;
     }
 
-    MUTEX_LOCK();
+    FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
     for (int i = 0; i < NSTATISTICS; i++) {
         PyObject *value = PyLong_FromLong(memo_statistics[i]);
         if (value == NULL) {
-            MUTEX_UNLOCK();
+            FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
             Py_DECREF(ret);
             return NULL;
         }
         // PyList_SetItem borrows a reference to value.
         if (PyList_SetItem(ret, i, value) < 0) {
-            MUTEX_UNLOCK();
+            FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
             Py_DECREF(ret);
             return NULL;
         }
     }
-    MUTEX_UNLOCK();
+    FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
     return ret;
 }
 #endif
@@ -366,9 +359,9 @@ _PyPegen_is_memoized(Parser *p, int type, void *pres)
                 if (count <= 0) {
                     count = 1;
                 }
-                MUTEX_LOCK();
+                FT_MUTEX_LOCK(&_PyRuntime.parser.mutex);
                 memo_statistics[type] += count;
-                MUTEX_UNLOCK();
+                FT_MUTEX_UNLOCK(&_PyRuntime.parser.mutex);
             }
 #endif
             p->mark = m->mark;
diff --git a/Python/ceval_gil.c b/Python/ceval_gil.c
index aa68371ac8febf..6bf64868cbb2d3 100644
--- a/Python/ceval_gil.c
+++ b/Python/ceval_gil.c
@@ -907,13 +907,9 @@ unsignal_pending_calls(PyThreadState *tstate, 
PyInterpreterState *interp)
 static void
 clear_pending_handling_thread(struct _pending_calls *pending)
 {
-#ifdef Py_GIL_DISABLED
-    PyMutex_Lock(&pending->mutex);
-    pending->handling_thread = NULL;
-    PyMutex_Unlock(&pending->mutex);
-#else
+    FT_MUTEX_LOCK(&pending->mutex);
     pending->handling_thread = NULL;
-#endif
+    FT_MUTEX_UNLOCK(&pending->mutex);
 }
 
 static int
diff --git a/Python/codecs.c b/Python/codecs.c
index 4e9aecfe75c2c9..8eb9f2db41359e 100644
--- a/Python/codecs.c
+++ b/Python/codecs.c
@@ -16,7 +16,7 @@ Copyright (c) Corporation for National Research Initiatives.
 #include "pycore_runtime.h"       // _Py_ID()
 #include "pycore_ucnhash.h"       // _PyUnicode_Name_CAPI
 #include "pycore_unicodeobject.h" // _PyUnicode_InternMortal()
-
+#include "pycore_pyatomic_ft_wrappers.h"
 
 static const char *codecs_builtin_error_handlers[] = {
     "strict", "ignore", "replace",
@@ -40,13 +40,10 @@ int PyCodec_Register(PyObject *search_function)
         PyErr_SetString(PyExc_TypeError, "argument must be callable");
         goto onError;
     }
-#ifdef Py_GIL_DISABLED
-    PyMutex_Lock(&interp->codecs.search_path_mutex);
-#endif
+    FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
     int ret = PyList_Append(interp->codecs.search_path, search_function);
-#ifdef Py_GIL_DISABLED
-    PyMutex_Unlock(&interp->codecs.search_path_mutex);
-#endif
+    FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
+
     return ret;
 
  onError:
@@ -66,9 +63,7 @@ PyCodec_Unregister(PyObject *search_function)
     PyObject *codec_search_path = interp->codecs.search_path;
     assert(PyList_CheckExact(codec_search_path));
     for (Py_ssize_t i = 0; i < PyList_GET_SIZE(codec_search_path); i++) {
-#ifdef Py_GIL_DISABLED
-        PyMutex_Lock(&interp->codecs.search_path_mutex);
-#endif
+        FT_MUTEX_LOCK(&interp->codecs.search_path_mutex);
         PyObject *item = PyList_GetItemRef(codec_search_path, i);
         int ret = 1;
         if (item == search_function) {
@@ -76,9 +71,7 @@ PyCodec_Unregister(PyObject *search_function)
             // while we hold search_path_mutex.
             ret = PyList_SetSlice(codec_search_path, i, i+1, NULL);
         }
-#ifdef Py_GIL_DISABLED
-        PyMutex_Unlock(&interp->codecs.search_path_mutex);
-#endif
+        FT_MUTEX_UNLOCK(&interp->codecs.search_path_mutex);
         Py_DECREF(item);
         if (ret != 1) {
             assert(interp->codecs.search_cache != NULL);
diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c
index dbd19d7755c237..ee9b0cd0e9cfc3 100644
--- a/Python/legacy_tracing.c
+++ b/Python/legacy_tracing.c
@@ -20,13 +20,6 @@ typedef struct _PyLegacyEventHandler {
 
 #define _PyLegacyEventHandler_CAST(op)  ((_PyLegacyEventHandler *)(op))
 
-#ifdef Py_GIL_DISABLED
-#define LOCK_SETUP()    
PyMutex_Lock(&_PyRuntime.ceval.sys_trace_profile_mutex);
-#define UNLOCK_SETUP()  
PyMutex_Unlock(&_PyRuntime.ceval.sys_trace_profile_mutex);
-#else
-#define LOCK_SETUP()
-#define UNLOCK_SETUP()
-#endif
 /* The Py_tracefunc function expects the following arguments:
  *   obj: the trace object (PyObject *)
  *   frame: the current frame (PyFrameObject *)
@@ -509,9 +502,9 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc 
func, PyObject *arg)
 
     // needs to be decref'd outside of the lock
     PyObject *old_profileobj;
-    LOCK_SETUP();
+    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
     Py_ssize_t profiling_threads = setup_profile(tstate, func, arg, 
&old_profileobj);
-    UNLOCK_SETUP();
+    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
     Py_XDECREF(old_profileobj);
 
     uint32_t events = 0;
@@ -605,10 +598,10 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc 
func, PyObject *arg)
     }
     // needs to be decref'd outside of the lock
     PyObject *old_traceobj;
-    LOCK_SETUP();
+    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
     assert(tstate->interp->sys_tracing_threads >= 0);
     Py_ssize_t tracing_threads = setup_tracing(tstate, func, arg, 
&old_traceobj);
-    UNLOCK_SETUP();
+    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
     Py_XDECREF(old_traceobj);
     if (tracing_threads < 0) {
         return -1;
diff --git a/Python/pystate.c b/Python/pystate.c
index c77868bc717daf..cd62bf86837f83 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1689,9 +1689,7 @@ PyThreadState_Clear(PyThreadState *tstate)
           "PyThreadState_Clear: warning: thread still has a generator\n");
     }
 
-#ifdef Py_GIL_DISABLED
-    PyMutex_Lock(&_PyRuntime.ceval.sys_trace_profile_mutex);
-#endif
+    FT_MUTEX_LOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
 
     if (tstate->c_profilefunc != NULL) {
         tstate->interp->sys_profiling_threads--;
@@ -1702,9 +1700,7 @@ PyThreadState_Clear(PyThreadState *tstate)
         tstate->c_tracefunc = NULL;
     }
 
-#ifdef Py_GIL_DISABLED
-    PyMutex_Unlock(&_PyRuntime.ceval.sys_trace_profile_mutex);
-#endif
+    FT_MUTEX_UNLOCK(&_PyRuntime.ceval.sys_trace_profile_mutex);
 
     Py_CLEAR(tstate->c_profileobj);
     Py_CLEAR(tstate->c_traceobj);

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: arch...@mail-archive.com

Reply via email to