Tokio: `executor::block_on` freezing and `Handle::block_on` causing "Cannot start a runtime from within a runtime.."

Hey, folks!

I have a project that worked with async-std. I have a synchronous function that reads a database info from sqlx. My code is something like this:

    pub fn last_symbol_id(&self) -> Option<u32> {
        let future = sqlx::query_as("SELECT MAX(id) FROM symbol").fetch_one(&self.pool);
        futures::executor::block_on(future).unwrap()
    }

Then now I want to change to use tokio. I changed the feature of sqlx to use tokio instead async-std.

But then futures::executor::block_on is freezing with tokio. My next attempt was to use Handle::current().block_on(future).

Sadly I have the error:
"Cannot start a runtime from within a runtime. This happens because a function (like block_on) attempted to block the current thread while the thread is being used to drive asynchronous tasks."

I thought that probably Handle::current() is trying to start a new runtime in this context.
So I changed the project to get a handle of Handle::current() in the main() and passing this handle until the function. But again I got the same error.

What am I doing wrong?

Thanks in advance!

Out of curiosity, may I ask why?

You're invoking async, a machinery that provides benefits only when you need to manage many concurrent I/O bound tasks, and you use it to solve a single task, dedicating a thread for it.

The traditional way to use a thread for a single task is to do it in a synchronous way, letting the OS block the thread when it waits for instance for a reply from the database. It's faster and has less overhead because it avoids the unnecessary async machinery that is designed for a completely different scenario.

If, on the other hand, this function is part of a larger app, then make it async. This will percolate through the code, but that's the bargain you struck when committing to async, as was just recently discussed.

What you are doing wrong is trying to call block_on from the context of an async fn. It's a really bad idea. You can read why here.

To be clear, this is also problematic with async-std. It's just that async-std prefers to silently do the wrong thing, whereas Tokio prefers to inform you of the issue.

1 Like

First, thanks for the replay!

I'm a newbie, then probably I'm missing some things.

You got the point. This function is part of a larger app. I have this hard decision: turn the full app async or not. At this first time, I decide the app be non-async.

In my short experience, I had problems mixing async with traits. I'm aware that there is the async_trait crate, but I’m not sure that it is an effective solution in all cases.

Recently I knew that Rust Analyzer doesn’t use async. Matklad is a guy that I have a big respect. Armin Ronacher also said that Rust async has a bad design, arguing the use of Pin.

I really have a big fear with deal Pin and other things that I don’t know. It’s possible to develop a complete “high level” async application without using Pin or Future complexity?

I did not know that. I will have to review my application design then. Many thanks for the reply!

There's no point in using async in Rust Analyzer. Async is mainly useful for networking applications.

Yes. We've put a lot of work in to making sure that application developers need to use these as little as possible. If you do run in to trouble, there's always help available here or on the Tokio github or discord.

It's clear that I have a lot to learn and have to take a step back in my studies.
Thanks! You're my heroin!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.