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

Introduce #[pyclass(unsendable)] #1009

Merged
merged 2 commits into from Jun 30, 2020
Merged

Conversation

kngwyu
Copy link
Member

@kngwyu kngwyu commented Jun 29, 2020

Now PyClass requires Send, but what should we do when we really don't want it sent to another thread?
Then we can use #[pyclass(unsendable)].
Inspired by send-wrapper, a class with unsendable panics when it is accessed by another thread.

#[pyclass(unsendable)]
struct Unsendable { ... }
unsendable = Unsendable()
def func():
    return unsendable.value()
import threading
# func is executed in anohter thread, thus causes panic
threading.Thread.start(target=func)

To enable this, I introduced the PyClassSend trait.

pub trait PyClassSend {
    type ThreadChecker: PyClassThreadChecker;
}

Without unsendable, we use ThreadCheckerStub as a ThreadChecker, which requires Send. Thus not unsendable class has to be Sendable.

impl pyo3::pyclass::PyClassSend for Class {
    type ThreadChecker = pyo3::pyclass::ThreadCheckerStub<Class>;
}
  • Documentation

@programmerjake
Copy link
Contributor

PyClassSend should probably be an unsafe trait, since if the wrong type is used in the impl, a !Send object can be accessed from a different thread.

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

@programmerjake
Good catch, thanks.

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

@programmerjake

PyClassSend should probably be an unsafe trait, since if the wrong type is used in the impl, a !Send object can be accessed from a different thread.

I think it's safe in that outer class cannot implement PyClassThreadChecker because it requires fn __private__(&self) -> crate::internal_tricks::PrivateMarker. And our all internal PyClassThreadCheckers are safe.

@kngwyu kngwyu force-pushed the pyclass-unsendable branch 2 times, most recently from 8addc6b to 94eb102 Compare June 29, 2020 05:57
@davidhewitt
Copy link
Member

I think the problem is that this "safe" code is effectively equivalent to unsafe impl Send:

impl pyo3::pyclass::PyClassSend for Unsendable {
    type ThreadChecker = pyo3::pyclass::ThreadCheckerStub<Sendable>;
}

I'm using Sendable's no-op thread checker to make Unsendable also have a no-op checker.

@birkenfeld
Copy link
Member

Why does the stub have the type argument btw? (For the "real" checker it's used to format the panic message)

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

I think the problem is that this "safe" code is effectively equivalent to unsafe impl Send:

Ah, I understand, thanks. Then it have to be unsafe.

Why does the stub have the type argument btw?

For checking T: Send. There can be a better way, though.

@davidhewitt
Copy link
Member

Having read through this implementation, I think it's very elegant. I had reservations about whether this was necessary for the complexity it adds. But the implementation is rather neat and should be zero-cost for sendable pyclasses after optimisations, so I have warmed to this idea.

For checking T: Send. There can be a better way, though.

You might be interested in this example for quote_spanned which asserts a class is Sync. It might be possible to do similar here as part of the #[pyclass] proc_macro for Send.

https://docs.rs/quote/1.0.7/quote/macro.quote_spanned.html#example

We could then just skip generating the check when unsendable is set.

Though I think doing that trick might mean that users who are manually implementing PyClass (if they really wanted to) would not have any check ensuring their type is Send.

@davidhewitt
Copy link
Member

For the documentation: I'm thinking that there's a new chapter I'd like to write explaining with better examples the GIL, threading, Send and PyAny vs Py<> / PyObject in just a bit more detail.

So if you want, instead of writing the documentation in this PR, open an issue once this PR is merged and assign it to me.

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

For the documentation: I'm thinking that there's a new chapter I'd like to write explaining with better examples the GIL, threading, Send and PyAny vs Py<> / PyObject in just a bit more detail.

Well, it would be good
But anyway I think we need a short document on migration.md and I'll push it.

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

I changed the trait declaration to

pub trait PyClassSend {
    type ThreadChecker: PyClassThreadChecker<Self>;
}

for inhibiting invalid ThreadChecker.

@davidhewitt
Copy link
Member

davidhewitt commented Jun 29, 2020

I think the PyClassSend trait still needs to be unsafe.

I think this would still be possible for a user to write:

impl PyClassThreadChecker<Unsendable> for ThreadCheckerStub<()> { ... }

And then the "safe" code above can just use ThreadCheckerStub<()> to skip the thread checks for the Unsendable struct.

@kngwyu
Copy link
Member Author

kngwyu commented Jun 29, 2020

I think this would still be possible for a user to write:

It's prevented by private_decl!. So we can say that it's practically safe, though I'm not sure if it's a good way to ensure safety.

Copy link
Member

@konstin konstin left a comment

Choose a reason for hiding this comment

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

This is great, thank you! 💯

@davidhewitt
Copy link
Member

It's prevented by private_decl!. So we can say that it's practically safe, though I'm not sure if it's a good way to ensure safety.

Very true. Thanks, I'm satisfied!

@kngwyu kngwyu merged commit b2e7acd into PyO3:master Jun 30, 2020
@kngwyu kngwyu deleted the pyclass-unsendable branch June 30, 2020 05:44
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.

None yet

5 participants