mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
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>
This commit is contained in:
parent
113b2d7583
commit
8cc5aa47ee
10 changed files with 247 additions and 29 deletions
|
|
@ -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).
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue