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

calling futures::executor::block_on in block_in_place may hang #2603

Closed
leoyvens opened this issue Jun 10, 2020 · 14 comments
Closed

calling futures::executor::block_on in block_in_place may hang #2603

leoyvens opened this issue Jun 10, 2020 · 14 comments
Labels
A-tokio Area: The main tokio crate C-question User questions that are neither feature requests nor bug reports M-task Module: tokio/task

Comments

@leoyvens
Copy link

Version
0.2.21

Platform
Linux

Description
I'm basically having the problem in this issue #2376, which is to use block_on within a tokio task, and sometimes it hangs. This comment #2376 (comment) says using spawn_blocking fixes it. I figured block_in_place should also work, but it didn't. A guess for why is that when using block_in_place the thread local variable for the task budget continues to decrease, when it should stop counting. Possibly as was done for LocalSet.

@leoyvens leoyvens added A-tokio Area: The main tokio crate C-bug Category: This is a bug. labels Jun 10, 2020
@Darksonn Darksonn added C-question User questions that are neither feature requests nor bug reports M-task Module: tokio/task and removed C-bug Category: This is a bug. labels Jun 10, 2020
@Darksonn
Copy link
Contributor

Use Handle::block_on instead of futures::executor::block_on.

@leoyvens
Copy link
Author

@Darksonn thank you for pointing me to that alternative, I hadn't considered it.

But is it not a potential bug that the task budget keeps counting within block_in_place?

@That3Percent
Copy link

That3Percent commented Jun 10, 2020

I feel like there is room for improvement here. There are a lot of blocking options... futures::executor::block_on, Handle::block_on, spawn_blocking, and block_in_place, just to name a few.
Deciding when it's appropriate to call one over another requires global knowledge of everything happening both up and down the stack, how the runtime and scheduler have been configured, and intimate familiarity with subtle implementation details to avoid panics, deadlocks, timeouts that don't fire, and the like.
Unfortunately, I don't have any suggestions for a magic API that Just Works, so sorry for ranting. But, I'm not sure how to write code that is robust in this context either.

@carllerche
Copy link
Member

Calling futures::executor::block_on from block_in_place should be fine. If it isn't, then something else is going on.

@carllerche
Copy link
Member

Generally, the answer is to not use block_in_place.

Besides that, you only really need to pick between Handle::block_on and spawn_blocking. One you call from outside of the runtime and one you call while inside the runtime.

@Darksonn
Copy link
Contributor

Darksonn commented Jun 10, 2020

Re: @That3Percent

Unless you are doing something complicated, you shouldn't be using anything but Tokio's block_on and spawn_blocking functions. Mixing in another executor such as futures::executor::block_on is a bad idea, and preferably you shouldn't use it at all.

Tokio's block_on and spawn_blocking do very different things. The block_on method transitions from sync code into async, whereas spawn_blocking transitions from async code into sync. Both functions should be used exactly at the border when you transition from one world to another, and trying to make the transition deeper in the callstack than the boundary wont work because spawn_blocking must be awaited.

The block_in_place method is a low-level method that most people shouldn't need to know or care about. It is like spawn_blocking, but allows you to make the async → sync transition deeper in the callstack. Unfortunately this cannot be implemented in a good way, and this makes it a big footgun. Unlike the two other methods, you should know what you are doing when using it.

The global knowledge thing you talk about is indicative of either not using spawn_blocking at the boundary from async → sync code at all, or trying to make the transition deeper in the callstack than at the border.

Unfortunately there are no ways to lint or otherwise detect missing spawn_blocking around blocking code at compile time. The problems rarely come from using the wrong method with block in the name — they come from not using any at all. (and when they do come from using one of them, it's pretty much always our footgun block_in_place)

@nlfiedler
Copy link

If the code in question is deeper in the call stack, what should I do? Calling futures::executor::block_on() just happens to work, but that sounds risky given the comments here and the related issue.

I've got a small crate inside a larger application, and that larger application is using actix-web (and hence tokio) to serve web and GraphQL content. This smaller crate is thoroughly synchronous. It is one of several similar crates, so changing everything to asynchronous is a big change. This small crate is calling a library function that returns a Future that I need to do something with in the of middle my synchronous function so I can return a result. How do I wait on this future without writing my own executor?

I'm sure this is a stupid question, and I apologize for that. I've spent several hours reading documentation and trying everything I could think of, short of hand-rolling my own executor. Should I give up and change the entire application to be asynchronous everywhere so I can call this one function?

Thanks for tokio by the way, the docs are well written and the API is rather extensive.

@Darksonn
Copy link
Contributor

Darksonn commented Jul 21, 2020

@nlfiedler You should make use of the fact that calling block_on inside calls to spawn_blocking is perfectly okay. The spawn_blocking function has to be called in async code, so you should add it at your outer transition from async to sync code. You can't do it once you are deep in the synchronous callstack.

I recommend using our Handle::block_on instead of the block_on from futures. Any code running inside spawn_blocking is in Tokio's context, so you can simply ask for a handle when you need it with Handle::current.

Note that if your synchronous code does anything else blocking such as using std IO types, then you must use spawn_blocking for that reason too.

@nlfiedler
Copy link

Thank you for the explanation, that confirms what I was afraid of. This application started two years ago, long before async/await was stabilized. The async aspects of the application consist of the #[actix_rt::main] on the main function (which I believe is equivalent to #[tokio::main]), and the Future that is now coming back from the library (rusoto), which was a recent change in their API. I'm stuck between a rock and a hard place, looking for an escape hatch, even if only temporarily.

@Darksonn
Copy link
Contributor

You could consider using block_in_place as an escape hatch, but I generally do not recommend it as there are several situations where it doesn't work (e.g. single-threaded executor and the main function).

@nlfiedler
Copy link

Yes, I saw that, and it is helpful for a situation other than mine. I need to realize the results of the Future from code that is synchronous, deep within the call stack of a thread managed by tokio.

Anyway, I've derailed this issue long enough. Thanks for your help.

@Darksonn
Copy link
Contributor

You can use block_in_place for that. You simply put a block_on inside it.

@nlfiedler
Copy link

Yep, tried that -- you get "runtime inside a runtime" error.

@carllerche
Copy link
Member

Try again w/ the latest release? #2711 might have fixed it.

nacknime-official added a commit to nacknime-official/digital-asset-rpc-infrastructure that referenced this issue Sep 13, 2023
…or::block_on`

Because `futures::executor::block_on` sometimes did deadlocks and it's
another async executor, so it's not recommended to mix multiple
executors at the same time.
tokio-rs/tokio#2603 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-tokio Area: The main tokio crate C-question User questions that are neither feature requests nor bug reports M-task Module: tokio/task
Projects
None yet
Development

No branches or pull requests

5 participants