-
Notifications
You must be signed in to change notification settings - Fork 784
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
feat: add coroutine::CancelHandle
#3599
Conversation
CodSpeed Performance ReportMerging #3599 will improve performances by 22.03%Comparing Summary
Benchmarks breakdown
|
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.
Thanks, I haven't reviewed the full implementation in detail yet because I think there's a little bit of discussion we could have around the design.
First off, there's two alternatives I can think of and I wonder if they're any good.
The more similar alternative would be to have the cancel handle annotation be on the argument:
#[pyfunction]
async fn cancellable_sleep(seconds: f64, #[pyo3(cancel_handle)] mut cancel: CancelHandle) -> usize {
futures::select! {
_ = sleep(seconds).fuse() => 42,
_ = cancel.cancelled().fuse() => 0,
}
}
and another alternative could be to have a function which returns a future which only resolves if the current coroutine is cancelled:
async fn cancellable_sleep(seconds: f64) -> usize {
futures::select! {
_ = sleep(seconds).fuse() => 42,
_ = pyo3::coroutine_is_cancelled().fuse() => 0,
}
}
I think this would be possible by putting the throw
object into the waker and then this future could retrieve it from the waker.
I quite like this for not needing to pass the cancel: CancelHandle
argument, but it does feel a bit "global state"-y, which may have unintended footguns.
There's then another question I have in the back of my mind, which is how this interacts with PyFuture
. What happens if that inner Python awaitable gets cancelled? It would be quite nice for this cancel handle to also deal with that case, but I don't think it will in this design. Is there a way we can unify these?
I like argument annotation, and I prefer it actually.
|
Me too, it's more localised. I think the error message when the annotation is forgotten will be the same "
Would this change in the future with the functionality available on nightly? It might be ok for this future to always just be pending if not in a coroutine context.
Can you please explain in a little bit more detail why this is the case?
👍 - ok let's worry about |
Yes, normally.
I prefer panicking here, because I think it's less error prone.
The default behavior is to reraise the error when |
c5cb1b5
to
170d1e6
Compare
34e25c5
to
d66be8b
Compare
guide/src/async-await.md
Outdated
use pyo3::coroutine::CancelHandle; | ||
|
||
#[pyfunction] | ||
async fn cancellable(#[pyo3(cancel_handle)]mut cancel: CancelHandle) { |
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.
Space missing after the annotation.
@@ -44,6 +45,7 @@ impl<'a> FnArg<'a> { | |||
other => return Err(handle_argument_error(other)), | |||
}; | |||
|
|||
let is_cancel_handle = arg_attrs.cancel_handle.is_some(); |
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.
Please leave an empty line after this statement.
src/coroutine.rs
Outdated
self.close(); | ||
return Err(PyErr::from_value(exc.as_ref(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.
Maybe write this out as (None, _)
just to make it more obvious.
7eac478
to
aa8ef40
Compare
aa8ef40
to
75eaaff
Compare
Thank you a lot for the review. I've added a last test for |
75eaaff
to
bc8fc72
Compare
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.
Sorry for not managing to review this until just now. All looks great in my eyes, thanks @adamreichold for being so thorough as always!
I just had one tiny suggestion, which may not be a great idea depending on compiler output...
|
||
#[pyfunction] | ||
fn cancel_handle_synchronous(#[pyo3(cancel_handle)] _param: String) {} | ||
|
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.
It might be nice to add tests to see what goes on in these cases:
#[pyfunction] | |
fn cancel_handle_wrong_type(#[pyo3(cancel_handle)] _param: String) {} | |
#[pyfunction] | |
fn missing_cancel_handle_attribute(_param: pyo3::coroutine::CancelHandle) {} |
(I expect will just be type errors, but how hard will it be for the user to see what they did wrong)
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've added your test cases. I let you judge about the compiler output.
bc8fc72
to
8a674c2
Compare
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 compiler output is as painful as I expected, but anyway let's move forward with this one. Can always find ways to improve later. 👍
Relates to #1632
See #3540 (comment) for the reason behind
CancelHandle
implementation.