8000 gh-133296: Publicly expose critical section API that accepts PyMutex by ngoldbaum · Pull Request #135899 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-133296: Publicly expose critical section API that accepts PyMutex
  • Loading branch information
ngoldbaum committed Jun 24, 2025
commit 176aa3dcb35efe8b4dc4180b267dc37796866dc5
40 changes: 40 additions & 0 deletions Doc/c-api/init.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,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` instances rather than 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
Expand Down Expand Up @@ -2339,6 +2345,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 second argument - it must be a :c:type:`PyMutex`.

On the default build, this macro expands to ``{``.

.. versionadded:: 3.15

.. c:macro:: Py_END_CRITICAL_SECTION()

Ends the critical section and releases the per-object lock.
Expand Down Expand Up @@ -2368,6 +2391,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 second and third arguments - they must be :c:type:`PyMutex` instances.

On the default build, this macro expands to ``{``.

.. versionadded:: 3.15

.. c:macro:: Py_END_CRITICAL_SECTION2()

Ends the critical section and releases the per-object locks.
Expand Down
20 changes: 20 additions & 0 deletions Include/cpython/critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(mut) \
{
# define Py_END_CRITICAL_SECTION() \
}
# define Py_BEGIN_CRITICAL_SECTION2(a, b) \
{
# define Py_BEGIN_CRITICAL_SECTION2_MUTEX(mut) \
{
# define Py_END_CRITICAL_SECTION2() \
}
#else /* !Py_GIL_DISABLED */
Expand Down Expand Up @@ -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); \
}
Expand All @@ -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); \
}
Expand Down
14 changes: 2 additions & 12 deletions Include/internal/pycore_critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
New variants for the critical section API that accept one or two
:c:type:`PyMutex` instances rather than :c:type:`PyObject` instances are now
public in the non-limited C API.
2 changes: 1 addition & 1 deletion Modules/_ctypes/ctypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,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
Expand Down
7 changes: 7 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2419,8 +2419,15 @@
Py_BEGIN_CRITICAL_SECTION2(module, module);
Py_END_CRITICAL_SECTION2();

PyMutex mut = {0};

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

unused variable ‘mut’ [-Wunused-variable]

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

unused variable ‘mut’ [-Wunused-variable]

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

unused variable ‘mut’ [-Wunused-variable]

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

unused variable ‘mut’ [-Wunused-variable]

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

unused variable ‘mut’ [-Wunused-variable]

Check warning on line 2422 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

unused variable ‘mut’ [-Wunused-variable]
Py_BEGIN_CRITICAL_SECTION_MUTEX(&mut);
Py_END_CRITICAL_SECTION();

Py_BEGIN_CRITICAL_SECTION2_MUTEX(&mut, &mut);

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

‘Py_BEGIN_CRITICAL_SECTION2_MUTEX’ undeclared (first use in this function)

Check failure on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

macro "Py_BEGIN_CRITICAL_SECTION2_MUTEX" passed 2 arguments, but takes just 1

Check warning on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (x64)

too many arguments for function-like macro invocation 'Py_BEGIN_CRITICAL_SECTION2_MUTEX' [D:\a\cpython\cpython\PCbuild\_testcapi.vcxproj]

Check warning on line 2426 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Windows / Build and test (arm64)

too many arguments for function-like macro invocation 'Py_BEGIN_CRITICAL_SECTION2_MUTEX' [C:\a\cpython\cpython\PCbuild\_testcapi.vcxproj]
Py_END_CRITICAL_SECTION2();

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

no return statement in function returning non-void [-Wreturn-type]

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

no return statement in function returning non-void [-Wreturn-type]

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

no return statement in function returning non-void [-Wreturn-type]

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

no return statement in function returning non-void [-Wreturn-type]

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

no return statement in function returning non-void [-Wreturn-type]

Check warning on line 2427 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

no return statement in function returning non-void [-Wreturn-type]

Py_RETURN_NONE;
}

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Hypothesis tests on Ubuntu

expected identifier or ‘(’ before ‘}’ token

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04)

expected identifier or ‘(’ before ‘}’ token

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu (bolt) / build and test (ubuntu-24.04)

expected identifier or ‘(’ before ‘}’ token

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Ubuntu / build and test (ubuntu-24.04-arm)

expected identifier or ‘(’ before ‘}’ token

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Cross build Linux

expected identifier or ‘(’ before ‘}’ token

Check failure on line 2430 in Modules/_testcapimodule.c

View workflow job for this annotation

GitHub Actions / Address sanitizer (ubuntu-24.04)

expected identifier or ‘(’ before ‘}’ token


// Used by `finalize_thread_hang`.
Expand Down
4 changes: 2 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@ class object "PyObject *" "&PyBaseObject_Type"
// while the stop-the-world mechanism is active. The slots and flags are read
// in many places without holding a lock and without atomics.
#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()

Expand Down
18 changes: 18 additions & 0 deletions Python/critical_section.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading
0