pyo3-asyncio
pyo3-asyncio copied to clipboard
Error when using `local_future_into_py` or `local_future_into_py_with_locals`
when using local_future_into_py
/ local_future_into_py_with_locals
and then when I try to call the python functions this gets raised
pyo3_runtime.PanicException: `spawn_local` called from outside of a `task::LocalSet`
I was using local_future_into_py
at first and got such error and a friend recommended me to try local_future_into_py_with_locals
instead but I did such with no change in output.
code here: https://github.com/Tom-the-Bomb/akinator.py
thanks in advance
You are not using that function inside a LocalSet, which is what caused that panic. Here is the example from pyo3-asyncio
https://docs.rs/pyo3-asyncio/latest/pyo3_asyncio/tokio/fn.local_future_into_py.html#examples
Although that example only demonstrates how you await a local future in a main function in rust, not from python. So I am not very sure on how you should do that either.
I think this is related to https://github.com/awestlake87/pyo3-asyncio/issues/59
Thanks @Cryptex-github, you're correct that the lack of a LocalSet is what's causing this to panic in Tokio. I've always found Tokio's !Send
functionality to be a bit convoluted (although there might be a design decision behind that), so I usually opt for async-std when handling !Send
futures if I can help it. async-std doesn't require a LocalSet, spawn_local
just has to be called from a thread that is running async-std.
There is some question around whether these conversions are useful or not. I was considering deprecating them, so I'd be interested to hear more about your use-case to see if they still provide some value.
The main reason they're a bit problematic is because they can't really be called from Python unless the Python is running on a Rust executor. The Python executor and Rust executors live on separate threads, so !Send
is incompatible with coroutines that are running on asyncio
. This could be solved with a shared Python/Rust executor, but that's currently not an option with pyo3-asyncio
alright thanks for the response, so basically at the moment there is no good solution or workaround for such? other than not having !Send
futures.
I currently switched to a sync implementation but I would still like to make an async one if possible in the future
There's usually a way to make !Send
futures work with Send
code, but the workaround depends a lot on the situation. If you're able to provide more info or an example of the problem I might have some ideas on how you could get around it.
Hmm alright so:
originally the functions I intended to call in such future were all Send
but I ran into an issue with lifetimes on the self reference:
changing the lifetime to a 'static
brings up another error so that did not work either.
I was suggested to try to use an Arc
on self.0
but then this comes up:
so I tried that with a RwLock which fixed everything but brings us to this problem as RwLock is !Send
, so i was forced to use local_future_into_py
which then results in the above Exception.
woop, sorry I think I made a mistake on my end, my bad. tokio's RwLock's read/write guards are not !Send
so I think it will work perfectly, just have to test a bit.
Your problem looks very similar to #50 since you're trying to use a borrowed self
inside a future that you pass on to the library. You might read through that thread, my comment here in particular might point you in the right direction.
Sounds like putting the data in an Arc
might be the solution for you, but Arc
s hold immutable data. In order to allow for mutability, you need to use something like an Arc<Mutex<T>>
so you can lock it before using it. This will get you around any lifetime errors with the borrowed self
because your future can use a cloned Arc
of the internal data, and the Mutex
should get you around those errors about assigning stuff to an arc.
I can't see the full type of your self.0
so I can't be 100% certain, but it sounds like you might have ran into an issue with lock guards being !Send
, but that shouldn't be a problem as long as you remember to drop the lock guard before the next await
boundary.
ah yea so I ended up going with Arc<RwLock<T>>
where RwLock is from tokio::sync
I think the read and write locks there do implement Send
so it worked
but just to be sure what do you exactly mean with the last statement? Sorry I am quite new to this and rust and all so.
but it sounds like you might have ran into an issue with lock guards being !Send, but that shouldn't be a problem as long as you remember to drop the lock guard before the next await boundary.
here's the code I have currently
it works with a simple test but just making sure I haven't done anything wrong or missed anything that could result in a hidden problem.
Thanks!
Async mutexes work too, so I think you're good! And they don't have the restriction that I mentioned. Some (all?) synchronous mutexes do though, including std::sync::Mutex
.
In your case it looks like the async mutex is appropriate since the operations that you want to call on Akinator are async themselves. Sometimes you need to use a mutex in both sync and async contexts, so it's nice to be able to use a sync mutex in those cases. What I mentioned was this situation
let mut guard = mutex.lock().unwrap();
operation.await; // lock guard can't be held across this await (error might mention MutexGuard being !Send)
guard.value = 1;
Instead you can ensure that the guard is dropped before the next await
{
let mut guard = mutex.lock().unwrap();
guard.value = 1;
}
operation.await;