From 84ba45348caa53549300b2a9f2e7331b7b32988f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 17:15:50 -0500 Subject: [PATCH 1/9] Document legacy PyThread_*lock APIs --- Doc/c-api/init.rst | 95 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 4a841c9e3c8f9a..175e28d181a829 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2539,3 +2539,98 @@ code triggered by the finalizer blocks and calls :c:func:`PyEval_SaveThread`. In the default build, this macro expands to ``}``. .. versionadded:: 3.13 + + +Legacy Locking APIs +------------------- + +These APIs are obsolete since Python 3.13 with the introduction of +:c:type:`PyMutex`. + +.. versionchanged:: 3.15 + These APIs are now a simple wrapper around ``PyMutex``. + + +.. c:type:: PyThread_type_lock + + A pointer to a mutual exclusion lock. + + +.. c:type:: PyLockStatus + + The result of acquiring a lock with a timeout. + + .. c:enumerator:: PY_LOCK_FAILURE + + Failed to acquire the lock. + + .. c:enumerator:: PY_LOCK_ACQUIRED + + The lock was successfully acquired. + + .. c:enumerator:: PY_LOCK_INTR + + The lock was interrupted by an interrupt signal. + + +.. c:function:: PyThread_type_lock PyThread_allocate_lock(void) + + Allocate a new lock. + + On success, this function returns a non-zero lock; on failure, this + function returns ``0`` without an exception set. + + The caller does not need to hold an :term:`attached thread state`. + + .. versionchanged:: 3.15 + This function now always uses :c:type:`PyMutex`. In prior versions, this + would use a lock provided by the operating system. + + +.. c:function:: PyThread_free_lock(PyThread_type_lock lock) + + Destroy the lock *lock*. + + +.. c:function:: PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag) + + Acquire lock *lock*. + + This will wait for *microseconds* microseconds to acquire the lock. If the + timeout expires, this function returns :c:enumerator:`PY_LOCK_FAILURE`. + If *microseconds* is ``-1``, this will wait indefinitely until the lock has + been released. + + If *intr_flag* is ``1``, acquiring the lock may be interrupted by CTRL^C, + in which case this function returns :c:enumerator:`PY_LOCK_INTR`. + + If the lock is successfully acquired, this function returns + :c:enumerator:`PY_LOCK_ACQUIRED`. + + The caller does not need to hold an :term:`attached thread state`. + + +.. c:function:: int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) + + Acquire lock *lock*. + + If *waitflag* is ``1`` and another thread currently holds the lock, this + function will wait until the lock can be acquired and will always return + ``1``. + + If *waitflag* is ``0`` and another thread holds the lock, this function will + not wait and instead return ``0``. If the lock is not held by any other + thread, then this function will quickly acquire it and return ``1``. + + Unlike :c:func:`PyThread_acquire_lock_timed`, acquiring the lock cannot be + interrupted by CTRL^C. + + The caller does not need to hold an :term:`attached thread state`. + + +.. c:function:: int PyThread_release_lock(PyThread_type_lock lock) + + Release lock *lock*. If *lock* is not held, then this function issues a + fatal error. + + The caller does not need to hold an :term:`attached thread state`. From ec3778ec625b39198560a23c9be4e42954a035a4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 17:19:25 -0500 Subject: [PATCH 2/9] Eliminate some redundancy. --- Doc/c-api/init.rst | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 175e28d181a829..44051d8025a9b1 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2587,14 +2587,17 @@ These APIs are obsolete since Python 3.13 with the introduction of would use a lock provided by the operating system. -.. c:function:: PyThread_free_lock(PyThread_type_lock lock) +.. c:function:: void PyThread_free_lock(PyThread_type_lock lock) - Destroy the lock *lock*. + Destroy *lock*. The lock should not be held by any thread when calling + this. + + The caller does not need to hold an :term:`attached thread state`. .. c:function:: PyLockStatus PyThread_acquire_lock_timed(PyThread_type_lock lock, long long microseconds, int intr_flag) - Acquire lock *lock*. + Acquire *lock* with a timeout. This will wait for *microseconds* microseconds to acquire the lock. If the timeout expires, this function returns :c:enumerator:`PY_LOCK_FAILURE`. @@ -2612,7 +2615,7 @@ These APIs are obsolete since Python 3.13 with the introduction of .. c:function:: int PyThread_acquire_lock(PyThread_type_lock lock, int waitflag) - Acquire lock *lock*. + Acquire *lock*. If *waitflag* is ``1`` and another thread currently holds the lock, this function will wait until the lock can be acquired and will always return @@ -2630,7 +2633,7 @@ These APIs are obsolete since Python 3.13 with the introduction of .. c:function:: int PyThread_release_lock(PyThread_type_lock lock) - Release lock *lock*. If *lock* is not held, then this function issues a + Release *lock*. If *lock* is not held, then this function issues a fatal error. The caller does not need to hold an :term:`attached thread state`. From 1523e76873c5c4fff038cf6a9f792f37789e4455 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 17:52:23 -0500 Subject: [PATCH 3/9] Document the remaining PyThread* APIs. --- Doc/c-api/init.rst | 115 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 44051d8025a9b1..f5bc6449f8abc7 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2637,3 +2637,118 @@ These APIs are obsolete since Python 3.13 with the introduction of fatal error. The caller does not need to hold an :term:`attached thread state`. + + +Operating System Thread APIs +============================ + +.. c:macro:: PYTHREAD_INVALID_THREAD_ID + + Sentinel value for an invalid thread ID. + + This is currently equivalent to ``-1``. + + +.. c:function:: unsigned long PyThread_start_new_thread(void (*func)(void *), void *arg) + + Start function *func* in a new thread with argument *arg*. + The resulting thread is not intended to be joined. + + *func* must not be ``NULL``, but *arg* may be ``NULL``. + + On success, this function returns the ID of the new thread; on failure, + this returns :c:macro:`PYTHREAD_INVALID_THREAD_ID`. + + The caller does not need to hold an :term:`attached thread state`. + + +.. c:function:: unsigned long PyThread_get_thread_ident(void) + + Return the identifier of the current thread, which will never be zero. + + This function cannot fail, and the caller does not need to hold an + :term:`attached thread state`. + + .. seealso:: + :py:func:`threading.get_ident` + + +.. c:function:: PyObject *PyThread_GetInfo(void) + + Get general information about the current thread in the form of a + :ref:`struct sequence ` object. This information is + accessible as :py:attr:`sys.thread_info` in Python. + + On success, this returns a new :term:`strong reference` to the thread + information; on failure, this returns ``NULL`` with an exception set. + + The caller must hold an :term:`attached thread state`. + + +.. c:macro:: PY_HAVE_THREAD_NATIVE_ID + + This is defined when the system supports native thread IDs. + + +.. c:function:: unsigned long PyThread_get_thread_native_id(void) + + Get the native ID of the current thread as it was assigned by the operating + system's kernel, which will never be less than zero. + + This function is only available when :c:macro:`PY_HAVE_THREAD_NATIVE_ID` is + defined. + + This function cannot fail, and the caller does not need to hold an + :term:`attached thread state`. + + .. seealso:: + :py:func:`threading.get_native_id` + + +.. c:function:: void PyThread_exit_thread(void) + + Terminate the current thread. This function is generally considered unsafe + and should be avoided. It is kept solely for backwards compatibility. + + This function is only safe to call if all functions in the full call + stack are written to safely allow it. + + .. warning:: + + If the current system uses POSIX threads (also known as "pthreads"), + this calls :man:`pthread_exit(3)`, attempts to unwind the stack and + call C++ destructors on some libc implementations. However, if a + ``noexcept`` function is reached, they may terminate the process. + Other systems, such as macOS, do unwinding. + + On Windows, this function calls ``_endthreadex``, which kills the thread + without calling C++ destructors. + + In any case, there is a risk of corruption on the thread's stack. + + .. deprecated:: 3.14 + + +.. c:function:: void PyThread_init_thread(void) + + Initialize ``PyThread*`` APIs. Python executes this function automatically, + so there's little need to call it from an extension module. + + +.. c:function:: int PyThread_set_stacksize(size_t size) + + Set the stack size of the current thread to *size*. + + This function returns ``0`` on success, ``-1`` if *size* is invalid, or + ``-2`` if the system does not support changing the stack size. This function + does not set exceptions. + + The caller does not need to hold an :term:`attached thread state`. + + +.. c:function:: size_t PyThread_get_stacksize(void) + + Return the stack size of the current thread, or ``0`` if the system's + default stack size is in use. + + The caller does not need to hold an :term:`attached thread state`. From f01a9f8d5e1a39b3632ebc41c57ae659b801f3b6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 17:53:03 -0500 Subject: [PATCH 4/9] Fix Sphinx ref. --- Doc/c-api/init.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index f5bc6449f8abc7..fefc0b32894b8b 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2716,7 +2716,7 @@ Operating System Thread APIs .. warning:: If the current system uses POSIX threads (also known as "pthreads"), - this calls :man:`pthread_exit(3)`, attempts to unwind the stack and + this calls :manpage:`pthread_exit(3)`, attempts to unwind the stack and call C++ destructors on some libc implementations. However, if a ``noexcept`` function is reached, they may terminate the process. Other systems, such as macOS, do unwinding. From 85fea13a443277df05be247aeefb85f127432ea3 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 17:54:18 -0500 Subject: [PATCH 5/9] Fix typo. --- Doc/c-api/init.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index fefc0b32894b8b..1a0cc3316dde81 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2716,8 +2716,8 @@ Operating System Thread APIs .. warning:: If the current system uses POSIX threads (also known as "pthreads"), - this calls :manpage:`pthread_exit(3)`, attempts to unwind the stack and - call C++ destructors on some libc implementations. However, if a + this calls :manpage:`pthread_exit(3)`, which attempts to unwind the stack + and call C++ destructors on some libc implementations. However, if a ``noexcept`` function is reached, they may terminate the process. Other systems, such as macOS, do unwinding. From 88d905d8c4b92855d856448318de02eedd4348dc Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 18:06:02 -0500 Subject: [PATCH 6/9] Set namespace to NULL for PyLockStatus things. --- Doc/c-api/init.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 1a0cc3316dde81..04f58e56390746 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2560,6 +2560,8 @@ These APIs are obsolete since Python 3.13 with the introduction of The result of acquiring a lock with a timeout. + .. c:namespace:: NULL + .. c:enumerator:: PY_LOCK_FAILURE Failed to acquire the lock. From f50cbbbcbf41841c5795f38f88d621199d453e22 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 20 Nov 2025 18:10:44 -0500 Subject: [PATCH 7/9] Fix typo. --- Doc/c-api/init.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 04f58e56390746..64ae664fcc15c3 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2720,7 +2720,7 @@ Operating System Thread APIs If the current system uses POSIX threads (also known as "pthreads"), this calls :manpage:`pthread_exit(3)`, which attempts to unwind the stack and call C++ destructors on some libc implementations. However, if a - ``noexcept`` function is reached, they may terminate the process. + ``noexcept`` function is reached, it may terminate the process. Other systems, such as macOS, do unwinding. On Windows, this function calls ``_endthreadex``, which kills the thread From 36c8ebc47a44a501eff05e90544d9c9379c4e14d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 21 Nov 2025 08:42:22 -0500 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Victor Stinner --- Doc/c-api/init.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 64ae664fcc15c3..45339abf4aa423 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2572,14 +2572,14 @@ These APIs are obsolete since Python 3.13 with the introduction of .. c:enumerator:: PY_LOCK_INTR - The lock was interrupted by an interrupt signal. + The lock was interrupted by a signal. .. c:function:: PyThread_type_lock PyThread_allocate_lock(void) Allocate a new lock. - On success, this function returns a non-zero lock; on failure, this + On success, this function returns a lock; on failure, this function returns ``0`` without an exception set. The caller does not need to hold an :term:`attached thread state`. @@ -2625,10 +2625,10 @@ These APIs are obsolete since Python 3.13 with the introduction of If *waitflag* is ``0`` and another thread holds the lock, this function will not wait and instead return ``0``. If the lock is not held by any other - thread, then this function will quickly acquire it and return ``1``. + thread, then this function will acquire it and return ``1``. Unlike :c:func:`PyThread_acquire_lock_timed`, acquiring the lock cannot be - interrupted by CTRL^C. + interrupted by a signal. The caller does not need to hold an :term:`attached thread state`. @@ -2648,7 +2648,7 @@ Operating System Thread APIs Sentinel value for an invalid thread ID. - This is currently equivalent to ``-1``. + This is currently equivalent to ``(unsigned long)-1``. .. c:function:: unsigned long PyThread_start_new_thread(void (*func)(void *), void *arg) @@ -2658,7 +2658,7 @@ Operating System Thread APIs *func* must not be ``NULL``, but *arg* may be ``NULL``. - On success, this function returns the ID of the new thread; on failure, + On success, this function returns the identifier of the new thread; on failure, this returns :c:macro:`PYTHREAD_INVALID_THREAD_ID`. The caller does not need to hold an :term:`attached thread state`. @@ -2689,12 +2689,12 @@ Operating System Thread APIs .. c:macro:: PY_HAVE_THREAD_NATIVE_ID - This is defined when the system supports native thread IDs. + This macro is defined when the system supports native thread IDs. .. c:function:: unsigned long PyThread_get_thread_native_id(void) - Get the native ID of the current thread as it was assigned by the operating + Get the native identifier of the current thread as it was assigned by the operating system's kernel, which will never be less than zero. This function is only available when :c:macro:`PY_HAVE_THREAD_NATIVE_ID` is @@ -2723,7 +2723,7 @@ Operating System Thread APIs ``noexcept`` function is reached, it may terminate the process. Other systems, such as macOS, do unwinding. - On Windows, this function calls ``_endthreadex``, which kills the thread + On Windows, this function calls ``_endthreadex()``, which kills the thread without calling C++ destructors. In any case, there is a risk of corruption on the thread's stack. @@ -2739,7 +2739,7 @@ Operating System Thread APIs .. c:function:: int PyThread_set_stacksize(size_t size) - Set the stack size of the current thread to *size*. + Set the stack size of the current thread to *size* bytes. This function returns ``0`` on success, ``-1`` if *size* is invalid, or ``-2`` if the system does not support changing the stack size. This function @@ -2750,7 +2750,7 @@ Operating System Thread APIs .. c:function:: size_t PyThread_get_stacksize(void) - Return the stack size of the current thread, or ``0`` if the system's + Return the stack size of the current thread in bytes, or ``0`` if the system's default stack size is in use. The caller does not need to hold an :term:`attached thread state`. From 2338b2bf72f7ff362ba4b52c58d5d5c7975068a7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Fri, 21 Nov 2025 09:49:54 -0500 Subject: [PATCH 9/9] Add note about Py_MakePendingCalls() --- Doc/c-api/init.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst index 45339abf4aa423..70643bc07f61d0 100644 --- a/Doc/c-api/init.rst +++ b/Doc/c-api/init.rst @@ -2606,8 +2606,10 @@ These APIs are obsolete since Python 3.13 with the introduction of If *microseconds* is ``-1``, this will wait indefinitely until the lock has been released. - If *intr_flag* is ``1``, acquiring the lock may be interrupted by CTRL^C, - in which case this function returns :c:enumerator:`PY_LOCK_INTR`. + If *intr_flag* is ``1``, acquiring the lock may be interrupted by a signal, + in which case this function returns :c:enumerator:`PY_LOCK_INTR`. Upon + interruption, it's generally expected that the caller makes a call to + :c:func:`Py_MakePendingCalls` to propagate an exception to Python code. If the lock is successfully acquired, this function returns :c:enumerator:`PY_LOCK_ACQUIRED`.