[3.14] gh-133296: Publicly expose critical section API that accepts PyMutex (gh-135899) (#136969)

Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
This commit is contained in:
Miss Islington (bot) 2025-07-22 11:44:59 +02:00 committed by GitHub
parent ddd3413687
commit 11f510167c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 96 additions and 15 deletions

View file

@ -2458,6 +2458,12 @@ is resumed, and its locks reacquired. This means the critical section API
provides weaker guarantees than traditional locks -- they are useful because
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
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
@ -2503,6 +2509,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.
.. 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:: 3.14
.. c:macro:: Py_END_CRITICAL_SECTION()
Ends the critical section and releases the per-object lock.
@ -2532,6 +2555,23 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`.
.. 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:: 3.14
.. c:macro:: Py_END_CRITICAL_SECTION2()
Ends the critical section and releases the per-object locks.

View file

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

View file

@ -21,16 +21,6 @@ extern "C" {
#define _Py_CRITICAL_SECTION_MASK 0x3
#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
// PySequence_Fast APIs without the GIL. For performance, the argument *to*
// PySequence_Fast() is provided to the macro, not the *result* of
@ -75,8 +65,6 @@ extern "C" {
#else /* !Py_GIL_DISABLED */
// 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_END_CRITICAL_SECTION_SEQUENCE_FAST() }
# define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex)
@ -119,6 +107,7 @@ _PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
_PyCriticalSection_BeginSlow(c, m);
}
}
#define PyCriticalSection_BeginMutex _PyCriticalSection_BeginMutex
static inline void
_PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
@ -194,6 +183,7 @@ _PyCriticalSection2_BeginMutex(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2)
_PyCriticalSection2_BeginSlow(c, m1, m2, 0);
}
}
#define PyCriticalSection2_BeginMutex _PyCriticalSection2_BeginMutex
static inline void
_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

@ -419,7 +419,7 @@ typedef struct {
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()
static inline uint8_t

View file

@ -2419,6 +2419,16 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
Py_BEGIN_CRITICAL_SECTION2(module, module);
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;
}

View file

@ -65,11 +65,11 @@ class object "PyObject *" "&PyBaseObject_Type"
// be released and reacquired during a subclass update if there's contention
// on the subclass lock.
#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 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()

View file

@ -130,6 +130,15 @@ PyCriticalSection_Begin(PyCriticalSection *c, PyObject *op)
#endif
}
#undef PyCriticalSection_BeginMutex
void
PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
{
#ifdef Py_GIL_DISABLED
_PyCriticalSection_BeginMutex(c, m);
#endif
}
#undef PyCriticalSection_End
void
PyCriticalSection_End(PyCriticalSection *c)
@ -148,6 +157,15 @@ PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
#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
void
PyCriticalSection2_End(PyCriticalSection2 *c)