Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement extra reset variants for Interval #5878

Merged
merged 1 commit into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions tokio/src/time/interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,8 @@ impl Interval {
///
/// This method ignores [`MissedTickBehavior`] strategy.
///
/// This is equivalent to calling `reset_at(Instant::now() + period)`.
///
/// # Examples
///
/// ```
Expand All @@ -521,6 +523,107 @@ impl Interval {
self.delay.as_mut().reset(Instant::now() + self.period);
}

/// Resets the interval immediately.
///
/// This method ignores [`MissedTickBehavior`] strategy.
///
/// This is equivalent to calling `reset_at(Instant::now())`.
///
/// # Examples
///
/// ```
/// use tokio::time;
///
/// use std::time::Duration;
///
/// #[tokio::main]
/// async fn main() {
/// let mut interval = time::interval(Duration::from_millis(100));
///
/// interval.tick().await;
///
/// time::sleep(Duration::from_millis(50)).await;
/// interval.reset_immediately();
///
/// interval.tick().await;
/// interval.tick().await;
///
/// // approximately 150ms have elapsed.
/// }
/// ```
pub fn reset_immediately(&mut self) {
self.delay.as_mut().reset(Instant::now());
}

/// Resets the interval after the specified [`std::time::Duration`].
///
/// This method ignores [`MissedTickBehavior`] strategy.
///
/// This is equivalent to calling `reset_at(Instant::now() + after)`.
///
/// # Examples
///
/// ```
/// use tokio::time;
///
/// use std::time::Duration;
///
/// #[tokio::main]
/// async fn main() {
/// let mut interval = time::interval(Duration::from_millis(100));
/// interval.tick().await;
///
/// time::sleep(Duration::from_millis(50)).await;
///
/// let after = Duration::from_millis(20);
/// interval.reset_after(after);
///
/// interval.tick().await;
/// interval.tick().await;
///
/// // approximately 170ms have elapsed.
/// }
/// ```
pub fn reset_after(&mut self, after: Duration) {
self.delay.as_mut().reset(Instant::now() + after);
}

/// Resets the interval to a [`crate::time::Instant`] deadline.
///
/// Sets the next tick to expire at the given instant. If the instant is in
/// the past, then the [`MissedTickBehavior`] strategy will be used to
/// catch up. If the instant is in the future, then the next tick will
/// complete at the given instant, even if that means that it will sleep for
/// longer than the duration of this [`Interval`]. If the [`Interval`] had
Comment on lines +595 to +597
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like the "even if that means that it will sleep for longer than the duration" part is not currently tested.

/// any missed ticks before calling this method, then those are discarded.
///
/// # Examples
///
/// ```
/// use tokio::time::{self, Instant};
///
/// use std::time::Duration;
///
/// #[tokio::main]
/// async fn main() {
/// let mut interval = time::interval(Duration::from_millis(100));
/// interval.tick().await;
///
/// time::sleep(Duration::from_millis(50)).await;
///
/// let deadline = Instant::now() + Duration::from_millis(30);
/// interval.reset_at(deadline);
///
/// interval.tick().await;
/// interval.tick().await;
///
/// // approximately 180ms have elapsed.
/// }
/// ```
pub fn reset_at(&mut self, deadline: Instant) {
self.delay.as_mut().reset(deadline);
}
victor-timofei marked this conversation as resolved.
Show resolved Hide resolved

/// Returns the [`MissedTickBehavior`] strategy currently being used.
pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
self.missed_tick_behavior
Expand Down
148 changes: 148 additions & 0 deletions tokio/tests/time_interval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,154 @@ async fn reset() {
check_interval_poll!(i, start, 1001);
}

#[tokio::test(start_paused = true)]
async fn reset_immediatelly() {
let start = Instant::now();

// This is necessary because the timer is only so granular, and in order for
// all our ticks to resolve, the time needs to be 1ms ahead of what we
// expect, so that the runtime will see that it is time to resolve the timer
time::advance(ms(1)).await;

let mut i = task::spawn(time::interval_at(start, ms(300)));

check_interval_poll!(i, start, 0);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 300);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

i.reset_immediately();

// We add one because when using `reset` method, `Interval` adds the
// `period` from `Instant::now()`, which will always be off by one
check_interval_poll!(i, start, 401);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 701);
}

#[tokio::test(start_paused = true)]
async fn reset_after() {
let start = Instant::now();

// This is necessary because the timer is only so granular, and in order for
// all our ticks to resolve, the time needs to be 1ms ahead of what we
// expect, so that the runtime will see that it is time to resolve the timer
time::advance(ms(1)).await;

let mut i = task::spawn(time::interval_at(start, ms(300)));

check_interval_poll!(i, start, 0);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 300);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

i.reset_after(Duration::from_millis(20));

// We add one because when using `reset` method, `Interval` adds the
// `period` from `Instant::now()`, which will always be off by one
time::advance(ms(20)).await;
check_interval_poll!(i, start, 421);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 721);
}

#[tokio::test(start_paused = true)]
async fn reset_at() {
let start = Instant::now();

// This is necessary because the timer is only so granular, and in order for
// all our ticks to resolve, the time needs to be 1ms ahead of what we
// expect, so that the runtime will see that it is time to resolve the timer
time::advance(ms(1)).await;

let mut i = task::spawn(time::interval_at(start, ms(300)));

check_interval_poll!(i, start, 0);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 300);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

i.reset_at(Instant::now() + Duration::from_millis(40));

// We add one because when using `reset` method, `Interval` adds the
// `period` from `Instant::now()`, which will always be off by one
time::advance(ms(40)).await;
check_interval_poll!(i, start, 441);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 741);
}

#[tokio::test(start_paused = true)]
async fn reset_at_bigger_than_interval() {
let start = Instant::now();

// This is necessary because the timer is only so granular, and in order for
// all our ticks to resolve, the time needs to be 1ms ahead of what we
// expect, so that the runtime will see that it is time to resolve the timer
time::advance(ms(1)).await;

let mut i = task::spawn(time::interval_at(start, ms(300)));

check_interval_poll!(i, start, 0);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

time::advance(ms(200)).await;
check_interval_poll!(i, start, 300);

time::advance(ms(100)).await;
check_interval_poll!(i, start);

i.reset_at(Instant::now() + Duration::from_millis(1000));

// Validate the interval does not tick until 1000ms have passed
time::advance(ms(300)).await;
check_interval_poll!(i, start);
time::advance(ms(300)).await;
check_interval_poll!(i, start);
time::advance(ms(300)).await;
check_interval_poll!(i, start);

// We add one because when using `reset` method, `Interval` adds the
// `period` from `Instant::now()`, which will always be off by one
time::advance(ms(100)).await;
check_interval_poll!(i, start, 1401);

time::advance(ms(300)).await;
check_interval_poll!(i, start, 1701);
}

fn poll_next(interval: &mut task::Spawn<time::Interval>) -> Poll<Instant> {
interval.enter(|cx, mut interval| interval.poll_tick(cx))
}
Expand Down