rust

Invoke async code in sync function blocked in Rust


I want to invoke a http rpc in Rust, but current context is sync. Then I tried like this to invoke the remote rpc:

 let uniq_id = executor::block_on(get_snowflake_id());

And this is the get_snowflake_id function:

use std::env;
use log::error;
use reqwest::Client;
use rust_wheel::model::response::api_response::ApiResponse;

pub async fn get_snowflake_id() -> Option<i64> {
    let client = Client::new();
    let infra_url = env::var("INFRA_URL").expect("INFRA_URL must be set");
    let url = format!("{}{}", infra_url, "/infra-inner/util/uniqid/gen");
    let resp = client
        .get(format!("{}", url))
        .body("{}")
        .send()
        .await;
    if let Err(e) = resp {
        error!("get id failed: {}", e);
        return None;
    }
    let text_response = resp.unwrap().text().await;
    if let Err(e) = text_response {
        error!("extract text failed: {}", e);
        return None;
    }
    let resp_str = text_response.unwrap_or_default();
    let resp_result = serde_json::from_str::<ApiResponse<i64>>(&resp_str);
    if let Err(pe) = resp_result {
        error!("parse failed: {}, response: {}", pe, &resp_str);
        return None;
    }
    Some(resp_result.unwrap().result)
}

But this code is blocked forever, am I doing the right way to invoke async code in sync context? I have to do like this because the context is sync, I can not change the current context invoke style to async. I have tried:

pub fn get_uniq_id() -> Option<i64> {
    task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(get_snowflake_id())
    })
}

This shows error can call blocking only when running on the multi-threaded runtime. This is the application entry:

#[actix_web::main]
async fn main() -> std::io::Result<()> {}

Solution

  • If your application ready uses Tokio, you should use a dedicated blocking call provided by Tokio:

    use tokio::runtime::Runtime;
    
    fn invoke_sync() -> Option<i64> {
        let rt = Runtime::new().unwrap();
        rt.block_on(get_snowflake_id())
    }
    
    use tokio::runtime::Runtime;
    use once_cell::sync::Lazy;
    
    static TOKIO_RT: Lazy<Runtime> = Lazy::new(|| {
        Runtime::new().expect("Failed to create Tokio runtime")
    });
    
    fn invoke_sync() -> Option<i64> {
        TOKIO_RT.block_on(get_snowflake_id())
    }
    

    If your application running inside a Tokio runtime, you should use Tokio's dedicated blocking mechanism:

    use tokio::task;
    
    fn invoke_sync() -> Option<i64> {
        task::block_in_place(|| {
            // This is safe if you're already within a Tokio runtime
            tokio::runtime::Handle::current().block_on(get_snowflake_id())
        })
    }
    

    Use tokio::task::block_in_place + Handle::current().block_on(...)

    Outside Tokio runtime (pure sync):

    Create and reuse a global runtime (tokio::runtime::Runtime) and call .block_on(...).

    FYI:Docs-Tokio