Skip to content

Commit 68b02db

Browse files
authored
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 <sgasse@users.noreply.github.com>
1 parent 822af18 commit 68b02db

5 files changed

Lines changed: 142 additions & 12 deletions

File tree

tokio/src/runtime/time/entry.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -527,19 +527,21 @@ impl TimerEntry {
527527
unsafe { self.driver().clear_entry(NonNull::from(self.inner())) };
528528
}
529529

530-
pub(crate) fn reset(mut self: Pin<&mut Self>, new_time: Instant) {
530+
pub(crate) fn reset(mut self: Pin<&mut Self>, new_time: Instant, reregister: bool) {
531531
unsafe { self.as_mut().get_unchecked_mut() }.deadline = new_time;
532-
unsafe { self.as_mut().get_unchecked_mut() }.registered = true;
532+
unsafe { self.as_mut().get_unchecked_mut() }.registered = reregister;
533533

534534
let tick = self.driver().time_source().deadline_to_tick(new_time);
535535

536536
if self.inner().extend_expiration(tick).is_ok() {
537537
return;
538538
}
539539

540-
unsafe {
541-
self.driver()
542-
.reregister(&self.driver.driver().io, tick, self.inner().into());
540+
if reregister {
541+
unsafe {
542+
self.driver()
543+
.reregister(&self.driver.driver().io, tick, self.inner().into());
544+
}
543545
}
544546
}
545547

@@ -553,7 +555,7 @@ impl TimerEntry {
553555

554556
if !self.registered {
555557
let deadline = self.deadline;
556-
self.as_mut().reset(deadline);
558+
self.as_mut().reset(deadline, true);
557559
}
558560

559561
let this = unsafe { self.get_unchecked_mut() };

tokio/src/runtime/time/tests/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ fn reset_future() {
164164
.as_mut()
165165
.poll_elapsed(&mut Context::from_waker(futures::task::noop_waker_ref()));
166166

167-
entry.as_mut().reset(start + Duration::from_secs(2));
167+
entry.as_mut().reset(start + Duration::from_secs(2), true);
168168

169169
// shouldn't complete before 2s
170170
block_on(futures::future::poll_fn(|cx| {

tokio/src/time/interval.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,10 @@ impl Interval {
482482
timeout + self.period
483483
};
484484

485-
self.delay.as_mut().reset(next);
485+
// When we arrive here, the internal delay returned `Poll::Ready`.
486+
// Reset the delay but do not register it. It should be registered with
487+
// the next call to [`poll_tick`].
488+
self.delay.as_mut().reset_without_reregister(next);
486489

487490
// Return the time when we were scheduled to tick
488491
Poll::Ready(timeout)

tokio/src/time/sleep.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,22 @@ impl Sleep {
353353
self.reset_inner(deadline)
354354
}
355355

356+
/// Resets the `Sleep` instance to a new deadline without reregistering it
357+
/// to be woken up.
358+
///
359+
/// Calling this function allows changing the instant at which the `Sleep`
360+
/// future completes without having to create new associated state and
361+
/// without having it registered. This is required in e.g. the
362+
/// [crate::time::Interval] where we want to reset the internal [Sleep]
363+
/// without having it wake up the last task that polled it.
364+
pub(crate) fn reset_without_reregister(self: Pin<&mut Self>, deadline: Instant) {
365+
let mut me = self.project();
366+
me.entry.as_mut().reset(deadline, false);
367+
}
368+
356369
fn reset_inner(self: Pin<&mut Self>, deadline: Instant) {
357370
let mut me = self.project();
358-
me.entry.as_mut().reset(deadline);
371+
me.entry.as_mut().reset(deadline, true);
359372

360373
#[cfg(all(tokio_unstable, feature = "tracing"))]
361374
{

tokio/tests/time_interval.rs

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#![warn(rust_2018_idioms)]
22
#![cfg(feature = "full")]
33

4-
use tokio::time::{self, Duration, Instant, MissedTickBehavior};
5-
use tokio_test::{assert_pending, assert_ready_eq, task};
4+
use std::pin::Pin;
5+
use std::task::{Context, Poll};
66

7-
use std::task::Poll;
7+
use futures::{Stream, StreamExt};
8+
use tokio::time::{self, Duration, Instant, Interval, MissedTickBehavior};
9+
use tokio_test::{assert_pending, assert_ready_eq, task};
810

911
// Takes the `Interval` task, `start` variable, and optional time deltas
1012
// 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> {
209211
fn ms(n: u64) -> Duration {
210212
Duration::from_millis(n)
211213
}
214+
215+
/// Helper struct to test the [tokio::time::Interval::poll_tick()] method.
216+
///
217+
/// `poll_tick()` should register the waker in the context only if it returns
218+
/// `Poll::Pending`, not when returning `Poll::Ready`. This struct contains an
219+
/// interval timer and counts up on every tick when used as stream. When the
220+
/// counter is a multiple of four, it yields the current counter value.
221+
/// Depending on the value for `wake_on_pending`, it will reschedule itself when
222+
/// it returns `Poll::Pending` or not. When used with `wake_on_pending=false`,
223+
/// we expect that the stream stalls because the timer will **not** reschedule
224+
/// the next wake-up itself once it returned `Poll::Ready`.
225+
struct IntervalStreamer {
226+
counter: u32,
227+
timer: Interval,
228+
wake_on_pending: bool,
229+
}
230+
231+
impl Stream for IntervalStreamer {
232+
type Item = u32;
233+
234+
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
235+
let this = Pin::into_inner(self);
236+
237+
if this.counter > 12 {
238+
return Poll::Ready(None);
239+
}
240+
241+
match this.timer.poll_tick(cx) {
242+
Poll::Pending => Poll::Pending,
243+
Poll::Ready(_) => {
244+
this.counter += 1;
245+
if this.counter % 4 == 0 {
246+
Poll::Ready(Some(this.counter))
247+
} else {
248+
if this.wake_on_pending {
249+
// Schedule this task for wake-up
250+
cx.waker().wake_by_ref();
251+
}
252+
Poll::Pending
253+
}
254+
}
255+
}
256+
}
257+
}
258+
259+
#[tokio::test(start_paused = true)]
260+
async fn stream_with_interval_poll_tick_self_waking() {
261+
let stream = IntervalStreamer {
262+
counter: 0,
263+
timer: tokio::time::interval(tokio::time::Duration::from_millis(10)),
264+
wake_on_pending: true,
265+
};
266+
267+
let (res_tx, mut res_rx) = tokio::sync::mpsc::channel(12);
268+
269+
// Wrap task in timeout so that it will finish eventually even if the stream
270+
// stalls.
271+
tokio::spawn(tokio::time::timeout(
272+
tokio::time::Duration::from_millis(150),
273+
async move {
274+
tokio::pin!(stream);
275+
276+
while let Some(item) = stream.next().await {
277+
res_tx.send(item).await.ok();
278+
}
279+
},
280+
));
281+
282+
let mut items = Vec::with_capacity(3);
283+
while let Some(result) = res_rx.recv().await {
284+
items.push(result);
285+
}
286+
287+
// We expect the stream to yield normally and thus three items.
288+
assert_eq!(items, vec![4, 8, 12]);
289+
}
290+
291+
#[tokio::test(start_paused = true)]
292+
async fn stream_with_interval_poll_tick_no_waking() {
293+
let stream = IntervalStreamer {
294+
counter: 0,
295+
timer: tokio::time::interval(tokio::time::Duration::from_millis(10)),
296+
wake_on_pending: false,
297+
};
298+
299+
let (res_tx, mut res_rx) = tokio::sync::mpsc::channel(12);
300+
301+
// Wrap task in timeout so that it will finish eventually even if the stream
302+
// stalls.
303+
tokio::spawn(tokio::time::timeout(
304+
tokio::time::Duration::from_millis(150),
305+
async move {
306+
tokio::pin!(stream);
307+
308+
while let Some(item) = stream.next().await {
309+
res_tx.send(item).await.ok();
310+
}
311+
},
312+
));
313+
314+
let mut items = Vec::with_capacity(0);
315+
while let Some(result) = res_rx.recv().await {
316+
items.push(result);
317+
}
318+
319+
// We expect the stream to stall because it does not reschedule itself on
320+
// `Poll::Pending` and neither does [tokio::time::Interval] reschedule the
321+
// task when returning `Poll::Ready`.
322+
assert_eq!(items, vec![]);
323+
}

0 commit comments

Comments
 (0)