Skip to content
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 PyClassBorrowChecker thread safe #4544

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

ngoldbaum
Copy link
Contributor

Ref #4265 (comment) and replies from @alex.

I tried doing this with an AtomicUsize but because try_borrow accepts an immutable reference, I couldn't figure out a way to get that to work without keeping the Cell. A mutex seemed like a more natural choice for the existing code structure.

I think in principle we could use a mutex on the GIL-enabled build as well, since the lock is only held very briefly in rust to update the borrow checker state.

Is the new test that only triggers on the free-threaded build OK? I could also write it to test that there isn't an exception on the GIL-enabled build.

@alex
Copy link
Contributor

alex commented Sep 10, 2024

Hmm, not sure I follow why taking an & reference should be a problem -- all of the relevant AtomicUsize methods are also &self?

@ngoldbaum
Copy link
Contributor Author

Hmm, not sure I follow why taking an & reference should be a problem

I realized today that if I make BorrowFlag mutable, then I also need to make increment and decrement take &self instead of self. I was confused by the error the borrow checker was giving me and thought it indicated a fundamental problem. Thanks for pointing out my reasoning was flawed.

BorrowFlag::UNUSED,
BorrowFlag::HAS_MUTABLE_BORROW,
Ordering::SeqCst,
Ordering::SeqCst,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I conservatively chose SeqCst for the ordering of all operations, I'm not sure if a different ordering is OK.

Copy link

codspeed-hq bot commented Sep 11, 2024

CodSpeed Performance Report

Merging #4544 will not alter performance

Comparing ngoldbaum:pyclass-borrow-checker (d92e733) with main (a32afdd)

Summary

✅ 81 untouched benchmarks

@ngoldbaum
Copy link
Contributor Author

Ping @colesbury - I'd appreciate a code review from you if you have some spare cycles.

Comment on lines 74 to 76
Err(..) => {
// value changed under us, need to reload and try again
let value = self.0.load(Ordering::Relaxed);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to reload? Isn't the value in Err(..) what we want?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, either way: the let here causes value to be rebound, which means that teh compare_exchange is always comparing to the original value, which I don't think is what you want.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks! much clearer this way without the second load.


fn __next__(&mut self) -> usize {
let should_wait = self.count == 0;
self.count += 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

reminder to myself this should use rust atomics

@mejrs
Copy link
Member

mejrs commented Sep 17, 2024

I feel quite strongly that we shouldn't merge this PR. The semantics in #4265 (comment) make much more sense to me.

@davidhewitt
Copy link
Member

davidhewitt commented Sep 18, 2024

@mejrs I'm very much in agreement with you that keeping these "refcell-like" semantics on the freethreaded build is likely to be completely unusable in real-world conditions.

Would you be prepared to accept this PR merging in 0.23 with the understanding that we will follow up in 0.24 with something akin to #4265 (comment), i.e. a more realistic design.

I think this allows us to make PyO3 sound for downstream testing of free-threading without needing to introduce breaking changes (of which there are already enough collected for this release).

@alex
Copy link
Contributor

alex commented Sep 18, 2024

I disagree strongly that these are unusable in real-world situations. I did a review of pyca/cryptography and these semantics would be appropriate for all of our usage of non-frozen pyclasses.

The reason is that while these types require mutable access, there's no particularly coherent behavior for concurrent multi-threaded usage. These types are, for example, iterators and hashers. There's no real world usage for concurrent mutable use.

@mejrs
Copy link
Member

mejrs commented Sep 18, 2024

Would you be prepared to accept this PR merging in 0.23 with the understanding that we will follow up in 0.24 with something akin to #4265 (comment), i.e. a more realistic design.

I'm OK with that.

I disagree strongly that these are unusable in real-world situations.

They may work in your situation, but not in general. Imagine a List-like pyclass with a fn push(&mut self, value) method. Most of the time that is going to work in a multi threaded context, but sometimes it will trip a borrow error and crash the program. It sounds like a footgun that everyone is going to run into at some point.

@ngoldbaum ngoldbaum mentioned this pull request Sep 20, 2024
3 tasks
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