asynchronousrustrust-tokiosea-orm

How to run async function in sync in tokio runtime?


I would like to initialize SeaOrm database connection in sync for easier usage inside application and testing, but not sure how to do it without error. I'm fine with any type of blocking as this happens only once. For testing #[tokio::test] is used.

In current example I get following 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.

#[tokio::main]
async fn main() {
    let database_service = DatabaseService::init();
}

pub struct DatabaseService {
    pub connection: DatabaseConnection,
}
impl DatabaseService {
    pub fn init() -> Self {
        let connection_options = ConnectOptions::new(&API_ENV.db_url);

        // tokio::runtime::Runtime::new() also gives similar error
        let connection = Handle::try_current()
            .expect("Can't get tokio runtime")
            .block_on(async { sea_orm::SqlxPostgresConnector::connect(connection_options).await })
            .expect("Can't connect to database");

        Self { connection }
    }
}

This is working with additional deps, but hangs in tests #[tokio::test] probably due to this:


// futures = "0.3.31"
// futures-executor = "0.3.31"


     let connection = futures::executor::block_on(sea_orm::SqlxPostgresConnector::connect(
            connection_options,
        ))
        .expect("Can't connect to database");

Solution

  • The only coherent way to block while executing code within a tokio executor thread, but not able to use await, is to call tokio::task::block_in_place(). This function will inform the executor that the thread is going to block, so that other work can be moved off the thread.

    However, I strongly recommend that you not use this just for “easier usage inside application”. Even though block_in_place() is better than running a blocking operation without notifying Tokio about it, it can still have surprising behavior and you should not make your application more brittle just to feel more convenient to write. In software engineering, that’s a bad tradeoff that has a good chance of biting you later.

    You should use spawn_blocking().await for blocking operations, and use await for async operations. Use block_in_place() only when it is impossible to have an await because you are writing code inside a non-async function, yet you are running code on an async executor thread anyway. That should be very rare and special circumstances.