summaryrefslogtreecommitdiffstats
path: root/Doc
diff options
context:
space:
mode:
authorJeremy Maitin-Shepard <jeremy@jeremyms.com>2024-10-02 16:17:49 (GMT)
committerGitHub <noreply@github.com>2024-10-02 16:17:49 (GMT)
commit8cc5aa47ee464ddfd8da5461edecf4a5c72df2ff (patch)
tree41cdda0e2be153a4363cec9d616856444b306273 /Doc
parent113b2d7583cdbf79da18e696f299a9aca24b599b (diff)
downloadcpython-8cc5aa47ee464ddfd8da5461edecf4a5c72df2ff.zip
cpython-8cc5aa47ee464ddfd8da5461edecf4a5c72df2ff.tar.gz
cpython-8cc5aa47ee464ddfd8da5461edecf4a5c72df2ff.tar.bz2
gh-87135: Hang non-main threads that attempt to acquire the GIL during finalization (GH-105805)
Instead of surprise crashes and memory corruption, we now hang threads that attempt to re-enter the Python interpreter after Python runtime finalization has started. These are typically daemon threads (our long standing mis-feature) but could also be threads spawned by extension modules that then try to call into Python. This marks the `PyThread_exit_thread` public C API as deprecated as there is no plausible safe way to accomplish that on any supported platform in the face of things like C++ code with finalizers anywhere on a thread's stack. Doing this was the least bad option. Co-authored-by: Gregory P. Smith <greg@krypto.org>
Diffstat (limited to 'Doc')
-rw-r--r--Doc/c-api/init.rst76
1 files changed, 60 insertions, 16 deletions
diff --git a/Doc/c-api/init.rst b/Doc/c-api/init.rst
index 561c8a1..0ed3f77 100644
--- a/Doc/c-api/init.rst
+++ b/Doc/c-api/init.rst
@@ -430,7 +430,11 @@ Initializing and finalizing the interpreter
Some memory allocated by extension modules may not be freed. Some extensions may not
work properly if their initialization routine is called more than once; this can
happen if an application calls :c:func:`Py_Initialize` and :c:func:`Py_FinalizeEx`
- more than once.
+ more than once. :c:func:`Py_FinalizeEx` must not be called recursively from
+ within itself. Therefore, it must not be called by any code that may be run
+ as part of the interpreter shutdown process, such as :py:mod:`atexit`
+ handlers, object finalizers, or any code that may be run while flushing the
+ stdout and stderr files.
.. audit-event:: cpython._PySys_ClearAuditHooks "" c.Py_FinalizeEx
@@ -960,6 +964,37 @@ thread, where the CPython global runtime was originally initialized.
The only exception is if :c:func:`exec` will be called immediately
after.
+.. _cautions-regarding-runtime-finalization:
+
+Cautions regarding runtime finalization
+---------------------------------------
+
+In the late stage of :term:`interpreter shutdown`, after attempting to wait for
+non-daemon threads to exit (though this can be interrupted by
+:class:`KeyboardInterrupt`) and running the :mod:`atexit` functions, the runtime
+is marked as *finalizing*: :c:func:`_Py_IsFinalizing` and
+:func:`sys.is_finalizing` return true. At this point, only the *finalization
+thread* that initiated finalization (typically the main thread) is allowed to
+acquire the :term:`GIL`.
+
+If any thread, other than the finalization thread, attempts to acquire the GIL
+during finalization, either explicitly via :c:func:`PyGILState_Ensure`,
+:c:macro:`Py_END_ALLOW_THREADS`, :c:func:`PyEval_AcquireThread`, or
+:c:func:`PyEval_AcquireLock`, or implicitly when the interpreter attempts to
+reacquire it after having yielded it, the thread enters **a permanently blocked
+state** where it remains until the program exits. In most cases this is
+harmless, but this can result in deadlock if a later stage of finalization
+attempts to acquire a lock owned by the blocked thread, or otherwise waits on
+the blocked thread.
+
+Gross? Yes. This prevents random crashes and/or unexpectedly skipped C++
+finalizations further up the call stack when such threads were forcibly exited
+here in CPython 3.13 and earlier. The CPython runtime GIL acquiring C APIs
+have never had any error reporting or handling expectations at GIL acquisition
+time that would've allowed for graceful exit from this situation. Changing that
+would require new stable C APIs and rewriting the majority of C code in the
+CPython ecosystem to use those with error handling.
+
High-level API
--------------
@@ -1033,11 +1068,14 @@ code, or when embedding the Python interpreter:
ensues.
.. note::
- Calling this function from a thread when the runtime is finalizing
- will terminate the thread, even if the thread was not created by Python.
- You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
- check if the interpreter is in process of being finalized before calling
- this function to avoid unwanted termination.
+ Calling this function from a thread when the runtime is finalizing will
+ hang the thread until the program exits, even if the thread was not
+ created by Python. Refer to
+ :ref:`cautions-regarding-runtime-finalization` for more details.
+
+ .. versionchanged:: next
+ Hangs the current thread, rather than terminating it, if called while the
+ interpreter is finalizing.
.. c:function:: PyThreadState* PyThreadState_Get()
@@ -1092,11 +1130,14 @@ with sub-interpreters:
to call arbitrary Python code. Failure is a fatal error.
.. note::
- Calling this function from a thread when the runtime is finalizing
- will terminate the thread, even if the thread was not created by Python.
- You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
- check if the interpreter is in process of being finalized before calling
- this function to avoid unwanted termination.
+ Calling this function from a thread when the runtime is finalizing will
+ hang the thread until the program exits, even if the thread was not
+ created by Python. Refer to
+ :ref:`cautions-regarding-runtime-finalization` for more details.
+
+ .. versionchanged:: next
+ Hangs the current thread, rather than terminating it, if called while the
+ interpreter is finalizing.
.. c:function:: void PyGILState_Release(PyGILState_STATE)
@@ -1374,17 +1415,20 @@ All of the following functions must be called after :c:func:`Py_Initialize`.
If this thread already has the lock, deadlock ensues.
.. note::
- Calling this function from a thread when the runtime is finalizing
- will terminate the thread, even if the thread was not created by Python.
- You can use :c:func:`Py_IsFinalizing` or :func:`sys.is_finalizing` to
- check if the interpreter is in process of being finalized before calling
- this function to avoid unwanted termination.
+ Calling this function from a thread when the runtime is finalizing will
+ hang the thread until the program exits, even if the thread was not
+ created by Python. Refer to
+ :ref:`cautions-regarding-runtime-finalization` for more details.
.. versionchanged:: 3.8
Updated to be consistent with :c:func:`PyEval_RestoreThread`,
:c:func:`Py_END_ALLOW_THREADS`, and :c:func:`PyGILState_Ensure`,
and terminate the current thread if called while the interpreter is finalizing.
+ .. versionchanged:: next
+ Hangs the current thread, rather than terminating it, if called while the
+ interpreter is finalizing.
+
:c:func:`PyEval_RestoreThread` is a higher-level function which is always
available (even when threads have not been initialized).