time: fix wake-up with interval on Ready (#5553)

When `tokio::time::Interval::poll_tick()` returns `Poll::Pending`, it
schedules itself for being woken up again through the waker of the
passed context, which is correct behavior.

However when `Poll::Ready(_)` is returned, the interval timer should be
reset but not scheduled to be woken up again as this is up to the
caller.

This commit fixes the bug by introducing a `reset_without_reregister`
method on `TimerEntry` which is called by `Intervall::poll_tick(cx)` in
case the delay poll returns `Poll::Ready(_)`.

Co-authored-by: Simon B. Gasse <[email protected]>
This commit is contained in:
sgasse
2023-03-27 17:39:53 +02:00
committed by GitHub
co-authored by Simon B. Gasse
parent 822af18cf5
commit 68b02db154
5 changed files with 142 additions and 12 deletions
+115 -3
View File
@@ -1,10 +1,12 @@
#![warn(rust_2018_idioms)]
#![cfg(feature = "full")]
use tokio::time::{self, Duration, Instant, MissedTickBehavior};
use tokio_test::{assert_pending, assert_ready_eq, task};
use std::pin::Pin;
use std::task::{Context, Poll};
use std::task::Poll;
use futures::{Stream, StreamExt};
use tokio::time::{self, Duration, Instant, Interval, MissedTickBehavior};
use tokio_test::{assert_pending, assert_ready_eq, task};
// Takes the `Interval` task, `start` variable, and optional time deltas
// For each time delta, it polls the `Interval` and asserts that the result is
@@ -209,3 +211,113 @@ fn poll_next(interval: &mut task::Spawn<time::Interval>) -> Poll<Instant> {
fn ms(n: u64) -> Duration {
Duration::from_millis(n)
}
/// Helper struct to test the [tokio::time::Interval::poll_tick()] method.
///
/// `poll_tick()` should register the waker in the context only if it returns
/// `Poll::Pending`, not when returning `Poll::Ready`. This struct contains an
/// interval timer and counts up on every tick when used as stream. When the
/// counter is a multiple of four, it yields the current counter value.
/// Depending on the value for `wake_on_pending`, it will reschedule itself when
/// it returns `Poll::Pending` or not. When used with `wake_on_pending=false`,
/// we expect that the stream stalls because the timer will **not** reschedule
/// the next wake-up itself once it returned `Poll::Ready`.
struct IntervalStreamer {
counter: u32,
timer: Interval,
wake_on_pending: bool,
}
impl Stream for IntervalStreamer {
type Item = u32;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = Pin::into_inner(self);
if this.counter > 12 {
return Poll::Ready(None);
}
match this.timer.poll_tick(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(_) => {
this.counter += 1;
if this.counter % 4 == 0 {
Poll::Ready(Some(this.counter))
} else {
if this.wake_on_pending {
// Schedule this task for wake-up
cx.waker().wake_by_ref();
}
Poll::Pending
}
}
}
}
}
#[tokio::test(start_paused = true)]
async fn stream_with_interval_poll_tick_self_waking() {
let stream = IntervalStreamer {
counter: 0,
timer: tokio::time::interval(tokio::time::Duration::from_millis(10)),
wake_on_pending: true,
};
let (res_tx, mut res_rx) = tokio::sync::mpsc::channel(12);
// Wrap task in timeout so that it will finish eventually even if the stream
// stalls.
tokio::spawn(tokio::time::timeout(
tokio::time::Duration::from_millis(150),
async move {
tokio::pin!(stream);
while let Some(item) = stream.next().await {
res_tx.send(item).await.ok();
}
},
));
let mut items = Vec::with_capacity(3);
while let Some(result) = res_rx.recv().await {
items.push(result);
}
// We expect the stream to yield normally and thus three items.
assert_eq!(items, vec![4, 8, 12]);
}
#[tokio::test(start_paused = true)]
async fn stream_with_interval_poll_tick_no_waking() {
let stream = IntervalStreamer {
counter: 0,
timer: tokio::time::interval(tokio::time::Duration::from_millis(10)),
wake_on_pending: false,
};
let (res_tx, mut res_rx) = tokio::sync::mpsc::channel(12);
// Wrap task in timeout so that it will finish eventually even if the stream
// stalls.
tokio::spawn(tokio::time::timeout(
tokio::time::Duration::from_millis(150),
async move {
tokio::pin!(stream);
while let Some(item) = stream.next().await {
res_tx.send(item).await.ok();
}
},
));
let mut items = Vec::with_capacity(0);
while let Some(result) = res_rx.recv().await {
items.push(result);
}
// We expect the stream to stall because it does not reschedule itself on
// `Poll::Pending` and neither does [tokio::time::Interval] reschedule the
// task when returning `Poll::Ready`.
assert_eq!(items, vec![]);
}