Reputation: 261
I set the timeout to 1s, but the task executes to 3s, but no panic occurs.
#code
#[should_panic]
fn test_timeout() {
let rt = create_runtime();
let timeout_duration = StdDuration::from_secs(1);
let sleep_duration = StdDuration::from_secs(3);
let _guard = rt.enter();
let timeout = time::timeout(timeout_duration, async {
log("timeout running");
thread::sleep(sleep_duration);
log("timeout finsihed");
"Ding!".to_string()
});
rt.block_on(timeout).unwrap();
}
Upvotes: 2
Views: 816
Reputation: 10237
Using thread::sleep
in asynchronous code is almost always wrong.
Conceptually, the timeout works like this:
tokio
spawns a timer which would wake up after the specified duration.tokio
spawns your future. If it returns Poll::Ready
, timer is thrown away and the future succeeds. If it returns Poll::Pending
, tokio
waits for the next event, i.e. for wakeup of either your future or the timer.tokio
polls it again. If it returns Poll::Ready
- again, timer is thrown away, future succeeds.tokio
polls the future one last time; if it's still Poll::Pending
, it times out and is not polled anymore, and timeout
returns an error.In your case, however, future do not return Poll::Pending
- it blocks inside the thread::sleep
. So, even though the timer could fire after one second has passed, tokio
has no way to react - it waits for the future to return, future returns only after the thread is unblocked, and, since there's no await
inside the block, it returns Poll::Ready
- so the timer isn't even checked.
To fix this, you're expected to use tokio::time::sleep
for any pauses inside async code. With it, the future times out properly. To illustrate this claim, let's see the self-contained example equivalent to your original code:
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
std::thread::sleep(sleep_duration);
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
As you've already noticed, this fails - unwrap_err
panics when called on Ok
, and timeout returns Ok
since the future didn't time out properly.
But when replacing std::thread::sleep(...)
with tokio::time::sleep(...).await
...
use core::time::Duration;
use tokio::time::timeout;
#[tokio::main]
async fn main() {
let timeout_duration = Duration::from_secs(1);
let sleep_duration = Duration::from_secs(3);
timeout(timeout_duration, async {
println!("timeout running");
tokio::time::sleep(sleep_duration).await;
println!("timeout finsihed");
"Ding!".to_string()
})
.await
.unwrap_err();
}
...we get the expected behavior - playground.
Upvotes: 5