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 secondsshort_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 abortlong_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!");
}
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.