-
Notifications
You must be signed in to change notification settings - Fork 886
use PyThreadState_GetUnchecked
to test if attached to the interpreter
#5350
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
Draft
davidhewitt
wants to merge
1
commit into
PyO3:main
Choose a base branch
from
davidhewitt:check-thread-state
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.
Draft
Changes from all commits
Commits
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
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
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @ZeroIntensity is this behaviour of
PyThreadState_GetUnchecked
returning non-null when inside GC traversal known? It's possible for user code to observe this in implementations oftp_traverse
handlers. (Yes, in theory they should not do anything other than visit types in those calls, but we cannot safely guarantee that!)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, what was being used previously that returned
NULL
inside the GC?As far as I know, Python has always required that
tp_traverse
is called with an attached thread state, because:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PyO3 was using an internal TLS counter (
ATTACH_COUNT
in the diff here) because we couldn't rely onPyGILState_Check
, and usingPyGILState_Ensure
was a performance drag. Looks likePyThreadState_GetUnchecked
is fast enough, and probably would be as fast as our internal TLS counter if we didn't have to do a second TLS check for GC, so it would be great to make that rule that second check unnecessary.This surprises me, because we've had several crashes reported over time when arbitrary code has been run inside
tp_traverse
, especially related toPy_DecRef
(which we have to assume arbitrary code will call). e.g. #3165, #4479. As a result we banned attaching to the interpreter while inside atp_traverse
handler.I think that is not incompatible with the thread being "detached", because GC is defined as a stop-the-world event?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok, you're right on the first part --
tp_traverse
is not allowed to call Python code. On the free-threaded build,tp_traverse
handlers are called during a stop-the-world, so it is definitely not allowed to enter the eval loop. Sorry about the confusion there!We don't have any explicit behavior for disallowing this in CPython. We just follow the usual rules; we either hold the GIL or make a stop-the-world pause so that no other thread can be attached. I think this is just a difference of the "consenting adults" approach in C vs the memory-safety approach in Rust.
I haven't looked too deep into it, but it might be safe to detach the thread state around
tp_traverse
calls, depending on what state we currently allow them to access. It's just not too useful for us to do so, because it will likely regress performance and also might break some weird-but-valid use cases (for example, callingPy_INCREF
in atp_traverse
handler, but notPy_DECREF
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I previously mentioned
Py_DECREF
as a concern it's worth pointing out that at least on the free-threaded buildPy_INCREF
is also a problem: python/cpython#123241It seems like there's some clarification needed on what is allowed operation during
tp_traverse
and the free-threaded build has made the problem worse, potentially. Until it's documented as permitted my instinct is to allow nothing.Perhaps, note that in PyO3 a user can still just do
unsafe { Python::assume_attached() }
to get "proof" that they're attached to the interpreter, but this would at least stick out as a big red flag in code review to analyze carefully.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(we're in the process of cleaning up API names, currently it's called
assume_gil_acquired
if you happen to search for this)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
Py_INCREF
should generally be allowed intp_traverse
. It's a bug that the free-threaded GC doesn't deal with it.If you're up to it, feel free to submit a PR adding some clarification to
tp_traverse
. Otherwise, I can do it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for falling off the thread a little - I'm happy to submit a PR, however I might take a little while to get around to it 👍