lost.sof
lost.sof

Reputation: 261

why not panic when task timeout?

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

Answers (1)

Cerberus
Cerberus

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.
  • If the future wakes up, tokio polls it again. If it returns Poll::Ready - again, timer is thrown away, future succeeds.
  • If the timer wakes up, 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();
}

Playground

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

Related Questions