-
Notifications
You must be signed in to change notification settings - Fork 757
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
make PyErrState
thread-safe
#4671
base: main
Are you sure you want to change the base?
Conversation
I noticed clippy was failing so I just pushed a fix. I'll try to get the CI green on this if there are any more issues. |
src/err/err_state.rs
Outdated
match self_state { | ||
Some(PyErrStateInner::Normalized(n)) => n, | ||
_ => unreachable!(), | ||
let normalized_state = PyErrStateInner::Normalized(state.normalize(py)); |
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 the only spot where there might be a deadlock is here, if normalize
somehow leads to arbitrary Python code execution.
Is that possible? If not I think it deserves a comment explaining why.
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.
If it can deadlock, I'm not sure what we can do, since at this point we haven't actually constructed any Python objects yet and we only have a handle to an FnOnce
that knows how to construct them.
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.
Great observation; I've added a wrapping call to py.allow_threads
before potentially blocking on the Once
, which I think avoids the deadlock (I pushed a test which did deadlock before that change).
The algorithm makes sense to me, I agree that this ensures that normalizing an error state can't be done simultaneously in two threads. |
CodSpeed Performance ReportMerging #4671 will degrade performances by 25.38%Comparing Summary
Benchmarks breakdown
|
Huh, I can reproduce the test failure happening on CI. It's flakey, but you can trigger it with |
.expect("Cannot normalize a PyErr while already normalizing it.") | ||
}; | ||
// avoid deadlock of `.call_once` with the GIL | ||
py.allow_threads(|| { |
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 guess somehow dropping the GIL somehow allows a race condition to happen where multiple threads try to simultaneously create a module...
|
||
#[test] | ||
fn test_no_deadlock_thread_switch() { | ||
static ERR: OnceLock<PyErr> = OnceLock::new(); |
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.
More of a style thing I guess, but I think you can avoid the static if you use std::thread::scope
This PR resolves the thread-safety challenges of #4584 for us to be able to at least ship 0.23.
I don't love the complexity that this lazy state creates inside error-handling pathways, so I think in the future I will work to proceed with #4669 and further steps to remove the lazy state. But 0.23 is already breaking enough, users don't need more changes and this should be an in-place drop-in.