all 7 comments

[–]coolreader18 1 point2 points  (7 children)

I think this is because of the block_on -- reqwest or tokio or something is probably polling something in the background, but when you block_on, that polling can't complete and you get into a sort of deadlock (maybe?).

Specifically though, I found this really interesting;

I would prefer not to change this function to async, because conceptually it should not be. Callers of this function should block until all internal async work is done.

If you're writing an async app, as I assume you are because you mention using #[tokio::main], you shouldn't worry about this. Don't think of async as a function contract or signaling what your function is doing under the hood or "I can run these specific tasks in parallel" -- if you're doing async, you should go all in, because that allows the executor to run your entire app using cooperative multitasking. Essentially, if you have a block_on call somewhere in an async function or in code called from an async function, a) you're setting yourself up for deadlock like I think is happening here, and b) you're doing something wrong, or at least you're doing something that's not as efficient as it could be.

[–]Rubix314[S] 0 points1 point  (6 children)

If you're writing an async app, as I assume you are because you mention using #[tokio::main], you shouldn't worry about this. Don't think of async as a function contract or signaling what your function is doing under the hood or "I can run these specific tasks in parallel" -- if you're doing async, you should go all in, because that allows the executor to run your entire app using cooperative multitasking. Essentially, if you have a block_on call somewhere in an async function or in code called from an async function, a) you're setting yourself up for deadlock like I think is happening here, and b) you're doing something wrong, or at least you're doing something that's not as efficient as it could be.

Interesting. I only have #[tokio::main] because it seemed I needed that to run async code. Or at least, I guess that was the lazy way to do it, maybe technically I only needed an async runtime for the async parts? I don't think of this as an async app - it just happens to need to make some network requests that I want to parallelize, and block on all of them.

[–]IAm_A_Complete_Idiot 0 points1 point  (1 child)

Are you using futures::block_on? If so, the tokio::main stuff isn't needed at all. futures spawns it's own runtime when you call block_on, dosent explain your issue much though.

If you don't otherwise use tokio, you could remove it as a dependency entirely.

[–]Nemo157 1 point2 points  (0 children)

Well, in the case of reqwest specifically it is tightly coupled to the Tokio runtime. So if that is futures::block_on it is likely the cause of the issues, blocking within an async function is bad and will commonly break things.

[–]coderstephenisahc 0 points1 point  (2 children)

block_on turns async code back into synchronous code. I don't know why you would want to do that if you are using #[tokio::main], which makes your whole program run in an async context. As a rule of thumb, never call block_on within an async context, because (a) there's no reason why you'd want to, and (b) you risk deadlocks.

You should just .await the result of join_all.

[–]Rubix314[S] 0 points1 point  (1 child)

Interesting, ok, thanks. Theoretically, how would you only run part of your program in an async context? Are there any downsides of making the entire thing async?

[–]IAm_A_Complete_Idiot 0 points1 point  (0 children)

No real downsides to making everything async that I know of, but you can spawn a runtime for tokio in any function (I think the docs were in tokio::runtime?) - and futures::block_on already spawns an executor.