Tokio isn't aborting tasks

Hi,

I'm trying to implement a timeout for async tasks in tokio and for some reason it just isn't working. Is there something missing in the code? Is this a bug?

The test implementation should just:

  • spawn a concurrent task with 2 concurrent tasks within
    • long_task takes 10 seconds
    • short_task takes 5 seconds
    • each task print out their name and current time every second then sleeps with an await
    • a "race" is started with tokio::select!
    • short_task should finish first and abort long_task while it sleeps
  • This concurrent task is awaited and should end after the tokio::select!
  • The main thread waits a little longer after the concurrent task ends to ensure its sub tasks aren't printing anymore
Code
use std::time::Duration;

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async move {
        let timeout_handle = tokio::spawn(timout_task());

        let wait_20_handle = tokio::spawn(long_task());
        let abort_handle_long = timeout_handle.abort_handle();

        // Race to the first one that's finished and cancel the loser
        tokio::select! {
                _ = timeout_handle => {
                    println!("Timeout of task");
                    abort_handle_long.abort();
                }
                _ = wait_20_handle => {
                    println!("Completed task wait_20");
                }
            }
    });

    handle.await.unwrap();

    println!("Waiting extra seconds");
    tokio::time::sleep(Duration::from_secs(10)).await
}

async fn timout_task(){
    for second in 1..=5 {
        println!("wait short: Waited for {}", second);
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}
async fn long_task(){
    for second in 1..=10 {
        println!("wait long: Waited for {}", second);
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
    println!("Shouldn't print this!");
}

Rust playground

Expected result:

wait long: Waited for 1
wait short: Waited for 1
wait short: Waited for 2
wait long: Waited for 2
wait long: Waited for 3
wait short: Waited for 3
wait short: Waited for 4
wait long: Waited for 4
wait short: Waited for 5
wait long: Waited for 5
wait long: Waited for 6
Timeout of task
Waiting extra seconds

Actual result

wait long: Waited for 1
wait short: Waited for 1
wait short: Waited for 2
wait long: Waited for 2
wait long: Waited for 3
wait short: Waited for 3
wait short: Waited for 4
wait long: Waited for 4
wait short: Waited for 5
wait long: Waited for 5
wait long: Waited for 6
Timeout of task
Waiting extra seconds
wait long: Waited for 7
wait long: Waited for 8
wait long: Waited for 9
wait long: Waited for 10
Shouldn't print this!

Notes

The doc says

When tasks are shut down, it will stop running at whichever .await it has yielded at. All local variables are destroyed by running their destructor. Once shutdown has completed, awaiting the JoinHandle will fail with a cancelled error.

Further on what "yielding" is

When one of these methods are called, the task is signalled to shut down next time it yields at an .await point.

So I'm expecting it to cancel at the tokio::time::sleep(...).await call.

Weirdly enough, aborting a single spawn doesn't pose any problems

Code and output
use tokio::time::{sleep, Duration};

#[tokio::main]
async fn main() {
    let handle = tokio::spawn(async {
        println!("Task started.");
        sleep(Duration::from_secs(5)).await;
        println!("This will not be printed if aborted.");
    });

    tokio::time::sleep(Duration::from_secs(1)).await;
    handle.abort();
    
    sleep(Duration::from_secs(8)).await;

    println!("Task aborted.");
}

Output

Task started.
Task aborted.

you are selecting the wrong handles

Your abort handle is for the wrong task:

        let timeout_handle = tokio::spawn(timout_task());

        let wait_20_handle = tokio::spawn(long_task());
-       let abort_handle_long = timeout_handle.abort_handle();
+       let abort_handle_long = wait_20_handle.abort_handle();

Playground.

2 Likes

Godammit :joy: I stared at the code so long and even asked friends to look at it. My rubberduck was of no help either.

Thanks!

3 Likes