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

Error when using local_future_into_py or local_future_into_py_with_locals #71

Open
Tom-the-Bomb opened this issue Aug 17, 2022 · 9 comments

Comments

@Tom-the-Bomb
Copy link

Tom-the-Bomb commented Aug 17, 2022

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

@Cryptex-github
Copy link

Cryptex-github commented Aug 17, 2022

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 #59

@awestlake87
Copy link
Owner

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

@Tom-the-Bomb
Copy link
Author

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

@awestlake87
Copy link
Owner

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.

@Tom-the-Bomb
Copy link
Author

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:
image

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:
image

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.

@Tom-the-Bomb
Copy link
Author

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.

@awestlake87
Copy link
Owner

awestlake87 commented Aug 20, 2022

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 Arcs 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.

@Tom-the-Bomb
Copy link
Author

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!

@awestlake87
Copy link
Owner

awestlake87 commented Aug 21, 2022

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;

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

No branches or pull requests

3 participants