Axum async handlers

I've just found I don't fully understand async, at least in the context of axum.

Say I have a handler which just loops and does nothing:

pub async fn status_handler() -> String {
 
 loop{}

 String::from("unreachable")

} 

I thout that the loop would block the executor pool, and no other axum requests would be served, as the pool is stuck in the loop. In other words: The task will be holding the cpu until it .awaits something, or the function exits.

Either my assumption is incorrect, or is something else happening under the hood?

Thanks!

In my understandig it should just block the one thead of the thread pool in which the task is currently processed. all other thread should execute tasks normally.

I made a small test project, with the following dependencies:

axum = "0.7.4"
tokio = { version = "1.36.0", features = ["full"] }
use axum::response::IntoResponse;
use axum::{routing::get, Router};

async fn blocking_handler() {
    println!("Blocking thead id: {:?}", std::thread::current().id());
    loop {}
}

async fn hello_world() -> impl IntoResponse {
    println!("Generic thread: {:?}", std::thread::current().id());
    "Hello World"
}

async fn goodbye_world() -> impl IntoResponse {
    println!("Generic thread 2: {:?}", std::thread::current().id());
    "Goodbye World"
}

#[tokio::main]
async fn main() {
    // build our application with a route
    let app = Router::new()
        .route("/", get(hello_world))
        .route("/1", get(goodbye_world))
        .route("/blocking", get(blocking_handler));

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3331").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Output:

Blocking thead id: ThreadId(17)
Generic thread: ThreadId(15)
Generic thread 2: ThreadId(2)
Generic thread: ThreadId(2)
Generic thread 2: ThreadId(2)
Generic thread 2: ThreadId(2)
Generic thread 2: ThreadId(2)

Though it seems if a call the blocking endpoint not at first, the processing is locked indeed:

Generic thread: ThreadId(17)
Generic thread: ThreadId(2)
Generic thread 2: ThreadId(2)
Blocking thead id: ThreadId(2)
< -- no further logs here -->

That may be because it seems like the scheduler is putting all work loads only to thread 2.

1 Like

I guess the important point here is that tokio runs with rt-multi-thread feature, so tokio uses threads (if available by the OS) to schedule tasks. I'd say if tokio was compiled to run with a single thread, the first request to the blocking handler will get the whole process stuck.

Interesting, thanks!

1 Like

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.