gh-133296: Publicly expose critical section API that accepts PyMutex (gh-135899)

This makes the following APIs public:

* `Py_BEGIN_CRITICAL_SECTION_MUTEX(mutex),`
* `Py_BEGIN_CRITICAL_SECTION2_MUTEX(mutex1, mutex2)`
* `void PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *mutex)`
* `void PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *mutex1, PyMutex *mutex2)`

The macros are identical to the corresponding `Py_BEGIN_CRITICAL_SECTION` and
`Py_BEGIN_CRITICAL_SECTION2` macros (e.g., they include braces), but they
accept a `PyMutex` instead of an object.

The new macros are still paired with the existing END macros
(`Py_END_CRITICAL_SECTION`, `Py_END_CRITICAL_SECTION2`).
This commit is contained in:
Nathan Goldbaum 2025-07-21 15:25:43 -06:00 committed by GitHub
parent f183996eb7
commit 89c220b93c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 96 additions and 15 deletions

View file

@ -2306,6 +2306,12 @@ is resumed, and its locks reacquired. This means the critical section API
provides weaker guarantees than traditional locks -- they are useful because provides weaker guarantees than traditional locks -- they are useful because
their behavior is similar to the :term:`GIL`. their behavior is similar to the :term:`GIL`.
Variants that accept :c:type:`PyMutex` pointers rather than Python objects are also
available. Use these variants to start a critical section in a situation where
there is no :c:type:`PyObject` -- for example, when working with a C type that
does not extend or wrap :c:type:`PyObject` but still needs to call into the C
API in a manner that might lead to deadlocks.
The functions and structs used by the macros are exposed for cases The functions and structs used by the macros are exposed for cases
where C macros are not available. They should only be used as in the where C macros are not available. They should only be used as in the
given macro expansions. Note that the sizes and contents of the structures may given macro expansions. Note that the sizes and contents of the structures may
@ -2351,6 +2357,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.
.. versionadded:: 3.13 .. versionadded:: 3.13
.. c:macro:: Py_BEGIN_CRITICAL_SECTION_MUTEX(m)
Locks the mutex *m* and begins a critical section.
In the free-threaded build, this macro expands to::
{
PyCriticalSection _py_cs;
PyCriticalSection_BeginMutex(&_py_cs, m)
Note that unlike :c:macro:`Py_BEGIN_CRITICAL_SECTION`, there is no cast for
the argument of the macro - it must be a :c:type:`PyMutex` pointer.
On the default build, this macro expands to ``{``.
.. versionadded:: next
.. c:macro:: Py_END_CRITICAL_SECTION() .. c:macro:: Py_END_CRITICAL_SECTION()
Ends the critical section and releases the per-object lock. Ends the critical section and releases the per-object lock.
@ -2380,6 +2403,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.
.. versionadded:: 3.13 .. versionadded:: 3.13
.. c:macro:: Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2)
Locks the mutexes *m1* and *m2* and begins a critical section.
In the free-threaded build, this macro expands to::
{
PyCriticalSection2 _py_cs2;
PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)
Note that unlike :c:macro:`Py_BEGIN_CRITICAL_SECTION2`, there is no cast for
the arguments of the macro - they must be :c:type:`PyMutex` pointers.
On the default build, this macro expands to ``{``.
.. versionadded:: next
.. c:macro:: Py_END_CRITICAL_SECTION2() .. c:macro:: Py_END_CRITICAL_SECTION2()
Ends the critical section and releases the per-object locks. Ends the critical section and releases the per-object locks.

View file

@ -73,22 +73,32 @@ typedef struct PyCriticalSection2 PyCriticalSection2;
PyAPI_FUNC(void) PyAPI_FUNC(void)
PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op); PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op);
PyAPI_FUNC(void)
PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m);
PyAPI_FUNC(void) PyAPI_FUNC(void)
PyCriticalSection_End(PyCriticalSection *c); PyCriticalSection_End(PyCriticalSection *c);
PyAPI_FUNC(void) PyAPI_FUNC(void)
PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b); PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b);
PyAPI_FUNC(void)
PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2);
PyAPI_FUNC(void) PyAPI_FUNC(void)
PyCriticalSection2_End(PyCriticalSection2 *c); PyCriticalSection2_End(PyCriticalSection2 *c);
#ifndef Py_GIL_DISABLED #ifndef Py_GIL_DISABLED
# define Py_BEGIN_CRITICAL_SECTION(op) \ # define Py_BEGIN_CRITICAL_SECTION(op) \
{ {
# define Py_BEGIN_CRITICAL_SECTION_MUTEX(mutex) \
{
# define Py_END_CRITICAL_SECTION() \ # define Py_END_CRITICAL_SECTION() \
} }
# define Py_BEGIN_CRITICAL_SECTION2(a, b) \ # define Py_BEGIN_CRITICAL_SECTION2(a, b) \
{ {
# define Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2) \
{
# define Py_END_CRITICAL_SECTION2() \ # define Py_END_CRITICAL_SECTION2() \
} }
#else /* !Py_GIL_DISABLED */ #else /* !Py_GIL_DISABLED */
@ -118,6 +128,11 @@ struct PyCriticalSection2 {
PyCriticalSection _py_cs; \ PyCriticalSection _py_cs; \
PyCriticalSection_Begin(&_py_cs, _PyObject_CAST(op)) PyCriticalSection_Begin(&_py_cs, _PyObject_CAST(op))
# define Py_BEGIN_CRITICAL_SECTION_MUTEX(mutex) \
{ \
PyCriticalSection _py_cs; \
PyCriticalSection_BeginMutex(&_py_cs, mutex)
# define Py_END_CRITICAL_SECTION() \ # define Py_END_CRITICAL_SECTION() \
PyCriticalSection_End(&_py_cs); \ PyCriticalSection_End(&_py_cs); \
} }
@ -127,6 +142,11 @@ struct PyCriticalSection2 {
PyCriticalSection2 _py_cs2; \ PyCriticalSection2 _py_cs2; \
PyCriticalSection2_Begin(&_py_cs2, _PyObject_CAST(a), _PyObject_CAST(b)) PyCriticalSection2_Begin(&_py_cs2, _PyObject_CAST(a), _PyObject_CAST(b))
# define Py_BEGIN_CRITICAL_SECTION2_MUTEX(m1, m2) \
{ \
PyCriticalSection2 _py_cs2; \
PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)
# define Py_END_CRITICAL_SECTION2() \ # define Py_END_CRITICAL_SECTION2() \
PyCriticalSection2_End(&_py_cs2); \ PyCriticalSection2_End(&_py_cs2); \
} }

View file

@ -21,16 +21,6 @@ extern "C" {
#define _Py_CRITICAL_SECTION_MASK 0x3 #define _Py_CRITICAL_SECTION_MASK 0x3
#ifdef Py_GIL_DISABLED #ifdef Py_GIL_DISABLED
# define Py_BEGIN_CRITICAL_SECTION_MUT(mutex) \
{ \
PyCriticalSection _py_cs; \
_PyCriticalSection_BeginMutex(&_py_cs, mutex)
# define Py_BEGIN_CRITICAL_SECTION2_MUT(m1, m2) \
{ \
PyCriticalSection2 _py_cs2; \
_PyCriticalSection2_BeginMutex(&_py_cs2, m1, m2)
// Specialized version of critical section locking to safely use // Specialized version of critical section locking to safely use
// PySequence_Fast APIs without the GIL. For performance, the argument *to* // PySequence_Fast APIs without the GIL. For performance, the argument *to*
// PySequence_Fast() is provided to the macro, not the *result* of // PySequence_Fast() is provided to the macro, not the *result* of
@ -75,8 +65,6 @@ extern "C" {
#else /* !Py_GIL_DISABLED */ #else /* !Py_GIL_DISABLED */
// The critical section APIs are no-ops with the GIL. // The critical section APIs are no-ops with the GIL.
# define Py_BEGIN_CRITICAL_SECTION_MUT(mut) {
# define Py_BEGIN_CRITICAL_SECTION2_MUT(m1, m2) {
# define Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(original) { # define Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(original) {
# define Py_END_CRITICAL_SECTION_SEQUENCE_FAST() } # define Py_END_CRITICAL_SECTION_SEQUENCE_FAST() }
# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) # define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex)
@ -119,6 +107,7 @@ _PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
_PyCriticalSection_BeginSlow(c, m); _PyCriticalSection_BeginSlow(c, m);
} }
} }
#define PyCriticalSection_BeginMutex _PyCriticalSection_BeginMutex
static inline void static inline void
_PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op) _PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
@ -194,6 +183,7 @@ _PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2)
_PyCriticalSection2_BeginSlow(c, m1, m2, 0); _PyCriticalSection2_BeginSlow(c, m1, m2, 0);
} }
} }
#define PyCriticalSection2_BeginMutex _PyCriticalSection2_BeginMutex
static inline void static inline void
_PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b) _PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)

View file

@ -0,0 +1,3 @@
New variants for the critical section API that accept one or two
:c:type:`PyMutex` pointers rather than :c:type:`PyObject` instances are now
public in the non-limited C API.

View file

@ -431,7 +431,7 @@ typedef struct {
visible to other threads before the `dict_final` bit is set. visible to other threads before the `dict_final` bit is set.
*/ */
#define STGINFO_LOCK(stginfo) Py_BEGIN_CRITICAL_SECTION_MUT(&(stginfo)->mutex) #define STGINFO_LOCK(stginfo) Py_BEGIN_CRITICAL_SECTION_MUTEX(&(stginfo)->mutex)
#define STGINFO_UNLOCK() Py_END_CRITICAL_SECTION() #define STGINFO_UNLOCK() Py_END_CRITICAL_SECTION()
static inline uint8_t static inline uint8_t

View file

@ -2418,6 +2418,16 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
Py_BEGIN_CRITICAL_SECTION2(module, module); Py_BEGIN_CRITICAL_SECTION2(module, module);
Py_END_CRITICAL_SECTION2(); Py_END_CRITICAL_SECTION2();
#ifdef Py_GIL_DISABLED
// avoid unused variable compiler warning on GIL-enabled build
PyMutex mut = {0};
Py_BEGIN_CRITICAL_SECTION_MUTEX(&mut);
Py_END_CRITICAL_SECTION();
Py_BEGIN_CRITICAL_SECTION2_MUTEX(&mut, &mut);
Py_END_CRITICAL_SECTION2();
#endif
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View file

@ -73,11 +73,11 @@ class object "PyObject *" "&PyBaseObject_Type"
// while the stop-the-world mechanism is active. The slots and flags are read // while the stop-the-world mechanism is active. The slots and flags are read
// in many places without holding a lock and without atomics. // in many places without holding a lock and without atomics.
#define TYPE_LOCK &PyInterpreterState_Get()->types.mutex #define TYPE_LOCK &PyInterpreterState_Get()->types.mutex
#define BEGIN_TYPE_LOCK() Py_BEGIN_CRITICAL_SECTION_MUT(TYPE_LOCK) #define BEGIN_TYPE_LOCK() Py_BEGIN_CRITICAL_SECTION_MUTEX(TYPE_LOCK)
#define END_TYPE_LOCK() Py_END_CRITICAL_SECTION() #define END_TYPE_LOCK() Py_END_CRITICAL_SECTION()
#define BEGIN_TYPE_DICT_LOCK(d) \ #define BEGIN_TYPE_DICT_LOCK(d) \
Py_BEGIN_CRITICAL_SECTION2_MUT(TYPE_LOCK, &_PyObject_CAST(d)->ob_mutex) Py_BEGIN_CRITICAL_SECTION2_MUTEX(TYPE_LOCK, &_PyObject_CAST(d)->ob_mutex)
#define END_TYPE_DICT_LOCK() Py_END_CRITICAL_SECTION2() #define END_TYPE_DICT_LOCK() Py_END_CRITICAL_SECTION2()

View file

@ -130,6 +130,15 @@ PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
#endif #endif
} }
#undef PyCriticalSection_BeginMutex
void
PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
{
#ifdef Py_GIL_DISABLED
_PyCriticalSection_BeginMutex(c, m);
#endif
}
#undef PyCriticalSection_End #undef PyCriticalSection_End
void void
PyCriticalSection_End(PyCriticalSection *c) PyCriticalSection_End(PyCriticalSection *c)
@ -148,6 +157,15 @@ PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
#endif #endif
} }
#undef PyCriticalSection2_BeginMutex
void
PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2)
{
#ifdef Py_GIL_DISABLED
_PyCriticalSection2_BeginMutex(c, m1, m2);
#endif
}
#undef PyCriticalSection2_End #undef PyCriticalSection2_End
void void
PyCriticalSection2_End(PyCriticalSection2 *c) PyCriticalSection2_End(PyCriticalSection2 *c)