Skip to content

gh-131185: Use a proper thread-local for cached thread states #132510

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

Merged
merged 27 commits into from
May 21, 2025

Conversation

ZeroIntensity
Copy link
Member

@ZeroIntensity ZeroIntensity commented Apr 14, 2025

Switches over to a _Py_thread_local in place of autoTssKey, and also fixes a few other checks regarding PyGILState_Ensure after finalization.

Note that this doesn't fix concurrent use of PyGILState_Ensure with Py_Finalize; I'm pretty sure zapthreads doesn't work at all, and that needs to be fixed seperately.

Copy link
Contributor

@colesbury colesbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core changes to pystate.c look good to me. Some comments below.

Python/pystate.c Outdated
}
#endif
//---------------------------------------------
// the GIL state bound to the current OS thread
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The previous comment here made more sense to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but it wasn't really correct if it was referring to the gilstate. I went with your suggestion below and referred to it as a "thread state used by PyGILState_Ensure()," is that better?

@ZeroIntensity ZeroIntensity requested a review from colesbury April 18, 2025 15:28
Comment on lines +2695 to +2697
if (!_PyEval_ThreadsInitialized() || runtime->gilstate.autoInterpreterState == NULL) {
PyThread_hang_thread();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look right to me. There's no synchronization here with the thread finalizing the process. Additionally, it has time-of-check to time-of-use issues because runtime->gilstate.autoInterpreterState is reloaded below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm planning on fixing this in a follow-up. For now, I'm ignoring cases where threads concurrently call PyGILState_Ensure while the interpreter is finalizing, because there's more to fix than just the gilstate pointer.

For example, zapthreads in PyInterpreterState_Delete performs UAF when there are non-main threads, because it accesses the next of the thread state it just freed. I think there are probably more cases like this. Anyways, for this PR, I'd like to focus on calling PyGILState_Ensure after the interpreter is long dead. Is that alright?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, that's fine. Can you leave a comment here about the limitations?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@colesbury colesbury left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@ericsnowcurrently - would you like to review this as well?

@ZeroIntensity
Copy link
Member Author

Bump @ericsnowcurrently @colesbury, I'd like to get all the finalization races fixed before the beta freeze, and we're running out of time. We need this one as a first step.

@ericsnowcurrently
Copy link
Member

I haven't had time to follow this, unfortunately. I'll take a look as soon as I can. In the meantime, what's the motivation for the change?

@ZeroIntensity
Copy link
Member Author

It fixes a crash with PyGILState_Ensure when the interpreter is finalized, but it has the added benefit of just being a simpler implementation.

@ericsnowcurrently
Copy link
Member

I do think we should first make a change that does the how is-finalizing dance (likely with a lock). The switch to a thread-local, while nice, should be orthogonal to actually fixing the bug. (We probably don't need to backport the thread-local change.)

@kumaraditya303
Copy link
Contributor

I do think we should first make a change that does the how is-finalizing dance (likely with a lock). The switch to a thread-local, while nice, should be orthogonal to actually fixing the bug. (We probably don't need to backport the thread-local change.)

I don't think we should backport this change, this code is old so this isn't a new bug. I believe having different implementation for this across branches isn't worth it, it can introduce new bugs. How about keeping this change for 3.15 only?

@ZeroIntensity
Copy link
Member Author

@ericsnowcurrently Do you have any opposition to landing this on main? #134307 will be used for 3.14. I'd like to get this one merged so I don't have to deal with conflicts.

@ericsnowcurrently
Copy link
Member

Nope. Thanks for taking care of this. I'll merge it.

@ericsnowcurrently ericsnowcurrently merged commit b8998fe into python:main May 21, 2025
39 checks passed
@ZeroIntensity ZeroIntensity deleted the gilstate-thread-local branch May 21, 2025 13:02
lkollar pushed a commit to lkollar/cpython that referenced this pull request May 26, 2025
…ythongh-132510)

Switches over to a _Py_thread_local in place of autoTssKey, and also fixes a few other checks regarding PyGILState_Ensure after finalization.

Note that this doesn't fix concurrent use of PyGILState_Ensure with Py_Finalize; I'm pretty sure zapthreads doesn't work at all, and that needs to be fixed seperately.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants