gh-126907: Lock the atexit state on the free-threaded build#126908
gh-126907: Lock the atexit state on the free-threaded build#126908ZeroIntensity wants to merge 21 commits intopython:mainfrom
atexit state on the free-threaded build#126908Conversation
picnixz
left a comment
There was a problem hiding this comment.
Quick review while waiting for my train
vstinner
left a comment
There was a problem hiding this comment.
Overall, IMO the approach is valid. Here is a first review.
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Victor Stinner <vstinner@python.org>
vstinner
left a comment
There was a problem hiding this comment.
LGTM.
@colesbury @mpage: Do you want want to review this change?
kumaraditya303
left a comment
There was a problem hiding this comment.
I think it's easy to introduce reentrancy issues in this code, how about using a critical section instead of mutex?
colesbury
left a comment
There was a problem hiding this comment.
Thanks for taking this on Peter. Like @kumaraditya303 suggested, I think using critical sections will simplify the code and avoid some re-entrancy issues. Having to explicitly unlock the mutex inside some functions is a sign that the code should be refactored:
The low-level callbacks (ll_callbacks) and high-level Python callbacks are pretty different:
- The
PyMutexfor modifyingll_callbacksis fine - Use a critical section for protecting
state->callbacks. Ideally, the acquisition happens at the module boundaries. Use argument clinic and@critical_sectionfor the module methods. - I suspect a lot of the code would be simplified if
state->callbackswere a Python list object
| _PyAtExit_LOCK(state); | ||
| if (state->callbacks[i] == NULL) |
There was a problem hiding this comment.
The explicit unlocking means that this still has re-entrancy and concurrency issues if the callback list is modified during the comparison. callbacks[i] may be a different callback.
Using critical sections doesn't completely avoid the problem if __eq__ is particularly weird, but it avoids it for the common case of identity comparison and the issues are limited to the cases where the GIL-enabled build has issues as well.
|
Ok, I'll switch this over to critical sections. For now, I'd rather just ignore
It could, but it also might be a PITA because we'd need to introduce capsules into the mix. I'll play around with it and see how it looks. |
I don't think you need capsules, which I agree would be a pain. I think you can use a tuple of three items instead of Most of what The Python callbacks are executed while other threads are still running (and can possibly modify the callbacks). Using a PyListObject makes it easier to make a local copy of the callbacks before iterating over them, which avoids some re-entrancy and thread-safety issues in |
|
To be clear, I am only suggesting a PyListObject instead of |
|
I was just thinking about using a list because it has a nicer interface, not for thread safety.
They are? That's not good. The two places that execute atexit callbacks (apart from manually doing it) are Line 2040 in 5121685 Line 2440 in 5121685 Both of which are supposed to happen when no threads are left. I guess daemon and C threads could screw things up, but I'm willing to write that off as a "play stupid games, win stupid prizes." |
|
Marking as |
|
You might create a new PR if you switch to critical sections, so we can compare the two approaches, and the PR history is easier to follow. |
|
Closing now that #127935 got merged. Thanks to everyone that reviewed! |
atexitfrom threads in free-threading build segfaults #126907