]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-117511: Make PyMutex public in the non-limited API (#117731)
authorSam Gross <colesbury@gmail.com>
Thu, 20 Jun 2024 15:29:08 +0000 (11:29 -0400)
committerGitHub <noreply@github.com>
Thu, 20 Jun 2024 15:29:08 +0000 (11:29 -0400)
18 files changed:
Doc/c-api/init.rst
Doc/whatsnew/3.13.rst
Include/Python.h
Include/cpython/lock.h [new file with mode: 0644]
Include/cpython/weakrefobject.h
Include/internal/pycore_critical_section.h
Include/internal/pycore_lock.h
Include/internal/pycore_warnings.h
Include/lock.h [new file with mode: 0644]
Include/object.h
Makefile.pre.in
Misc/NEWS.d/next/C API/2024-04-10-16-48-04.gh-issue-117511.RZtBRK.rst [new file with mode: 0644]
Modules/_testinternalcapi/test_lock.c
Objects/object.c
PCbuild/pythoncore.vcxproj
PCbuild/pythoncore.vcxproj.filters
Python/critical_section.c
Python/lock.c

index 9e118d4f36145f80cdc616d913ece24f2f5b6999..58c79031de5320784b785d4f0449382083ec2f00 100644 (file)
@@ -55,6 +55,11 @@ The following functions can be safely called before Python is initialized:
   * :c:func:`PyMem_RawCalloc`
   * :c:func:`PyMem_RawFree`
 
+* Synchronization:
+
+  * :c:func:`PyMutex_Lock`
+  * :c:func:`PyMutex_Unlock`
+
 .. note::
 
    The following functions **should not be called** before
@@ -2152,3 +2157,41 @@ be used in new code.
 .. c:function:: void PyThread_delete_key_value(int key)
 .. c:function:: void PyThread_ReInitTLS()
 
+Synchronization Primitives
+==========================
+
+The C-API provides a basic mutual exclusion lock.
+
+.. c:type:: PyMutex
+
+   A mutual exclusion lock.  The :c:type:`!PyMutex` should be initialized to
+   zero to represent the unlocked state.  For example::
+
+      PyMutex mutex = {0};
+
+   Instances of :c:type:`!PyMutex` should not be copied or moved.  Both the
+   contents and address of a :c:type:`!PyMutex` are meaningful, and it must
+   remain at a fixed, writable location in memory.
+
+   .. note::
+
+      A :c:type:`!PyMutex` currently occupies one byte, but the size should be
+      considered unstable.  The size may change in future Python releases
+      without a deprecation period.
+
+   .. versionadded:: 3.13
+
+.. c:function:: void PyMutex_Lock(PyMutex *m)
+
+   Lock mutex *m*.  If another thread has already locked it, the calling
+   thread will block until the mutex is unlocked.  While blocked, the thread
+   will temporarily release the :term:`GIL` if it is held.
+
+   .. versionadded:: 3.13
+
+.. c:function:: void PyMutex_Unlock(PyMutex *m)
+
+   Unlock mutex *m*. The mutex must be locked --- otherwise, the function will
+   issue a fatal error.
+
+   .. versionadded:: 3.13
index 81daaabdb889d7b29e180eead896cb3e3bee80b0..02c2a5ce02b730b40934aa18839f5bf0644e3f7d 100644 (file)
@@ -2172,6 +2172,11 @@ New Features
   :c:func:`PyEval_GetLocals` return :term:`strong references <strong reference>`
   rather than borrowed references. (Added as part of :pep:`667`.)
 
+* Add :c:type:`PyMutex` API, a lightweight mutex that occupies a single byte.
+  The :c:func:`PyMutex_Lock` function will release the GIL (if currently held)
+  if the operation needs to block.
+  (Contributed by Sam Gross in :gh:`108724`.)
+
 Build Changes
 =============
 
index a1b33f6d3c42b21c0fb3708e6e80200971cafcd6..5cc4fb5bb9d37207c30b565828902b07bdf26fed 100644 (file)
@@ -64,6 +64,7 @@
 #include "pybuffer.h"
 #include "pystats.h"
 #include "pyatomic.h"
+#include "lock.h"
 #include "object.h"
 #include "refcount.h"
 #include "objimpl.h"
diff --git a/Include/cpython/lock.h b/Include/cpython/lock.h
new file mode 100644 (file)
index 0000000..8ee03e8
--- /dev/null
@@ -0,0 +1,63 @@
+#ifndef Py_CPYTHON_LOCK_H
+#  error "this header file must not be included directly"
+#endif
+
+#define _Py_UNLOCKED    0
+#define _Py_LOCKED      1
+
+// A mutex that occupies one byte. The lock can be zero initialized to
+// represent the unlocked state.
+//
+// Typical initialization:
+//   PyMutex m = (PyMutex){0};
+//
+// Or initialize as global variables:
+//   static PyMutex m;
+//
+// Typical usage:
+//   PyMutex_Lock(&m);
+//   ...
+//   PyMutex_Unlock(&m);
+//
+// The contents of the PyMutex are not part of the public API, but are
+// described to aid in understanding the implementation and debugging. Only
+// the two least significant bits are used. The remaining bits are always zero:
+// 0b00: unlocked
+// 0b01: locked
+// 0b10: unlocked and has parked threads
+// 0b11: locked and has parked threads
+typedef struct PyMutex {
+    uint8_t _bits;  // (private)
+} PyMutex;
+
+// exported function for locking the mutex
+PyAPI_FUNC(void) PyMutex_Lock(PyMutex *m);
+
+// exported function for unlocking the mutex
+PyAPI_FUNC(void) PyMutex_Unlock(PyMutex *m);
+
+// Locks the mutex.
+//
+// If the mutex is currently locked, the calling thread will be parked until
+// the mutex is unlocked. If the current thread holds the GIL, then the GIL
+// will be released while the thread is parked.
+static inline void
+_PyMutex_Lock(PyMutex *m)
+{
+    uint8_t expected = _Py_UNLOCKED;
+    if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_LOCKED)) {
+        PyMutex_Lock(m);
+    }
+}
+#define PyMutex_Lock _PyMutex_Lock
+
+// Unlocks the mutex.
+static inline void
+_PyMutex_Unlock(PyMutex *m)
+{
+    uint8_t expected = _Py_LOCKED;
+    if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_UNLOCKED)) {
+        PyMutex_Unlock(m);
+    }
+}
+#define PyMutex_Unlock _PyMutex_Unlock
index dcca166d7357cc72383d3074f4fc8998479ae1a6..28acf7265a08563af3de1deaf71cea08a6ec08d7 100644 (file)
@@ -36,7 +36,7 @@ struct _PyWeakReference {
      * Normally this can be derived from wr_object, but in some cases we need
      * to lock after wr_object has been set to Py_None.
      */
-    struct _PyMutex *weakrefs_lock;
+    PyMutex *weakrefs_lock;
 #endif
 };
 
index 7bebcdb67c71693ff254d91868569361b82020de..3e15c3aabffa974ccc65346b39c161920241f4c9 100644 (file)
@@ -202,7 +202,7 @@ _PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2,
 static inline void
 _PyCriticalSection_Begin(_PyCriticalSection *c, PyMutex *m)
 {
-    if (PyMutex_LockFast(&m->v)) {
+    if (PyMutex_LockFast(&m->_bits)) {
         PyThreadState *tstate = _PyThreadState_GET();
         c->mutex = m;
         c->prev = tstate->critical_section;
@@ -255,8 +255,8 @@ _PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2)
         m2 = tmp;
     }
 
-    if (PyMutex_LockFast(&m1->v)) {
-        if (PyMutex_LockFast(&m2->v)) {
+    if (PyMutex_LockFast(&m1->_bits)) {
+        if (PyMutex_LockFast(&m2->_bits)) {
             PyThreadState *tstate = _PyThreadState_GET();
             c->base.mutex = m1;
             c->mutex2 = m2;
index 882c4888e5058c8df2f84c0fe2db0d539cedaaad..8aa73946e2c6451aa162dcfb39a054e0c1c9a1c6 100644 (file)
@@ -13,48 +13,10 @@ extern "C" {
 #  error "this header requires Py_BUILD_CORE define"
 #endif
 
-
-// A mutex that occupies one byte. The lock can be zero initialized.
-//
-// Only the two least significant bits are used. The remaining bits should be
-// zero:
-// 0b00: unlocked
-// 0b01: locked
-// 0b10: unlocked and has parked threads
-// 0b11: locked and has parked threads
-//
-// Typical initialization:
-//   PyMutex m = (PyMutex){0};
-//
-// Or initialize as global variables:
-//   static PyMutex m;
-//
-// Typical usage:
-//   PyMutex_Lock(&m);
-//   ...
-//   PyMutex_Unlock(&m);
-
-// NOTE: In Py_GIL_DISABLED builds, `struct _PyMutex` is defined in Include/object.h.
-// The Py_GIL_DISABLED builds need the definition in Include/object.h for the
-// `ob_mutex` field in PyObject. For the default (non-free-threaded) build,
-// we define the struct here to avoid exposing it in the public API.
-#ifndef Py_GIL_DISABLED
-struct _PyMutex { uint8_t v; };
-#endif
-
-typedef struct _PyMutex PyMutex;
-
-#define _Py_UNLOCKED    0
-#define _Py_LOCKED      1
+//_Py_UNLOCKED is defined as 0 and _Py_LOCKED as 1 in Include/cpython/lock.h
 #define _Py_HAS_PARKED  2
 #define _Py_ONCE_INITIALIZED 4
 
-// (private) slow path for locking the mutex
-PyAPI_FUNC(void) _PyMutex_LockSlow(PyMutex *m);
-
-// (private) slow path for unlocking the mutex
-PyAPI_FUNC(void) _PyMutex_UnlockSlow(PyMutex *m);
-
 static inline int
 PyMutex_LockFast(uint8_t *lock_bits)
 {
@@ -62,35 +24,11 @@ PyMutex_LockFast(uint8_t *lock_bits)
     return _Py_atomic_compare_exchange_uint8(lock_bits, &expected, _Py_LOCKED);
 }
 
-// Locks the mutex.
-//
-// If the mutex is currently locked, the calling thread will be parked until
-// the mutex is unlocked. If the current thread holds the GIL, then the GIL
-// will be released while the thread is parked.
-static inline void
-PyMutex_Lock(PyMutex *m)
-{
-    uint8_t expected = _Py_UNLOCKED;
-    if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_LOCKED)) {
-        _PyMutex_LockSlow(m);
-    }
-}
-
-// Unlocks the mutex.
-static inline void
-PyMutex_Unlock(PyMutex *m)
-{
-    uint8_t expected = _Py_LOCKED;
-    if (!_Py_atomic_compare_exchange_uint8(&m->v, &expected, _Py_UNLOCKED)) {
-        _PyMutex_UnlockSlow(m);
-    }
-}
-
 // Checks if the mutex is currently locked.
 static inline int
 PyMutex_IsLocked(PyMutex *m)
 {
-    return (_Py_atomic_load_uint8(&m->v) & _Py_LOCKED) != 0;
+    return (_Py_atomic_load_uint8(&m->_bits) & _Py_LOCKED) != 0;
 }
 
 // Re-initializes the mutex after a fork to the unlocked state.
@@ -121,7 +59,7 @@ 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)) {
+    if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &expected, _Py_LOCKED)) {
         _PyMutex_LockTimed(m, -1, flags);
     }
 }
index 114796df42b2b6af8f8a48d0922a436b77cc8105..f9f6559312f4ef670c16a16092c788a0ef428d79 100644 (file)
@@ -14,7 +14,7 @@ struct _warnings_runtime_state {
     PyObject *filters;  /* List */
     PyObject *once_registry;  /* Dict */
     PyObject *default_action; /* String */
-    struct _PyMutex mutex;
+    PyMutex mutex;
     long filters_version;
 };
 
diff --git a/Include/lock.h b/Include/lock.h
new file mode 100644 (file)
index 0000000..782b9db
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef Py_LOCK_H
+#define Py_LOCK_H
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef Py_LIMITED_API
+#  define Py_CPYTHON_LOCK_H
+#  include "cpython/lock.h"
+#  undef Py_CPYTHON_LOCK_H
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* !Py_LOCK_H */
index ed39e7dc877810f313968995d60f6bb10ae579d5..a1e5b33b0fdaaef262a9d4349dabfc07c779741d 100644 (file)
@@ -137,17 +137,13 @@ struct _object {
 // fields have been merged.
 #define _Py_UNOWNED_TID             0
 
-// NOTE: In non-free-threaded builds, `struct _PyMutex` is defined in
-// pycore_lock.h. See pycore_lock.h for more details.
-struct _PyMutex { uint8_t v; };
-
 struct _object {
     // ob_tid stores the thread id (or zero). It is also used by the GC and the
     // trashcan mechanism as a linked list pointer and by the GC to store the
     // computed "gc_refs" refcount.
     uintptr_t ob_tid;
     uint16_t _padding;
-    struct _PyMutex ob_mutex;   // per-object lock
+    PyMutex ob_mutex;           // per-object lock
     uint8_t ob_gc_bits;         // gc-related state
     uint32_t ob_ref_local;      // local reference count
     Py_ssize_t ob_ref_shared;   // shared (atomic) reference count
index 22dba279faa93581d43f6c3e0fa66e29bfc4f60c..e140018407a19b690d2f6c10851a6732f6d2db8e 100644 (file)
@@ -1019,6 +1019,7 @@ PYTHON_HEADERS= \
                $(srcdir)/Include/intrcheck.h \
                $(srcdir)/Include/iterobject.h \
                $(srcdir)/Include/listobject.h \
+               $(srcdir)/Include/lock.h \
                $(srcdir)/Include/longobject.h \
                $(srcdir)/Include/marshal.h \
                $(srcdir)/Include/memoryobject.h \
@@ -1092,6 +1093,7 @@ PYTHON_HEADERS= \
                $(srcdir)/Include/cpython/import.h \
                $(srcdir)/Include/cpython/initconfig.h \
                $(srcdir)/Include/cpython/listobject.h \
+               $(srcdir)/Include/cpython/lock.h \
                $(srcdir)/Include/cpython/longintrepr.h \
                $(srcdir)/Include/cpython/longobject.h \
                $(srcdir)/Include/cpython/memoryobject.h \
diff --git a/Misc/NEWS.d/next/C API/2024-04-10-16-48-04.gh-issue-117511.RZtBRK.rst b/Misc/NEWS.d/next/C API/2024-04-10-16-48-04.gh-issue-117511.RZtBRK.rst
new file mode 100644 (file)
index 0000000..586685a
--- /dev/null
@@ -0,0 +1 @@
+Make the :c:type:`PyMutex` public in the non-limited C API.
index 1544fe1363c7c5b84e8d1a26da07b923049fad15..8d678412fe717917f82ab8ab51b6f353d3facbbd 100644 (file)
@@ -36,9 +36,9 @@ test_lock_basic(PyObject *self, PyObject *obj)
 
     // uncontended lock and unlock
     PyMutex_Lock(&m);
-    assert(m.v == 1);
+    assert(m._bits == 1);
     PyMutex_Unlock(&m);
-    assert(m.v == 0);
+    assert(m._bits == 0);
 
     Py_RETURN_NONE;
 }
@@ -57,10 +57,10 @@ lock_thread(void *arg)
     _Py_atomic_store_int(&test_data->started, 1);
 
     PyMutex_Lock(m);
-    assert(m->v == 1);
+    assert(m->_bits == 1);
 
     PyMutex_Unlock(m);
-    assert(m->v == 0);
+    assert(m->_bits == 0);
 
     _PyEvent_Notify(&test_data->done);
 }
@@ -73,7 +73,7 @@ test_lock_two_threads(PyObject *self, PyObject *obj)
     memset(&test_data, 0, sizeof(test_data));
 
     PyMutex_Lock(&test_data.m);
-    assert(test_data.m.v == 1);
+    assert(test_data.m._bits == 1);
 
     PyThread_start_new_thread(lock_thread, &test_data);
 
@@ -82,17 +82,17 @@ test_lock_two_threads(PyObject *self, PyObject *obj)
     uint8_t v;
     do {
         pysleep(10);  // allow some time for the other thread to try to lock
-        v = _Py_atomic_load_uint8_relaxed(&test_data.m.v);
+        v = _Py_atomic_load_uint8_relaxed(&test_data.m._bits);
         assert(v == 1 || v == 3);
         iters++;
     } while (v != 3 && iters < 200);
 
     // both the "locked" and the "has parked" bits should be set
-    assert(test_data.m.v == 3);
+    assert(test_data.m._bits == 3);
 
     PyMutex_Unlock(&test_data.m);
     PyEvent_Wait(&test_data.done);
-    assert(test_data.m.v == 0);
+    assert(test_data.m._bits == 0);
 
     Py_RETURN_NONE;
 }
index 16f940f46f137e6ffc872a3459954e00749a6a31..dc6402f0a99cd05303f80259a6f7368ddf0aecbc 100644 (file)
@@ -2372,7 +2372,7 @@ new_reference(PyObject *op)
 #else
     op->ob_tid = _Py_ThreadId();
     op->_padding = 0;
-    op->ob_mutex = (struct _PyMutex){ 0 };
+    op->ob_mutex = (PyMutex){ 0 };
     op->ob_gc_bits = 0;
     op->ob_ref_local = 1;
     op->ob_ref_shared = 0;
index 96960f0579a936898425342da6a48857f7d820e2..bfc2baf74c954c53655ca3b8ab7f216351f01cfe 100644 (file)
     <ClInclude Include="..\Include\cpython\import.h" />
     <ClInclude Include="..\Include\cpython\initconfig.h" />
     <ClInclude Include="..\Include\cpython\listobject.h" />
+    <ClInclude Include="..\Include\cpython\lock.h" />
     <ClInclude Include="..\Include\cpython\longintrepr.h" />
     <ClInclude Include="..\Include\cpython\longobject.h" />
     <ClInclude Include="..\Include\cpython\memoryobject.h" />
     <ClInclude Include="..\Include\intrcheck.h" />
     <ClInclude Include="..\Include\iterobject.h" />
     <ClInclude Include="..\Include\listobject.h" />
+    <ClInclude Include="..\Include\lock.h" />
     <ClInclude Include="..\Include\longobject.h" />
     <ClInclude Include="..\Include\marshal.h" />
     <ClInclude Include="..\Include\memoryobject.h" />
index 2e4bd786be47e910481cb8acbd51b90b8fb338a8..2686b92c6375cf647baebd3c8c7fef6a1a3b224c 100644 (file)
     <ClInclude Include="..\Include\listobject.h">
       <Filter>Include</Filter>
     </ClInclude>
+    <ClInclude Include="..\Include\lock.h">
+      <Filter>Include</Filter>
+    </ClInclude>
     <ClInclude Include="..\Include\longobject.h">
       <Filter>Include</Filter>
     </ClInclude>
     <ClInclude Include="..\Include\cpython\listobject.h">
       <Filter>Include\cpython</Filter>
     </ClInclude>
+    <ClInclude Include="..\Include\cpython\lock.h">
+      <Filter>Include</Filter>
+    </ClInclude>
     <ClInclude Include="..\Include\cpython\longintrepr.h">
       <Filter>Include</Filter>
     </ClInclude>
index 2214d80eeb297b86f6d77fb9925f59d8537b874e..ac679ac3f8a01971f204f5647752e2d633410f8d 100644 (file)
@@ -14,7 +14,7 @@ _PyCriticalSection_BeginSlow(_PyCriticalSection *c, PyMutex *m)
     c->prev = (uintptr_t)tstate->critical_section;
     tstate->critical_section = (uintptr_t)c;
 
-    _PyMutex_LockSlow(m);
+    PyMutex_Lock(m);
     c->mutex = m;
 }
 
index 555f4c25b9b21421f47e55aa6e93cd86bd6e8073..7c6a5175e88ff1108b6377b3d5a42727e390dce3 100644 (file)
@@ -47,18 +47,12 @@ _Py_yield(void)
 #endif
 }
 
-void
-_PyMutex_LockSlow(PyMutex *m)
-{
-    _PyMutex_LockTimed(m, -1, _PY_LOCK_DETACH);
-}
-
 PyLockStatus
 _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
 {
-    uint8_t v = _Py_atomic_load_uint8_relaxed(&m->v);
+    uint8_t v = _Py_atomic_load_uint8_relaxed(&m->_bits);
     if ((v & _Py_LOCKED) == 0) {
-        if (_Py_atomic_compare_exchange_uint8(&m->v, &v, v|_Py_LOCKED)) {
+        if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
             return PY_LOCK_ACQUIRED;
         }
     }
@@ -83,7 +77,7 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
     for (;;) {
         if ((v & _Py_LOCKED) == 0) {
             // The lock is unlocked. Try to grab it.
-            if (_Py_atomic_compare_exchange_uint8(&m->v, &v, v|_Py_LOCKED)) {
+            if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, v|_Py_LOCKED)) {
                 return PY_LOCK_ACQUIRED;
             }
             continue;
@@ -104,17 +98,17 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
         if (!(v & _Py_HAS_PARKED)) {
             // We are the first waiter. Set the _Py_HAS_PARKED flag.
             newv = v | _Py_HAS_PARKED;
-            if (!_Py_atomic_compare_exchange_uint8(&m->v, &v, newv)) {
+            if (!_Py_atomic_compare_exchange_uint8(&m->_bits, &v, newv)) {
                 continue;
             }
         }
 
-        int ret = _PyParkingLot_Park(&m->v, &newv, sizeof(newv), timeout,
+        int ret = _PyParkingLot_Park(&m->_bits, &newv, sizeof(newv), timeout,
                                      &entry, (flags & _PY_LOCK_DETACH) != 0);
         if (ret == Py_PARK_OK) {
             if (entry.handed_off) {
                 // We own the lock now.
-                assert(_Py_atomic_load_uint8_relaxed(&m->v) & _Py_LOCKED);
+                assert(_Py_atomic_load_uint8_relaxed(&m->_bits) & _Py_LOCKED);
                 return PY_LOCK_ACQUIRED;
             }
         }
@@ -136,7 +130,7 @@ _PyMutex_LockTimed(PyMutex *m, PyTime_t timeout, _PyLockFlags flags)
             }
         }
 
-        v = _Py_atomic_load_uint8_relaxed(&m->v);
+        v = _Py_atomic_load_uint8_relaxed(&m->_bits);
     }
 }
 
@@ -158,13 +152,13 @@ mutex_unpark(PyMutex *m, struct mutex_entry *entry, int has_more_waiters)
             v |= _Py_HAS_PARKED;
         }
     }
-    _Py_atomic_store_uint8(&m->v, v);
+    _Py_atomic_store_uint8(&m->_bits, v);
 }
 
 int
 _PyMutex_TryUnlock(PyMutex *m)
 {
-    uint8_t v = _Py_atomic_load_uint8(&m->v);
+    uint8_t v = _Py_atomic_load_uint8(&m->_bits);
     for (;;) {
         if ((v & _Py_LOCKED) == 0) {
             // error: the mutex is not locked
@@ -172,24 +166,16 @@ _PyMutex_TryUnlock(PyMutex *m)
         }
         else if ((v & _Py_HAS_PARKED)) {
             // wake up a single thread
-            _PyParkingLot_Unpark(&m->v, (_Py_unpark_fn_t *)mutex_unpark, m);
+            _PyParkingLot_Unpark(&m->_bits, (_Py_unpark_fn_t *)mutex_unpark, m);
             return 0;
         }
-        else if (_Py_atomic_compare_exchange_uint8(&m->v, &v, _Py_UNLOCKED)) {
+        else if (_Py_atomic_compare_exchange_uint8(&m->_bits, &v, _Py_UNLOCKED)) {
             // fast-path: no waiters
             return 0;
         }
     }
 }
 
-void
-_PyMutex_UnlockSlow(PyMutex *m)
-{
-    if (_PyMutex_TryUnlock(m) < 0) {
-        Py_FatalError("unlocking mutex that is not locked");
-    }
-}
-
 // _PyRawMutex stores a linked list of `struct raw_mutex_entry`, one for each
 // thread waiting on the mutex, directly in the mutex itself.
 struct raw_mutex_entry {
@@ -584,3 +570,19 @@ uint32_t _PySeqLock_AfterFork(_PySeqLock *seqlock)
 
      return 0;
 }
+
+#undef PyMutex_Lock
+void
+PyMutex_Lock(PyMutex *m)
+{
+    _PyMutex_LockTimed(m, -1, _PY_LOCK_DETACH);
+}
+
+#undef PyMutex_Unlock
+void
+PyMutex_Unlock(PyMutex *m)
+{
+    if (_PyMutex_TryUnlock(m) < 0) {
+        Py_FatalError("unlocking mutex that is not locked");
+    }
+}