diff --git a/Include/Python.h b/Include/Python.h index e05901b9e52b5a..1520453873af3f 100644 --- a/Include/Python.h +++ b/Include/Python.h @@ -119,6 +119,7 @@ #include "import.h" #include "abstract.h" #include "bltinmodule.h" +#include "critical_section.h" #include "cpython/pyctype.h" #include "pystrtod.h" #include "pystrcmp.h" diff --git a/Include/cpython/critical_section.h b/Include/cpython/critical_section.h new file mode 100644 index 00000000000000..38b2917bad8f5d --- /dev/null +++ b/Include/cpython/critical_section.h @@ -0,0 +1,75 @@ +#ifndef Py_CPYTHON_CRITICAL_SECTION_H +# error "this header file must not be included directly" +#endif + +// Python critical sections. +// +// These operations are no-ops in the default build. See +// pycore_critical_section.h for details. + +typedef struct _PyCriticalSection _PyCriticalSection; +typedef struct _PyCriticalSection2 _PyCriticalSection2; + +#ifndef Py_GIL_DISABLED +# define Py_BEGIN_CRITICAL_SECTION(op) \ + { +# define Py_END_CRITICAL_SECTION() \ + } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) \ + { +# define Py_END_CRITICAL_SECTION2() \ + } +#else /* !Py_GIL_DISABLED */ + +// (private) +struct _PyCriticalSection { + // Tagged pointer to an outer active critical section (or 0). + uintptr_t prev; + + // Mutex used to protect critical section + struct _PyMutex *mutex; +}; + +// (private) A critical section protected by two mutexes. Use +// Py_BEGIN_CRITICAL_SECTION2 and Py_END_CRITICAL_SECTION2. +struct _PyCriticalSection2 { + struct _PyCriticalSection base; + + struct _PyMutex *mutex2; +}; + +# define Py_BEGIN_CRITICAL_SECTION(op) \ + { \ + _PyCriticalSection _cs; \ + _PyCriticalSection_Begin(&_cs, _PyObject_CAST(op)) + +# define Py_END_CRITICAL_SECTION() \ + _PyCriticalSection_End(&_cs); \ + } + +# define Py_BEGIN_CRITICAL_SECTION2(a, b) \ + { \ + _PyCriticalSection2 _cs2; \ + _PyCriticalSection2_Begin(&_cs2, _PyObject_CAST(a), _PyObject_CAST(b)) + +# define Py_END_CRITICAL_SECTION2() \ + _PyCriticalSection2_End(&_cs2); \ + } + +#endif + +// (private) +PyAPI_FUNC(void) +_PyCriticalSection_Begin(_PyCriticalSection *c, PyObject *op); + +// (private) +PyAPI_FUNC(void) +_PyCriticalSection_End(_PyCriticalSection *c); + +// CPython internals should use pycore_critical_section.h instead. +#ifdef Py_BUILD_CORE +# undef Py_BEGIN_CRITICAL_SECTION +# undef Py_END_CRITICAL_SECTION +# undef Py_BEGIN_CRITICAL_SECTION2 +# undef Py_END_CRITICAL_SECTION2 +#endif diff --git a/Include/critical_section.h b/Include/critical_section.h new file mode 100644 index 00000000000000..3b37615a8b17e2 --- /dev/null +++ b/Include/critical_section.h @@ -0,0 +1,16 @@ +#ifndef Py_CRITICAL_SECTION_H +#define Py_CRITICAL_SECTION_H +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef Py_LIMITED_API +# define Py_CPYTHON_CRITICAL_SECTION_H +# include "cpython/critical_section.h" +# undef Py_CPYTHON_CRITICAL_SECTION_H +#endif + +#ifdef __cplusplus +} +#endif +#endif /* !Py_CRITICAL_SECTION_H */ diff --git a/Include/internal/pycore_critical_section.h b/Include/internal/pycore_critical_section.h index 573d09a09683ef..afb92361cea352 100644 --- a/Include/internal/pycore_critical_section.h +++ b/Include/internal/pycore_critical_section.h @@ -90,28 +90,30 @@ extern "C" { # define Py_BEGIN_CRITICAL_SECTION_MUT(mutex) \ { \ _PyCriticalSection _cs; \ - _PyCriticalSection_Begin(&_cs, mutex) + _PyCriticalSection_BeginInline(&_cs, mutex) # define Py_BEGIN_CRITICAL_SECTION(op) \ Py_BEGIN_CRITICAL_SECTION_MUT(&_PyObject_CAST(op)->ob_mutex) # define Py_END_CRITICAL_SECTION() \ - _PyCriticalSection_End(&_cs); \ + _PyCriticalSection_EndInline(&_cs); \ } # define Py_BEGIN_CRITICAL_SECTION2(a, b) \ { \ _PyCriticalSection2 _cs2; \ - _PyCriticalSection2_Begin(&_cs2, &_PyObject_CAST(a)->ob_mutex, &_PyObject_CAST(b)->ob_mutex) + _PyCriticalSection2_BeginInline(&_cs2, \ + &_PyObject_CAST(a)->ob_mutex, \ + &_PyObject_CAST(b)->ob_mutex) # define Py_END_CRITICAL_SECTION2() \ - _PyCriticalSection2_End(&_cs2); \ + _PyCriticalSection2_EndInline(&_cs2); \ } // Asserts that the mutex is locked. The mutex must be held by the // top-most critical section otherwise there's the possibility // that the mutex would be swalled out in some code paths. -#define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) \ +#define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) \ _PyCriticalSection_AssertHeld(mutex) // Asserts that the mutex for the given object is locked. The mutex must @@ -119,51 +121,29 @@ extern "C" { // possibility that the mutex would be swalled out in some code paths. #ifdef Py_DEBUG -#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \ +# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) \ if (Py_REFCNT(op) != 1) { \ _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(&_PyObject_CAST(op)->ob_mutex); \ } #else /* Py_DEBUG */ -#define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) +# define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) #endif /* Py_DEBUG */ #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_SECTION(op) -# define Py_END_CRITICAL_SECTION() -# define Py_BEGIN_CRITICAL_SECTION2(a, b) -# define Py_END_CRITICAL_SECTION2() +# define Py_BEGIN_CRITICAL_SECTION_MUT(mut) { +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } # define _Py_CRITICAL_SECTION_ASSERT_MUTEX_LOCKED(mutex) # define _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(op) #endif /* !Py_GIL_DISABLED */ -typedef struct { - // Tagged pointer to an outer active critical section (or 0). - // The two least-significant-bits indicate whether the pointed-to critical - // section is inactive and whether it is a _PyCriticalSection2 object. - uintptr_t prev; - - // Mutex used to protect critical section - PyMutex *mutex; -} _PyCriticalSection; - -// A critical section protected by two mutexes. Use -// _PyCriticalSection2_Begin and _PyCriticalSection2_End. -typedef struct { - _PyCriticalSection base; - - PyMutex *mutex2; -} _PyCriticalSection2; - -static inline int -_PyCriticalSection_IsActive(uintptr_t tag) -{ - return tag != 0 && (tag & _Py_CRITICAL_SECTION_INACTIVE) == 0; -} +typedef struct _PyCriticalSection2 _PyCriticalSection2; // Resumes the top-most critical section. PyAPI_FUNC(void) @@ -177,8 +157,19 @@ PyAPI_FUNC(void) _PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2, int is_m1_locked); +PyAPI_FUNC(void) +_PyCriticalSection_SuspendAll(PyThreadState *tstate); + +#ifdef Py_GIL_DISABLED + +static inline int +_PyCriticalSection_IsActive(uintptr_t tag) +{ + return tag != 0 && (tag & _Py_CRITICAL_SECTION_INACTIVE) == 0; +} + static inline void -_PyCriticalSection_Begin(_PyCriticalSection *c, PyMutex *m) +_PyCriticalSection_BeginInline(_PyCriticalSection *c, PyMutex *m) { if (PyMutex_LockFast(&m->v)) { PyThreadState *tstate = _PyThreadState_GET(); @@ -207,20 +198,20 @@ _PyCriticalSection_Pop(_PyCriticalSection *c) } static inline void -_PyCriticalSection_End(_PyCriticalSection *c) +_PyCriticalSection_EndInline(_PyCriticalSection *c) { PyMutex_Unlock(c->mutex); _PyCriticalSection_Pop(c); } static inline void -_PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2) +_PyCriticalSection2_BeginInline(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2) { if (m1 == m2) { // If the two mutex arguments are the same, treat this as a critical // section with a single mutex. c->mutex2 = NULL; - _PyCriticalSection_Begin(&c->base, m1); + _PyCriticalSection_BeginInline(&c->base, m1); return; } @@ -253,7 +244,7 @@ _PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2) } static inline void -_PyCriticalSection2_End(_PyCriticalSection2 *c) +_PyCriticalSection2_EndInline(_PyCriticalSection2 *c) { if (c->mutex2) { PyMutex_Unlock(c->mutex2); @@ -262,11 +253,6 @@ _PyCriticalSection2_End(_PyCriticalSection2 *c) _PyCriticalSection_Pop(&c->base); } -PyAPI_FUNC(void) -_PyCriticalSection_SuspendAll(PyThreadState *tstate); - -#ifdef Py_GIL_DISABLED - static inline void _PyCriticalSection_AssertHeld(PyMutex *mutex) { @@ -285,7 +271,7 @@ _PyCriticalSection_AssertHeld(PyMutex *mutex) #endif } -#endif +#endif /* Py_GIL_DISABLED */ #ifdef __cplusplus } diff --git a/Makefile.pre.in b/Makefile.pre.in index 9e99c95e2af042..54a1dc15fc4e62 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -1002,6 +1002,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/codecs.h \ $(srcdir)/Include/compile.h \ $(srcdir)/Include/complexobject.h \ + $(srcdir)/Include/critical_section.h \ $(srcdir)/Include/descrobject.h \ $(srcdir)/Include/dictobject.h \ $(srcdir)/Include/dynamic_annotations.h \ @@ -1078,6 +1079,7 @@ PYTHON_HEADERS= \ $(srcdir)/Include/cpython/compile.h \ $(srcdir)/Include/cpython/complexobject.h \ $(srcdir)/Include/cpython/context.h \ + $(srcdir)/Include/cpython/critical_section.h \ $(srcdir)/Include/cpython/descrobject.h \ $(srcdir)/Include/cpython/dictobject.h \ $(srcdir)/Include/cpython/fileobject.h \ diff --git a/Misc/NEWS.d/next/C API/2024-05-21-19-41-41.gh-issue-119344.QKvzQb.rst b/Misc/NEWS.d/next/C API/2024-05-21-19-41-41.gh-issue-119344.QKvzQb.rst new file mode 100644 index 00000000000000..5a2e4d980b59be --- /dev/null +++ b/Misc/NEWS.d/next/C API/2024-05-21-19-41-41.gh-issue-119344.QKvzQb.rst @@ -0,0 +1 @@ +The critical section API is now public as part of the non-limited C API. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b7c3fcf47f23fc..c5e378bdb63693 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -65,20 +65,20 @@ class object "PyObject *" "&PyBaseObject_Type" #define BEGIN_TYPE_LOCK() \ { \ _PyCriticalSection _cs; \ - _PyCriticalSection_Begin(&_cs, TYPE_LOCK); \ + _PyCriticalSection_BeginInline(&_cs, TYPE_LOCK); \ #define END_TYPE_LOCK() \ - _PyCriticalSection_End(&_cs); \ + _PyCriticalSection_EndInline(&_cs); \ } #define BEGIN_TYPE_DICT_LOCK(d) \ { \ _PyCriticalSection2 _cs; \ - _PyCriticalSection2_Begin(&_cs, TYPE_LOCK, \ - &_PyObject_CAST(d)->ob_mutex); \ + _PyCriticalSection2_BeginInline(&_cs, TYPE_LOCK, \ + &_PyObject_CAST(d)->ob_mutex); \ #define END_TYPE_DICT_LOCK() \ - _PyCriticalSection2_End(&_cs); \ + _PyCriticalSection2_EndInline(&_cs); \ } #define ASSERT_TYPE_LOCK_HELD() \ diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj index 16fb424b11c6a8..1053d282d54688 100644 --- a/PCbuild/pythoncore.vcxproj +++ b/PCbuild/pythoncore.vcxproj @@ -135,6 +135,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/PCbuild/pythoncore.vcxproj.filters b/PCbuild/pythoncore.vcxproj.filters index cf9bc0f4bc1c70..2853a65d2a5ee8 100644 --- a/PCbuild/pythoncore.vcxproj.filters +++ b/PCbuild/pythoncore.vcxproj.filters @@ -72,6 +72,9 @@ Include + + Include + Include @@ -372,6 +375,9 @@ Include\cpython + + Include\cpython + Include\cpython diff --git a/Python/critical_section.c b/Python/critical_section.c index 2214d80eeb297b..2336121f1771f3 100644 --- a/Python/critical_section.c +++ b/Python/critical_section.c @@ -3,12 +3,47 @@ #include "pycore_lock.h" #include "pycore_critical_section.h" +#ifdef Py_GIL_DISABLED static_assert(_Alignof(_PyCriticalSection) >= 4, "critical section must be aligned to at least 4 bytes"); +#endif + +void +_PyCriticalSection_Begin(_PyCriticalSection *c, PyObject *op) +{ +#ifdef Py_GIL_DISABLED + _PyCriticalSection_BeginInline(c, &op->ob_mutex); +#endif +} + +void +_PyCriticalSection_End(_PyCriticalSection *c) +{ +#ifdef Py_GIL_DISABLED + _PyCriticalSection_EndInline(c); +#endif +} + +void +_PyCriticalSection2_Begin(_PyCriticalSection2 *c, PyObject *a, PyObject *b) +{ +#ifdef Py_GIL_DISABLED + _PyCriticalSection2_BeginInline(c, &a->ob_mutex, &b->ob_mutex); +#endif +} + +void +_PyCriticalSection2_End(_PyCriticalSection2 *c) +{ +#ifdef Py_GIL_DISABLED + _PyCriticalSection2_EndInline(c); +#endif +} void _PyCriticalSection_BeginSlow(_PyCriticalSection *c, PyMutex *m) { +#ifdef Py_GIL_DISABLED PyThreadState *tstate = _PyThreadState_GET(); c->mutex = NULL; c->prev = (uintptr_t)tstate->critical_section; @@ -16,12 +51,14 @@ _PyCriticalSection_BeginSlow(_PyCriticalSection *c, PyMutex *m) _PyMutex_LockSlow(m); c->mutex = m; +#endif } void _PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2, int is_m1_locked) { +#ifdef Py_GIL_DISABLED PyThreadState *tstate = _PyThreadState_GET(); c->base.mutex = NULL; c->mutex2 = NULL; @@ -34,19 +71,23 @@ _PyCriticalSection2_BeginSlow(_PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2, PyMutex_Lock(m2); c->base.mutex = m1; c->mutex2 = m2; +#endif } +#ifdef Py_GIL_DISABLED static _PyCriticalSection * untag_critical_section(uintptr_t tag) { return (_PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK); } +#endif // Release all locks held by critical sections. This is called by // _PyThreadState_Detach. void _PyCriticalSection_SuspendAll(PyThreadState *tstate) { +#ifdef Py_GIL_DISABLED uintptr_t *tagptr = &tstate->critical_section; while (_PyCriticalSection_IsActive(*tagptr)) { _PyCriticalSection *c = untag_critical_section(*tagptr); @@ -64,11 +105,13 @@ _PyCriticalSection_SuspendAll(PyThreadState *tstate) *tagptr |= _Py_CRITICAL_SECTION_INACTIVE; tagptr = &c->prev; } +#endif } void _PyCriticalSection_Resume(PyThreadState *tstate) { +#ifdef Py_GIL_DISABLED uintptr_t p = tstate->critical_section; _PyCriticalSection *c = untag_critical_section(p); assert(!_PyCriticalSection_IsActive(p)); @@ -97,4 +140,5 @@ _PyCriticalSection_Resume(PyThreadState *tstate) } tstate->critical_section &= ~_Py_CRITICAL_SECTION_INACTIVE; +#endif }