Why a future polled twice, when execute inside `tokio::time::timeout`?

In this example below, Foo future polled multiple times, Its is inside tokio::time::timeout block.

Playground

struct Foo(bool);
impl Future for Foo {
    type Output = ();
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        println!("poll: {:?}", cx);
        if self.0 { return Poll::Ready(()); }
        self.get_mut().0 = true;
        Poll::Pending
    }
}
#[tokio::main]
async fn main() {
    timeout(Duration::from_secs(1), async {
        println!("=====================");
        Foo(false).await;
        println!("=====================");
    }).await.unwrap();
}

As we can saw, Foo future polled twice. What's going on inside timeout?

=====================
poll: Context { waker: Waker { data: 0x557c09b25490, vtable: 0x557c07bf54b0 } }
poll: Context { waker: Waker { data: 0x557c09b25490, vtable: 0x557c07bf54b0 } }
=====================

The Timeout future is also polled twice - once right when it is created, and then again 1 second later when the timeout is reached. Timeout internally always polls the inner future before checking the timeout: tokio/timeout.rs at master · tokio-rs/tokio · GitHub.

1 Like

But what is the reason ?

As this create problem. Because Foo future should be in pending state. unless polled with condition (manually).

It is always allowed to poll something too many times. The timeout future does that.

2 Likes

Hi @alice, Is there any way to prevent polling many times?

You could include your own check that ignores the poll if you don't want it? Besides that, no.

In general, a future is supposed to behave properly if polled too often.

1 Like

Futures work by having a async runtime poll them as many times as it wants. It may even use busy waiting by polling it as many times as it wants. As optimization however a so called waker has been introduced. As soon as a future is ready it has to notify this waker, which tells the runtime to poll the future again. It may also notify the waker if it isn't ready yet, though this will waste resources. It is in the general case not possible to prevent spurious polls. An example where spurious polls are impossible to prevent is tokio::join!. This macro allows waiting on the completion of multiple futures. All futures share the same waker, so at every poll when any future is ready, all futures have to be polled to find out which one is ready. As for tokio::time::timeout specifically, it basically works by doing tokio::join! on the specified future and a timer (the actual implementation is slightly different, but this is the gist). The specified future comes first, so it gets polled every time the timeout gets polled, even if the timer has expired.

2 Likes

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.