-
Notifications
You must be signed in to change notification settings - Fork 49
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
Await !Send
Rust futures in Python
#59
Comments
Unfortunately, I don't think it's possible without a Maybe we can get something figured out for your use-case though. If you provide some more info about what you're trying to do I might be able to give you some pointers on how to get around it. Sometimes you can circumvent this issue by first spawning a
Thanks, I appreciate it! |
Basically I want to do exactly what I showed here: #[pyfunction]
fn make_awaitable(py: Python) -> PyResult<&PyAny> {
pyo3_asyncio::tokio::future_into_py(py, async {
something_async().await;
Ok(())
})
} just with a
I was trying to spawn a struct Fut(Rc<u32>);
impl Future for Fut {
type Output = u32;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
todo!()
}
}
#[pyfunction]
fn nonsend_fut(py: Python) -> PyResult<&PyAny> {
future_into_py(py, async move {
let rt = Builder::new_current_thread().enable_all().build().unwrap();
let set = LocalSet::new();
let fut = async move {
let fut = Fut(Rc::new(0));
Ok(fut.await)
};
rt.block_on(set.run_until(fut))
})
} Which seems to deadlock however. It seems to me that creating a |
How is your project set up? Are you using |
I'm not using the |
Ok, if you're using the current thread scheduler, does that mean that you're initializing tokio and spawning a thread for it somewhere in your native extension? |
Well, I also have a binary that uses the library, and I do that in the binary. But I hoped that for the extension I could just use the global I suppose that spawning a thread which would contain a single threaded-runtime and then communicating with it using e.g. |
I had an idea about runtime initialization, but then I remembered I had done something very similar in one of my tests: Try something like this: #[pyfunction]
fn return_3(py: Python) -> PyResult<&PyAny> {
pyo3_asyncio::tokio::future_into_py(py, async move {
let result = tokio::task::spawn_blocking(|| {
let data = Rc::new(3i32);
tokio::task::LocalSet::new().block_on(pyo3_asyncio::tokio::get_runtime(), async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
println!("using local data {}", *data);
*data
})
});
Ok(result.await.unwrap())
})
} Instead of creating a new runtime, you create a blocking task on the tokio runtime to run the localset. It's not a workaround I've tested very well so I don't know how many local tasks you can run concurrently like this. Let me know if this works for you! |
Right, this is conceptually similar to the "separate thread + single threaded runtime + queue" approach, but it's better if it's handled by tokio, of course. Originally, I hoped to avoid something like this, but now I realized that it can be actually quite useful. If you use a single-threaded runtime and Of course, an even better solution would be if Python and Rust event loops knew about each other and I can't test it right now, but I'll experiment with it over the weekend and let you know if it works. Thank you very much for the suggestion! |
Great, this solution seems to work! For some reason it deadlocks with a single-threaded runtime, maybe it does not support Maybe this approach could be put into the documentation, because currently if someone has a |
That's strange. I didn't see any deadlocks when I tried it with the current thread scheduler. I didn't restrict the number of blocking threads in tokio though, so maybe that's why? It does make me wonder why tokio doesn't support
Yeah it probably should. Honestly this is making me rethink having those conversions to begin with because as you say, they can really only be used from the Rust side, which pretty much defeats the purpose. Maybe just documenting the spawn blocking workaround is good enough. |
@TkTech opened the original issue that led to those conversions being created. I'd be curious to know if they still have a use-case for these conversions that wasn't mentioned here or if they have similar issues with these conversions. If they're of the same opinion, I might just deprecate these conversions. |
Ultimately, I ended up moving away from pyo3-asyncio. I created my own event loop shims by implementing |
@TkTech Is your solution open-sourced? I think that it might be useful for other people (myself included!). |
Sounds very similar to what @ThibaultLemaire was working on in #6. Might be worth taking a second look at it! |
That sounds a bit like what I implemented, yes. Here's the code for your convenience (to spare you the long github discussion). In a nutshell, the idea is to write a wrapper that behaves like a Python awaitable/task but is able to drive a Rust future. I eventually dropped the project because staying off the Python thread was much faster and I couldn't think of a situation where I cannot guarantee |
Not yet, but it will be. It's part of a tool that allows users to provide their own scripts to interact with events on IRC (a rewrite of https://github.com/TkTech/notifico). It's very simplistic, and takes advantage of the fact that |
I don't have any context on this thread, but tokio-rs/tokio#3370 merged and is released in 1.16.0. |
@AzureMarker thanks! Looks like that would be a cleaner replacement for the |
Oh, I just realized the spawn_pinned changes didn't release with 1.16.0 since they're in |
Hi! I want to use
pyo3-asyncio
to convert Rust futures into Python awaitables that can then be awaited in Python. ForSend
futures, this is quite easy, e.g.:However, when the Rust Future is not
Send
, this gets problematic. I know that there is thelocal_future_into_py
function, which is demonstrated here. However, to actually await that future, the context needs to be inside aLocalSet
.Is it possible to create a Python awaitable from a Rust
!Send
future and then await it in Python (and not in Rust usingLocalSet
)? My idea would be to use a global single-threaded tokio runtime and run everything inside a globalLocalSet
, but I'm not sure if that's possible currently.Thank you for a great library btw! :) Even if it wouldn't support this use case, it's very useful.
The text was updated successfully, but these errors were encountered: