Skip to content

require #[pyclass] to be Sync#4566

Merged
davidhewitt merged 15 commits into
PyO3:mainfrom
davidhewitt:pyclass-sync
Nov 5, 2024
Merged

require #[pyclass] to be Sync#4566
davidhewitt merged 15 commits into
PyO3:mainfrom
davidhewitt:pyclass-sync

Conversation

@davidhewitt

Copy link
Copy Markdown
Member

For #4265

A brief attempt to add a compile error to require #[pyclass] values to be Sync. I allow this constraint to be opted-out with the existing #[pyclass(unsendable)] option.

Mostly here I've focused on implementing the compile error with a reasonable diagnostic, I think there's a lot of documentation to add too.

@ngoldbaum ngoldbaum mentioned this pull request Sep 20, 2024
3 tasks
@ngoldbaum

ngoldbaum commented Oct 29, 2024

Copy link
Copy Markdown
Contributor

I rebased this and am looking at resolving the test failures.

The first thing I hit was test_getter_setter::cell_getter_setter, which is mostly testing that IntoPyObject for Cell<i32> works correctly. I think in order to get to a similar level of ergonomics for atomics and mutexes, we'll need to add IntoPyObject impls for all the atomic types and maybe for Mutex and RWLock? Otherwise you run into errors like

error[E0599]: no method named `into_pyobject` found for struct `AtomicI32` in the current scope
   --> tests/test_getter_setter.rs:232:42
    |
232 |         let cell = AtomicI32::new(20i32).into_pyobject(py).unwrap();
    |                                          ^^^^^^^^^^^^^ method not found in `AtomicI32`

Let me know if anyone thinks alternate approaches would be better.

@ngoldbaum

Copy link
Copy Markdown
Contributor

Although that said, this might also be a separate issue:

error[E0277]: the trait bound `AtomicI32: PyFunctionArgument<'_, '_>` is not satisfied
   --> tests/test_getter_setter.rs:219:1
    |
219 | #[pyclass]
    | ^^^^^^^^^^ the trait `Clone` is not implemented for `AtomicI32`, which is required by `AtomicI32: PyFunctionArgument<'_, '_>`

@ngoldbaum

Copy link
Copy Markdown
Contributor

The latest push switches members that use Cell for interior mutability or have Box members to atomics and mutexes. Now the only remaining issue with the cargo tests is the problems raised by test_getter_setter described above.

@codspeed-hq

codspeed-hq Bot commented Oct 29, 2024

Copy link
Copy Markdown

CodSpeed Performance Report

Merging #4566 will not alter performance

Comparing davidhewitt:pyclass-sync (ba09a08) with main (9f955e4)

Summary

✅ 83 untouched benchmarks

@ngoldbaum

Copy link
Copy Markdown
Contributor

If I add an IntoPyObject impl for AtomicI32 I still get the error about a missing Clone impl. However based on this answer to a similar question, I'm now thinking that the real issue is the generated get and set methods should be taking references to the inner value. I'm currently trying to understand how the get and set descriptors actually get set up inside the pyo3 proc macro, but it's pretty complicated.

@ngoldbaum

Copy link
Copy Markdown
Contributor

This ultimately comes down to the implementation of the PyClassGetterGenerator class. It delegates in this case to a function called pyo3_get_value_into_pyobject which has a Clone bound on the FieldT parameter. Since atomics aren't Clone, this leads to the compilation error I'm seeing.

I think a way forward is to expand this machinery to recognize fields that are atomics (maybe via a macro to generate all the necessary code for each atomic type?).

I'm going to stop here because this is getting pretty far afield and someone with more rust expertise might be able to give an alternate path forward.

@Icxolu

Icxolu commented Oct 29, 2024

Copy link
Copy Markdown
Member

Can you try implementing IntoPyObject for &AtomicI32? I think this should fix the error. #4449 has the list of priorities for pyo3(get).

@davidhewitt

Copy link
Copy Markdown
Member Author

I think the problem will be what kind of atomic ordering should we use? I guess Relaxed might be enough here.

@ngoldbaum

Copy link
Copy Markdown
Contributor

Can you try implementing IntoPyObject for &AtomicI32

I tried adding impls for both and am still getting the same error, see last push. I get the same error if I do the impl for either of or both &AtomicI32 and Atomic32.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I get the same error if I do the impl for either of or both &AtomicI32 and Atomic32.

Actually this is not the same error. We fixed the issue in the GetterGenerator but now there's a different issue in the argument extraction.

@davidhewitt

Copy link
Copy Markdown
Member Author

Actually, I had a better idea; let's not worry about atomics in this PR and instead just add unsendable designator to this class for now.

@davidhewitt

Copy link
Copy Markdown
Member Author

Opened #4668 for the atomics question. For now @ngoldbaum I force-pushed here.

I think there is copy that needs to be added to the migration guide and also maybe class.md where we talk about Send, we should probably now also talk about Sync. I can try to have a push at getting the docs done tonight.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I think there is copy that needs to be added to the migration guide and also maybe class.md where we talk about Send, we should probably now also talk about Sync. I can try to have a push at getting the docs done tonight.

Thanks for taking this on! I added a WIP changelog entry that should link to this. Should the changelog entry give a brief explanation for why we're doing this?

@ngoldbaum

Copy link
Copy Markdown
Contributor

Woohoo, tests are green! Just needs docs I think. I may try to write something if David doesn't get to it.

@mejrs mejrs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like the stderr output; it's a lot of text. Is there a way to reduce that?

Comment thread src/coroutine.rs
Comment on lines +38 to +40
// Safety: `Coroutine` is allowed to be `Sync` even though the future is not,
// because the future is polled with `&mut self` receiver
unsafe impl Sync for Coroutine {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this the same logic that Exclusive uses? If so, I would prefer that we use that (or our own version of it) rather than this impl.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I marked this ready for review, but I think this hasn't been looked at yet. I'll leave that to David since I'm not sure if using Exclusive makes sense.

Comment thread src/impl_/pyclass/assertions.rs Outdated
diagnostic::on_unimplemented(
message = "the trait `Sync` is not implemented for `{Self}`",
label = "needs to implement `Sync` to be `#[pyclass]`",
note = "to opt-out of threading support, use `#[pyclass(unsendable)]`",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd prefer "replace non-Sync things by Sync things" - not sure on the wording for that.

Comment thread src/impl_/pyclass/assertions.rs Outdated
diagnostic_namespace,
diagnostic::on_unimplemented(
message = "the trait `Sync` is not implemented for `{Self}`",
label = "needs to implement `Sync` to be `#[pyclass]`",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd like the tone to be more rustc-y. Also, I feel like "needs to implement Sync" could be understood as "I need to unsafe impl Sync for MyClass" - we should avoid that.

Suggested change
label = "needs to implement `Sync` to be `#[pyclass]`",
note = "required by `#[pyclass]`",

@ngoldbaum

ngoldbaum commented Oct 31, 2024

Copy link
Copy Markdown
Contributor

Is there a way to reduce that?

I think the most straightforward way to get a shorter error message is to mark the pyclass itself with on_unimplemented for Sync. But according to the RFC for diagnostic::on_unimplemented adding this on types is a "future possibility", and I think it's not currently possible.

Perhaps it's possible to refactor this so that there's a little less indirection and maybe get rid of one of the frames in the error message? I don't immediately see how.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I tried refactoring this to avoid the long error and couldn't figure it out. I hoped that if I made PyClassSync have a blanket impl for Sync that I might be able to generate the diagnostic but it looks like the compiler ignores PyClassSync in that case, so the default generics trickery used here or something like it does seem to be necessary to get on_unimplemented to trigger.

@ngoldbaum

Copy link
Copy Markdown
Contributor

I think the big to-do here is docs. @davidhewitt would you like me to take a look at that sometime this week or do you still want to take a crack at it yourself?

@davidhewitt

Copy link
Copy Markdown
Member Author

I hope to make various progress on release prep & final pass over docs tomorrow, but feel free to push here if you have time.

Otherwise maybe I'll merge this PR as is and we can have one or more docs follow ups to round off the release 😁

Comment thread examples/decorator/src/lib.rs Outdated
@@ -10,7 +10,7 @@ pub struct PyCounter {
// Keeps track of how many calls have gone through.
//
// See the discussion at the end for why `Cell` is used.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you please update (or maybe delete) this line and the discussion it references? I'm fine with docs being added in followup PRs but it's bad form to leave docs in a wrong (rather than just incomplete or missing) state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Done, thanks for pointing out that I missed that.

@ngoldbaum ngoldbaum marked this pull request as ready for review November 5, 2024 16:42

@davidhewitt davidhewitt left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Super, thanks. Let's merge this and then we can parallelise finishing work on docs for 0.23 👍

@davidhewitt davidhewitt added this pull request to the merge queue Nov 5, 2024
Merged via the queue into PyO3:main with commit 1befe1d Nov 5, 2024
@davidhewitt davidhewitt deleted the pyclass-sync branch November 5, 2024 20:59
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