-
-
Notifications
You must be signed in to change notification settings - Fork 34.6k
gh-149101: Implement PEP 788 #149116
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
ZeroIntensity
wants to merge
82
commits into
python:main
Choose a base branch
from
ZeroIntensity:pep-788
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
gh-149101: Implement PEP 788 #149116
Changes from 11 commits
Commits
Show all changes
82 commits
Select commit
Hold shift + click to select a range
43b0798
Copy the reference implementation.
ZeroIntensity 722dbdf
Merge branch 'main' of https://github.com/python/cpython into pep-788
ZeroIntensity 73cccbb
Document the new APIs.
ZeroIntensity 25e1cf0
Add a whatsnew entry.
ZeroIntensity af1022a
Fix stable ABI things.
ZeroIntensity 2fb8419
Fix test_embed.
ZeroIntensity 1096a32
SIlly news entry.
ZeroIntensity 407062f
Documentation fixes.
ZeroIntensity a33bdfc
Make the sentinel const instead of changing the C analyzer.
ZeroIntensity c504a9f
Fix the html IDs job.
ZeroIntensity f95bfc7
Merge branch 'main' into pep-788
encukou a00bfbb
Apply suggestions from code review
ZeroIntensity 6ca2499
Update Doc/c-api/threads.rst
ZeroIntensity 74259f8
Fix lint and remove dead comment.
ZeroIntensity eff9b40
Improve new PyThreadState API docs.
ZeroIntensity bc78c10
Fix lint.
ZeroIntensity 0a30c25
Apply suggestions from code review
ZeroIntensity fe3d8a1
Update Doc/c-api/threads.rst
ZeroIntensity ad69f96
Fix line endings.
ZeroIntensity 1623556
Remove accidental formatting changes.
ZeroIntensity cad8786
Fix memory ordering for the event reset.
ZeroIntensity e042656
Improve NO_TSTATE_SENTINEL.
ZeroIntensity 6a05c90
Fix some test things.
ZeroIntensity 728ee3a
Remove note about deallocation.
ZeroIntensity d9e1170
Explicitly mark implementation details in the docs.
ZeroIntensity 2f824d3
Add missing versionadded markers.
ZeroIntensity ab9d783
Remove dead comment.
ZeroIntensity 10e241e
Merge branch 'main' of https://github.com/python/cpython into pep-788
ZeroIntensity 25687af
Some improvements to PyThreadState_Release() based on review.
ZeroIntensity 6d3b40a
Add a comment.
ZeroIntensity 0c08141
Remove _ prefix from struct names.
ZeroIntensity 31f1155
Only issue a fatal error for KeyboardInterrupt.
ZeroIntensity 6c62b40
Don't use a full PyThreadState for NO_TSTATE_SENTINEL on release builds.
ZeroIntensity d8ce02d
Fix race during event spinning.
ZeroIntensity e105120
Merge branch 'main' of https://github.com/python/cpython into pep-788
ZeroIntensity eb73203
Merge in the main branch
encukou 09a0d69
Switch to PyThreadStateToken
encukou 5a3f808
Remove ensure_tstate_is_valid
encukou d43ba9d
Merge in the main branch
encukou 9a04e93
Apply suggestions from code review
ZeroIntensity a382721
Simply exit instead of emitting a fatal error upon CTRL^C.
ZeroIntensity 03862fb
Hardcode zero as the main interpreter ID.
ZeroIntensity df720a5
Fix leak in get_main_interp_guard.
ZeroIntensity 767c894
Improve readability and fix warning.
ZeroIntensity 38a2153
Use void instead of a struct for PyThreadStateToken.
ZeroIntensity 321c783
Run test_embed with TSan.
ZeroIntensity 811ca19
Switch to a single atomic field instead of using locks and events.
ZeroIntensity ac58a16
Fix existing thread-safety issue with interp_has_threads().
ZeroIntensity 4ced3a1
Use the parking lot to avoid eating CPU cycles.
ZeroIntensity 9345afd
Stupid merge conflicts.
ZeroIntensity 11cbe5c
Fix C analyzer.
ZeroIntensity 99098d7
Stupid warnings.
ZeroIntensity 1c184f1
Fix thread leaks in test_embed.
ZeroIntensity af57d9d
Fix deadlock in test_atexit.
ZeroIntensity 7289037
Fix missing newline.
ZeroIntensity a538acc
Remove stray newline change from pycore_lock.h
ZeroIntensity 4bec1e7
Use sequential memory ordering in _testembed.
ZeroIntensity 5d28e1a
Potentially fix some things?
ZeroIntensity 62b137e
Add a test stressing a lot of threads against finalization.
ZeroIntensity 2378c88
Export _Py_yield.
ZeroIntensity e7466f1
Fix race in interp_has_threads.
ZeroIntensity 5d797cf
Fix formatting.
ZeroIntensity 5f1d2e0
Merge branch 'main' of https://github.com/python/cpython into pep-788
ZeroIntensity 4b446e7
Close owned guards before deleting them, not after.
ZeroIntensity b4db0a6
Add assertions to ensure that the guard count is zero upon reinitiali…
ZeroIntensity c43cae7
Move PyInterpreterGuard_Close call to after the deletion again.
ZeroIntensity 5e25ba9
Update Python/pystate.c
ZeroIntensity 08e845c
Update Python/pylifecycle.c
ZeroIntensity 17b0e2a
Update Doc/c-api/interp-lifecycle.rst
ZeroIntensity f60d888
Use PyThreadStateToken in the example.
ZeroIntensity dd930ef
Merge branch 'pep-788' of https://github.com/zerointensity/cpython in…
ZeroIntensity 7aa2f65
Fix swapped links the whatsnew.
ZeroIntensity 541169d
Fix failure note + use affirmative tone in PyInterpreterView_FromMain…
ZeroIntensity d75e6d9
Remove usage of _Py_yield.
ZeroIntensity 9f7b014
Fix issue with re-entrant clearing.
ZeroIntensity f9cb8f7
Remove needless calls to _PyThreadState_Attach/_PyThreadState_Detach.
ZeroIntensity 29fa419
Simplify locking in make_pre_finalization_calls.
ZeroIntensity 211edf1
Fix a few other bugs.
ZeroIntensity aaa516e
Move some tests to _testinternalcapi.
ZeroIntensity e1da86c
Add a test stressing detachment behavior.
ZeroIntensity 95cdd3a
Fix unused test.
ZeroIntensity e203fd5
Release the GIL while joining the thread in test_thread_state_release…
ZeroIntensity File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -578,31 +578,199 @@ Initializing and finalizing the interpreter | |
|
|
||
| .. _cautions-regarding-runtime-finalization: | ||
|
|
||
| Cautions regarding runtime finalization | ||
| --------------------------------------- | ||
| Cautions regarding interpreter 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 attach a :term:`thread state` | ||
| during finalization, either explicitly or | ||
| implicitly, 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 :term:`thread state` C APIs | ||
| have never had any error reporting or handling expectations at :term:`thread state` | ||
| attachment 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. | ||
| is marked as finalizing, meaning that :c:func:`Py_IsFinalizing` and | ||
| :func:`sys.is_finalizing` return true. At this point, only the finalization | ||
| thread (the thread that initiated finalization; this is typically the main thread) | ||
| is allowed to :term:`attach <attached thread state>` a thread state. | ||
|
|
||
| Other threads that attempt to attach during finalization, either explicitly | ||
| (such as via :c:func:`PyThreadState_Ensure` or :c:macro:`Py_END_ALLOW_THREADS`) | ||
| or implicitly (such as in-between bytecode instructions), will enter a | ||
| **permanently blocked state**. Generally, this is harmless, but this can | ||
| result in deadlocks. For example, a thread may be permanently blocked while | ||
| holding a lock, meaning that the finalization thread can never acquire that | ||
| lock. | ||
|
|
||
| Prior to CPython 3.13, the thread would exit instead of hanging, | ||
| which led to other issues (see the warning note at | ||
| :c:func:`PyThread_exit_thread`). | ||
|
|
||
| Gross? Yes. Starting in Python 3.15, there are a number of C APIs that make | ||
| it possible to avoid these issues by temporarily preventing finalization: | ||
|
|
||
| .. _interpreter-guards: | ||
|
|
||
| .. seealso:: | ||
|
|
||
| :pep:`788` | ||
|
|
||
| .. c:type:: PyInterpreterGuard | ||
|
|
||
| An opaque interpreter guard structure. | ||
|
|
||
| By holding an interpreter guard, the caller can ensure that the interpreter | ||
| will not finalize until the guard is closed (through | ||
| :c:func:`PyInterpreterGuard_Close`). | ||
|
|
||
| When a guard is held, a thread attempting to finalize the interpreter will | ||
| have to wait until the guard is closed before threads can be blocked. | ||
|
ZeroIntensity marked this conversation as resolved.
Outdated
|
||
| After finalization has started, threads are forever unable to acquire | ||
| guards for that interpreter. This means that if you forget to close an | ||
| interpreter guard, the process will **permanently hang** during | ||
| finalization! | ||
|
|
||
|
ZeroIntensity marked this conversation as resolved.
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. c:function:: PyInterpreterGuard *PyInterpreterGuard_FromCurrent(void) | ||
|
|
||
| Create a finalization guard for the current interpreter. This will prevent | ||
| finalization from occuring until the guard is closed. | ||
|
ZeroIntensity marked this conversation as resolved.
Outdated
|
||
|
|
||
| For example: | ||
|
|
||
| .. code-block:: c | ||
|
|
||
| // Temporarily prevent finalization. | ||
| PyInterpreterGuard *guard = PyInterpreterGuard_FromCurrent(); | ||
| if (guard == NULL) { | ||
| // Finalization has already started or we're out of memory. | ||
| return NULL; | ||
| } | ||
|
|
||
| Py_BEGIN_ALLOW_THREADS; | ||
| // Do some critical processing here. For example, we can safely acquire | ||
| // locks that might be acquired by the finalization thread. | ||
| Py_END_ALLOW_THREADS; | ||
|
|
||
| // Now that we're done with our critical processing, the interpreter is | ||
| // allowed to finalize again. | ||
| PyInterpreterGuard_Close(guard); | ||
|
|
||
| On success, this function returns a guard for the current interpreter; | ||
| on failure, it returns ``NULL`` with an exception set. | ||
|
|
||
| This function will fail only if the current interpreter has already started | ||
| finalizing, or if the process is out of memory. | ||
|
|
||
| The guard pointer returned by this function must be eventually closed | ||
| with :c:func:`PyInterpreterGuard_Close`; failing to do so will result in | ||
| the Python process infinitely hanging. | ||
|
|
||
| The caller must hold an :term:`attached thread state`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. c:function:: PyInterpreterGuard *PyInterpreterGuard_FromView(PyInterpreterView *view) | ||
|
|
||
| Create a finalization guard for an interpreter through a view. | ||
|
|
||
| On success, this function returns a guard to the interpreter | ||
| represented by *view*. The view is still valid after calling this | ||
| function. The guard must eventually be closed with | ||
| :c:func:`PyInterpreterGuard_Close`. | ||
|
|
||
| If the interpreter no longer exists, is already finalizing, or out of memory, | ||
| then this function returns ``NULL`` without setting an exception. | ||
|
|
||
| The caller does not need to hold an :term:`attached thread state`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. c:function:: void PyInterpreterGuard_Close(PyInterpreterGuard *guard) | ||
|
|
||
| Close an interpreter guard, allowing the interpreter to start | ||
| finalization if no other guards remain. If an interpreter guard | ||
| is never closed, the interpreter will infinitely wait when trying | ||
| to enter finalization! | ||
|
|
||
| After an interpreter guard is closed, it may not be used in | ||
| :c:func:`PyThreadState_Ensure`. Doing so will result in undefined | ||
| behavior. | ||
|
|
||
| Currently, this function will deallocate *guard*, but this may change in | ||
| the future. | ||
|
|
||
| This function cannot fail, and the caller doesn't need to hold an | ||
| :term:`attached thread state`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. _interpreter-views: | ||
|
|
||
| Interpreter views | ||
| ----------------- | ||
|
|
||
| In some cases, it may be necessary to access an interpreter that may have been | ||
| deleted. This can be done using interpreter views. | ||
|
|
||
| .. c:type:: PyInterpreterView | ||
|
|
||
| An opaque view of an interpreter. | ||
|
|
||
| This is a thread-safe way to access an interpreter that may have be | ||
| finalizing or already destroyed. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. c:function:: PyInterpreterView *PyInterpreterView_FromCurrent(void) | ||
|
|
||
| Create a view to the current interpreter. | ||
|
|
||
| This function is generally meant to be used alongside | ||
| :c:func:`PyInterpreterGuard_FromView` or :c:func:`PyThreadState_EnsureFromView`. | ||
|
|
||
| On success, this function returns a view to the current interpreter; on | ||
| failure, it returns ``NULL`` with an exception set. | ||
|
|
||
| The caller must hold an :term:`attached thread state`. | ||
|
|
||
| .. versionadded:: next | ||
|
|
||
|
|
||
| .. c:function:: void PyInterpreterView_Close(PyInterpreterView *view) | ||
|
|
||
| Close an interpreter view. | ||
|
|
||
| If an interpreter view is never closed, the view's memory will never be | ||
| freed, but there are no other consequences. (In contrast, forgetting to | ||
| close a guard will infinitely hang the main thread during finalization.) | ||
|
|
||
| Currently, this function will deallocate *view*, but this may change in | ||
| the future. | ||
|
|
||
|
encukou marked this conversation as resolved.
Outdated
|
||
| This function cannot fail, and the caller doesn't need to hold an | ||
| :term:`attached thread state`. | ||
|
|
||
|
|
||
| .. c:function:: PyInterpreterView *PyInterpreterView_FromMain() | ||
|
ZeroIntensity marked this conversation as resolved.
Outdated
|
||
|
|
||
| Create a view for the main interpreter (the first and default | ||
| interpreter in a Python process; see | ||
| :c:func:`PyInterpreterState_Main`). | ||
|
|
||
| On success, this function returns a view to the main | ||
| interpreter; on failure, it returns ``NULL`` without an exception set. | ||
| Failure indicates that the process is out of memory or that the main | ||
| interpreter has finalized (or never existed). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks inaccurate: it only fails on OOM and returns a valid view when the interpreter is finalized or not yet initalized. (Found by Claude) |
||
|
|
||
| Generally speaking, using this function is strongly discouraged, because | ||
| it typically compromises subinterpreter support for a program. It exists | ||
|
ZeroIntensity marked this conversation as resolved.
Outdated
|
||
| for exceptional cases where there is no other option (such as when a native | ||
| threading library doesn't provide a ``void *arg`` parameter that could be | ||
| used to store a ``PyInterpreterGuard`` or ``PyInterpreterView`` pointer). | ||
|
|
||
| The caller does not need to hold an :term:`attached thread state`. | ||
|
|
||
|
|
||
| Process-wide parameters | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.