-
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: support async fn
in macros with coroutine implementation
#3540
Conversation
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.
Thank you! I think this will need to go through several cycles of review as I get my head around it, so please bear with me 🙏
Just as a process note, I would be glad if we could aim for at least two maintainer reviews here as this appears to be a fundamental expansion of the scope of PyO3. Of course, not necessarily mine, just more than one to increase the bus factor just a little bit. |
} | ||
} | ||
|
||
pub(crate) fn iter_result(result: IterNextOutput<PyObject, PyObject>) -> PyResult<PyObject> { |
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.
This will make our handling of iterators even more of a mess, won't it? So this is basically what we want to do with all iterators but it would apply only to coroutines? Maybe we should align all of this if we go ahead?
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 if this not clear, but this is mainly directed at @davidhewitt, not so much at @wyfo.)
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 honestly think __next__
should allow to return PyResult<PyObject>
in addition to PyResult<IterNextOutput<PyObject, PyObject>>
or PyResult<Option<PyObject>>
.
It would remove the need for this small utilitary function.
But I did want to touch too much parts of PyO3 in this first draft.
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 updated the PR with the feedbacks. I've also completely refactored the waker to lazily initialize the Python future (so there is no object allocated when the future is already ready), but I've used Moreover, I've added a method to release the GIL while polling the future (it was the first request when I released pyo3-async). Obviously, I will need to work on test now, especially with the unsafe code added (if you want to keep it). |
Since you acquire the GIL anyway, did you consider our own |
(Otherwise, I would prefer an initial safe version using |
I've considered it, but it had no reinitialization methods, and this unsafe algorithm was the first thing that came into my mind. I've added a |
New version with I've tried the |
CodSpeed Performance ReportMerging #3540 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.
Thank you for the continued iteration here, I'm feeling relatively optimistic that we can get to async fn
support from this approach. That said, it's definitely an expansion in PyO3's complexity so I agree we'll need a few maintainer passes, and I'd prefer we merged functionality in baby steps where we can be confident in each piece. Sorry I didn't manage to keep up with the threads from the last few days.
I've been thinking a bit about how we make sure that this functionality is a solid foundation which the rest of the ecosystem can build on.
I think that pyo3-asyncio
might be somewhat compatible with its existing TaskLocals
and other helpers, however I guess it might want to return a custom object instead of our Coroutine
. (I haven't thought particularly hard about this.)
Similarly I guess the way that users could support other async runtimes is by supplying their own Coroutine
implementation?
From that, it seems to me like we might want to have pyo3(coroutine_type = X)
as an option on async functions to switch the returned concrete type. Plus maybe a global api pyo3::set_coroutine_type::<X>()
to do this once for the whole extension module.
However I think really we don't need to replace the whole coroutine type, just the waker? (After all, all async def
functions in Python return the same coroutine type...)
} | ||
} | ||
|
||
pub(crate) fn iter_result(result: IterNextOutput<PyObject, PyObject>) -> PyResult<PyObject> { |
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.
pyo3-asyncio is completely different, as
Do you talk about other Python async runtime? Indeed, it's about coroutine implementation, and more particularly the waker (the current coroutine code would still require some adaptations to make it generic, but nothing complicated). |
Yes, I agree that mostly this PR will not change things for Having at least a fork or branch of pyo3-asyncio with some examples using this |
New version here, with a rework of the cancellation handling! Now I'm quite satisfied with it (but I had to replace I've also updated macros to take in account @davidhewitt I took the liberty of resolving some of your conversations, but one remains open, I would like to have your opinion about #3540 (comment) P.S. I've rebased onto main to use #3556. |
I've still added a last commit to this PR with a small improvement I had forgotten in my previous list: panic handling while polling the future, in order to properly close the coroutine (not closing it led to surprising side effect when the coroutine was dropped with the not-awaited warning feature) |
I need to update the code because of #3578 |
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 very much, new docs look good. Just two final points to finish off.
Also, there's a lot of commits in this PR history now. We like to try to keep the history tidy, but GH merge queue sadly doesn't give us the option to squash. Can you please squash manually into one commit when you fixup the docs? (Or several commits if desired.)
guide/src/async-await.md
Outdated
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 add this page to the doctests
in lib.rs
(and the examples may need to have some adjustments to compile).
Other than that, this doc looks good to me 👍
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.
Yep I was aware about the draft chapters, but I think just slightly better to not have them at all until we're ready to have them.
Thanks for the many revisions here, I think many users will be very excited to have this!
I didn't know about emscripten, but it seems that asyncio is not supported on this target. Should I add some |
Hmm good question. I believe that pyodide might support asyncio, in which case we could potentially benefit from testing that. For now how about marking the tests which fail with |
@@ -0,0 +1,98 @@ | |||
#![cfg(feature = "macros")] | |||
#![cfg(not(target_arch = "wasm32"))] |
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.
@davidhewitt I've added this line to deal with emscripten for now. Is it ok for you? Can I let you open the issue? I don't really know anything about wasm.
It's not my area of expertise also, though I've had a couple of rounds of understanding it for PyO3's sake. I've added it to #2582 |
Hmm, guide entry isn't MSRV compatible. I'm sort of okay with that, however that does create potential distribution problems for users who follow the guide. Is there a futures alternative we can pull in as a dev dependency? |
Actually, the failing example is the workaround for |
🥳 🍾 |
Fixes #1632Here is a draft for the implementation of asyncio coroutine and async fn support. It lacks exhaustive documentation and test, the goal is at first to review the implementation.