monoio/time/interval.rs
1use std::{
2 future::Future,
3 pin::Pin,
4 task::{Context, Poll},
5};
6
7use crate::{
8 macros::support::poll_fn,
9 time::{sleep_until, Duration, Instant, Sleep},
10};
11
12/// Creates new [`Interval`] that yields with interval of `period`.
13///
14/// The first tick completes immediately. The default [`MissedTickBehavior`] is
15/// [`Burst`](MissedTickBehavior::Burst), but this can be configured
16/// by calling [`set_missed_tick_behavior`](Interval::set_missed_tick_behavior).
17///
18/// An interval will tick indefinitely. At any time, the [`Interval`] value can
19/// be dropped. This cancels the interval.
20///
21/// This function is equivalent to
22/// [`interval_at(Instant::now(), period)`](interval_at).
23///
24/// # Panics
25///
26/// This function panics if `period` is zero.
27///
28/// # Examples
29///
30/// ```
31/// use monoio::time::{self, Duration};
32///
33/// #[monoio::main(timer_enabled = true)]
34/// async fn main() {
35/// let mut interval = time::interval(Duration::from_millis(10));
36///
37/// interval.tick().await; // ticks immediately
38/// interval.tick().await; // ticks after 10ms
39/// interval.tick().await; // ticks after 10ms
40///
41/// // approximately 20ms have elapsed.
42/// }
43/// ```
44///
45/// A simple example using `interval` to execute a task every two seconds.
46///
47/// The difference between `interval` and [`sleep`] is that an [`Interval`]
48/// measures the time since the last tick, which means that [`.tick().await`]
49/// may wait for a shorter time than the duration specified for the interval
50/// if some time has passed between calls to [`.tick().await`].
51///
52/// If the tick in the example below was replaced with [`sleep`], the task
53/// would only be executed once every three seconds, and not every two
54/// seconds.
55///
56/// ```
57/// use monoio::time;
58///
59/// async fn task_that_takes_a_second() {
60/// println!("hello");
61/// time::sleep(time::Duration::from_secs(1)).await
62/// }
63///
64/// #[monoio::main(timer_enabled = true)]
65/// async fn main() {
66/// let mut interval = time::interval(time::Duration::from_secs(2));
67/// for _i in 0..5 {
68/// interval.tick().await;
69/// task_that_takes_a_second().await;
70/// }
71/// }
72/// ```
73///
74/// [`sleep`]: crate::time::sleep()
75/// [`.tick().await`]: Interval::tick
76pub fn interval(period: Duration) -> Interval {
77 assert!(period > Duration::new(0, 0), "`period` must be non-zero.");
78
79 interval_at(Instant::now(), period)
80}
81
82/// Creates new [`Interval`] that yields with interval of `period` with the
83/// first tick completing at `start`.
84///
85/// The default [`MissedTickBehavior`] is [`Burst`](MissedTickBehavior::Burst),
86/// but this can be configured by calling
87/// [`set_missed_tick_behavior`](Interval::set_missed_tick_behavior).
88///
89/// An interval will tick indefinitely. At any time, the [`Interval`] value can
90/// be dropped. This cancels the interval.
91///
92/// # Panics
93///
94/// This function panics if `period` is zero.
95///
96/// # Examples
97///
98/// ```
99/// use monoio::time::{interval_at, Duration, Instant};
100///
101/// #[monoio::main(timer_enabled = true)]
102/// async fn main() {
103/// let start = Instant::now() + Duration::from_millis(50);
104/// let mut interval = interval_at(start, Duration::from_millis(10));
105///
106/// interval.tick().await; // ticks after 50ms
107/// interval.tick().await; // ticks after 10ms
108/// interval.tick().await; // ticks after 10ms
109///
110/// // approximately 70ms have elapsed.
111/// }
112/// ```
113pub fn interval_at(start: Instant, period: Duration) -> Interval {
114 assert!(period > Duration::new(0, 0), "`period` must be non-zero.");
115
116 Interval {
117 delay: Box::pin(sleep_until(start)),
118 period,
119 missed_tick_behavior: Default::default(),
120 }
121}
122
123/// Defines the behavior of an [`Interval`] when it misses a tick.
124///
125/// Sometimes, an [`Interval`]'s tick is missed. For example, consider the
126/// following:
127///
128/// ```
129/// use monoio::time::{self, Duration};
130/// # async fn task_that_takes_one_to_three_millis() {}
131///
132/// #[monoio::main(timer_enabled = true)]
133/// async fn main() {
134/// // ticks every 2 milliseconds
135/// let mut interval = time::interval(Duration::from_millis(2));
136/// for _ in 0..5 {
137/// interval.tick().await;
138/// // if this takes more than 2 milliseconds, a tick will be delayed
139/// task_that_takes_one_to_three_millis().await;
140/// }
141/// }
142/// ```
143///
144/// Generally, a tick is missed if too much time is spent without calling
145/// [`Interval::tick()`].
146///
147/// By default, when a tick is missed, [`Interval`] fires ticks as quickly as it
148/// can until it is "caught up" in time to where it should be.
149/// `MissedTickBehavior` can be used to specify a different behavior for
150/// [`Interval`] to exhibit. Each variant represents a different strategy.
151///
152/// Note that because the executor cannot guarantee exact precision with timers,
153/// these strategies will only apply when the delay is greater than 5
154/// milliseconds.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum MissedTickBehavior {
157 /// Tick as fast as possible until caught up.
158 ///
159 /// When this strategy is used, [`Interval`] schedules ticks "normally" (the
160 /// same as it would have if the ticks hadn't been delayed), which results
161 /// in it firing ticks as fast as possible until it is caught up in time to
162 /// where it should be. Unlike [`Delay`] and [`Skip`], the ticks yielded
163 /// when `Burst` is used (the [`Instant`]s that [`tick`](Interval::tick)
164 /// yields) aren't different than they would have been if a tick had not
165 /// been missed. Like [`Skip`], and unlike [`Delay`], the ticks may be
166 /// shortened.
167 ///
168 /// This looks something like this:
169 /// ```text
170 /// Expected ticks: | 1 | 2 | 3 | 4 | 5 | 6 |
171 /// Actual ticks: | work -----| delay | work | work | work -| work -----|
172 /// ```
173 ///
174 /// In code:
175 ///
176 /// ```
177 /// use monoio::time::{interval, Duration};
178 /// # async fn task_that_takes_200_millis() {}
179 ///
180 /// # #[monoio::main(timer_enabled = true)]
181 /// # async fn main() {
182 /// let mut interval = interval(Duration::from_millis(50));
183 ///
184 /// task_that_takes_200_millis().await;
185 /// // The `Interval` has missed a tick
186 ///
187 /// // Since we have exceeded our timeout, this will resolve immediately
188 /// interval.tick().await;
189 ///
190 /// // Since we are more than 100ms after the start of `interval`, this will
191 /// // also resolve immediately.
192 /// interval.tick().await;
193 ///
194 /// // Also resolves immediately, because it was supposed to resolve at
195 /// // 150ms after the start of `interval`
196 /// interval.tick().await;
197 ///
198 /// // Resolves immediately
199 /// interval.tick().await;
200 ///
201 /// // Since we have gotten to 200ms after the start of `interval`, this
202 /// // will resolve after 50ms
203 /// interval.tick().await;
204 /// # }
205 /// ```
206 ///
207 /// This is the default behavior when [`Interval`] is created with
208 /// [`interval`] and [`interval_at`].
209 ///
210 /// [`Delay`]: MissedTickBehavior::Delay
211 /// [`Skip`]: MissedTickBehavior::Skip
212 Burst,
213
214 /// Tick at multiples of `period` from when [`tick`] was called, rather than
215 /// from `start`.
216 ///
217 /// When this strategy is used and [`Interval`] has missed a tick, instead
218 /// of scheduling ticks to fire at multiples of `period` from `start` (the
219 /// time when the first tick was fired), it schedules all future ticks to
220 /// happen at a regular `period` from the point when [`tick`] was called.
221 /// Unlike [`Burst`] and [`Skip`], ticks are not shortened, and they aren't
222 /// guaranteed to happen at a multiple of `period` from `start` any longer.
223 ///
224 /// This looks something like this:
225 /// ```text
226 /// Expected ticks: | 1 | 2 | 3 | 4 | 5 | 6 |
227 /// Actual ticks: | work -----| delay | work -----| work -----| work -----|
228 /// ```
229 ///
230 /// In code:
231 ///
232 /// ```
233 /// use monoio::time::{interval, Duration, MissedTickBehavior};
234 /// # async fn task_that_takes_more_than_50_millis() {}
235 ///
236 /// # #[monoio::main(timer_enabled = true)]
237 /// # async fn main() {
238 /// let mut interval = interval(Duration::from_millis(50));
239 /// interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
240 ///
241 /// task_that_takes_more_than_50_millis().await;
242 /// // The `Interval` has missed a tick
243 ///
244 /// // Since we have exceeded our timeout, this will resolve immediately
245 /// interval.tick().await;
246 ///
247 /// // But this one, rather than also resolving immediately, as might happen
248 /// // with the `Burst` or `Skip` behaviors, will not resolve until
249 /// // 50ms after the call to `tick` up above. That is, in `tick`, when we
250 /// // recognize that we missed a tick, we schedule the next tick to happen
251 /// // 50ms (or whatever the `period` is) from right then, not from when
252 /// // were were *supposed* to tick
253 /// interval.tick().await;
254 /// # }
255 /// ```
256 ///
257 /// [`Burst`]: MissedTickBehavior::Burst
258 /// [`Skip`]: MissedTickBehavior::Skip
259 /// [`tick`]: Interval::tick
260 Delay,
261
262 /// Skip missed ticks and tick on the next multiple of `period` from
263 /// `start`.
264 ///
265 /// When this strategy is used, [`Interval`] schedules the next tick to fire
266 /// at the next-closest tick that is a multiple of `period` away from
267 /// `start` (the point where [`Interval`] first ticked). Like [`Burst`], all
268 /// ticks remain multiples of `period` away from `start`, but unlike
269 /// [`Burst`], the ticks may not be *one* multiple of `period` away from the
270 /// last tick. Like [`Delay`], the ticks are no longer the same as they
271 /// would have been if ticks had not been missed, but unlike [`Delay`], and
272 /// like [`Burst`], the ticks may be shortened to be less than one `period`
273 /// away from each other.
274 ///
275 /// This looks something like this:
276 /// ```text
277 /// Expected ticks: | 1 | 2 | 3 | 4 | 5 | 6 |
278 /// Actual ticks: | work -----| delay | work ---| work -----| work -----|
279 /// ```
280 ///
281 /// In code:
282 ///
283 /// ```
284 /// use monoio::time::{interval, Duration, MissedTickBehavior};
285 /// # async fn task_that_takes_75_millis() {}
286 ///
287 /// # #[monoio::main(timer_enabled = true)]
288 /// # async fn main() {
289 /// let mut interval = interval(Duration::from_millis(50));
290 /// interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
291 ///
292 /// task_that_takes_75_millis().await;
293 /// // The `Interval` has missed a tick
294 ///
295 /// // Since we have exceeded our timeout, this will resolve immediately
296 /// interval.tick().await;
297 ///
298 /// // This one will resolve after 25ms, 100ms after the start of
299 /// // `interval`, which is the closest multiple of `period` from the start
300 /// // of `interval` after the call to `tick` up above.
301 /// interval.tick().await;
302 /// # }
303 /// ```
304 ///
305 /// [`Burst`]: MissedTickBehavior::Burst
306 /// [`Delay`]: MissedTickBehavior::Delay
307 Skip,
308}
309
310impl MissedTickBehavior {
311 /// If a tick is missed, this method is called to determine when the next
312 /// tick should happen.
313 fn next_timeout(&self, timeout: Instant, now: Instant, period: Duration) -> Instant {
314 match self {
315 Self::Burst => timeout + period,
316 Self::Delay => now + period,
317 Self::Skip => {
318 now + period
319 - Duration::from_nanos(
320 ((now - timeout).as_nanos() % period.as_nanos())
321 .try_into()
322 // This operation is practically guaranteed not to
323 // fail, as in order for it to fail, `period` would
324 // have to be longer than `now - timeout`, and both
325 // would have to be longer than 584 years.
326 //
327 // If it did fail, there's not a good way to pass
328 // the error along to the user, so we just panic.
329 .expect(
330 "too much time has elapsed since the interval was supposed to tick",
331 ),
332 )
333 }
334 }
335 }
336}
337
338impl Default for MissedTickBehavior {
339 /// Returns [`MissedTickBehavior::Burst`].
340 ///
341 /// For most usecases, the [`Burst`] strategy is what is desired.
342 /// Additionally, to preserve backwards compatibility, the [`Burst`]
343 /// strategy must be the default. For these reasons,
344 /// [`MissedTickBehavior::Burst`] is the default for [`MissedTickBehavior`].
345 /// See [`Burst`] for more details.
346 ///
347 /// [`Burst`]: MissedTickBehavior::Burst
348 fn default() -> Self {
349 Self::Burst
350 }
351}
352
353/// Interval returned by [`interval`] and [`interval_at`]
354///
355/// This type allows you to wait on a sequence of instants with a certain
356/// duration between each instant. Unlike calling [`sleep`] in a loop, this lets
357/// you count the time spent between the calls to [`sleep`] as well.
358#[derive(Debug)]
359pub struct Interval {
360 /// Future that completes the next time the `Interval` yields a value.
361 delay: Pin<Box<Sleep>>,
362
363 /// The duration between values yielded by `Interval`.
364 period: Duration,
365
366 /// The strategy `Interval` should use when a tick is missed.
367 missed_tick_behavior: MissedTickBehavior,
368}
369
370impl Interval {
371 /// Completes when the next instant in the interval has been reached.
372 ///
373 /// # Examples
374 ///
375 /// ```
376 /// use std::time::Duration;
377 ///
378 /// use monoio::time;
379 ///
380 /// #[monoio::main(timer_enabled = true)]
381 /// async fn main() {
382 /// let mut interval = time::interval(Duration::from_millis(10));
383 ///
384 /// interval.tick().await;
385 /// interval.tick().await;
386 /// interval.tick().await;
387 ///
388 /// // approximately 20ms have elapsed.
389 /// }
390 /// ```
391 pub async fn tick(&mut self) -> Instant {
392 poll_fn(|cx| self.poll_tick(cx)).await
393 }
394
395 /// Poll for the next instant in the interval to be reached.
396 ///
397 /// This method can return the following values:
398 ///
399 /// * `Poll::Pending` if the next instant has not yet been reached.
400 /// * `Poll::Ready(instant)` if the next instant has been reached.
401 ///
402 /// When this method returns `Poll::Pending`, the current task is scheduled
403 /// to receive a wakeup when the instant has elapsed. Note that on multiple
404 /// calls to `poll_tick`, only the [`Waker`](std::task::Waker) from the
405 /// [`Context`] passed to the most recent call is scheduled to receive a
406 /// wakeup.
407 pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Instant> {
408 // Wait for the delay to be done
409 ready!(Pin::new(&mut self.delay).poll(cx));
410
411 // Get the time when we were scheduled to tick
412 let timeout = self.delay.deadline();
413
414 let now = Instant::now();
415
416 // If a tick was not missed, and thus we are being called before the
417 // next tick is due, just schedule the next tick normally, one `period`
418 // after `timeout`
419 //
420 // However, if a tick took excessively long and we are now behind,
421 // schedule the next tick according to how the user specified with
422 // `MissedTickBehavior`
423 let next = if now > timeout + Duration::from_millis(5) {
424 self.missed_tick_behavior
425 .next_timeout(timeout, now, self.period)
426 } else {
427 timeout + self.period
428 };
429
430 self.delay.as_mut().reset(next);
431
432 // Return the time when we were scheduled to tick
433 Poll::Ready(timeout)
434 }
435
436 /// Returns the [`MissedTickBehavior`] strategy currently being used.
437 pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
438 self.missed_tick_behavior
439 }
440
441 /// Sets the [`MissedTickBehavior`] strategy that should be used.
442 pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
443 self.missed_tick_behavior = behavior;
444 }
445
446 /// Returns the period of the interval.
447 pub fn period(&self) -> Duration {
448 self.period
449 }
450}