compio_runtime/
time.rs

1//! Utilities for tracking time.
2
3use std::{
4    error::Error,
5    fmt::Display,
6    future::Future,
7    time::{Duration, Instant},
8};
9
10use futures_util::{FutureExt, select};
11
12use crate::Runtime;
13
14/// Waits until `duration` has elapsed.
15///
16/// Equivalent to [`sleep_until(Instant::now() + duration)`](sleep_until). An
17/// asynchronous analog to [`std::thread::sleep`].
18///
19/// To run something regularly on a schedule, see [`interval`].
20///
21/// # Examples
22///
23/// Wait 100ms and print "100 ms have elapsed".
24///
25/// ```
26/// use std::time::Duration;
27///
28/// use compio_runtime::time::sleep;
29///
30/// # compio_runtime::Runtime::new().unwrap().block_on(async {
31/// sleep(Duration::from_millis(100)).await;
32/// println!("100 ms have elapsed");
33/// # })
34/// ```
35pub async fn sleep(duration: Duration) {
36    Runtime::with_current(|r| r.create_timer(duration)).await
37}
38
39/// Waits until `deadline` is reached.
40///
41/// To run something regularly on a schedule, see [`interval`].
42///
43/// # Examples
44///
45/// Wait 100ms and print "100 ms have elapsed".
46///
47/// ```
48/// use std::time::{Duration, Instant};
49///
50/// use compio_runtime::time::sleep_until;
51///
52/// # compio_runtime::Runtime::new().unwrap().block_on(async {
53/// sleep_until(Instant::now() + Duration::from_millis(100)).await;
54/// println!("100 ms have elapsed");
55/// # })
56/// ```
57pub async fn sleep_until(deadline: Instant) {
58    sleep(deadline - Instant::now()).await
59}
60
61/// Error returned by [`timeout`] or [`timeout_at`].
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct Elapsed;
64
65impl Display for Elapsed {
66    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67        f.write_str("deadline has elapsed")
68    }
69}
70
71impl Error for Elapsed {}
72
73/// Require a [`Future`] to complete before the specified duration has elapsed.
74///
75/// If the future completes before the duration has elapsed, then the completed
76/// value is returned. Otherwise, an error is returned and the future is
77/// canceled.
78pub async fn timeout<F: Future>(duration: Duration, future: F) -> Result<F::Output, Elapsed> {
79    select! {
80        res = future.fuse() => Ok(res),
81        _ = sleep(duration).fuse() => Err(Elapsed),
82    }
83}
84
85/// Require a [`Future`] to complete before the specified instant in time.
86///
87/// If the future completes before the instant is reached, then the completed
88/// value is returned. Otherwise, an error is returned.
89pub async fn timeout_at<F: Future>(deadline: Instant, future: F) -> Result<F::Output, Elapsed> {
90    timeout(deadline - Instant::now(), future).await
91}
92
93/// Interval returned by [`interval`] and [`interval_at`]
94///
95/// This type allows you to wait on a sequence of instants with a certain
96/// duration between each instant. Unlike calling [`sleep`] in a loop, this lets
97/// you count the time spent between the calls to [`sleep`] as well.
98#[derive(Debug)]
99pub struct Interval {
100    first_ticked: bool,
101    start: Instant,
102    period: Duration,
103}
104
105impl Interval {
106    pub(crate) fn new(start: Instant, period: Duration) -> Self {
107        Self {
108            first_ticked: false,
109            start,
110            period,
111        }
112    }
113
114    /// Completes when the next instant in the interval has been reached.
115    ///
116    /// See [`interval`] and [`interval_at`].
117    pub async fn tick(&mut self) -> Instant {
118        if !self.first_ticked {
119            sleep_until(self.start).await;
120            self.first_ticked = true;
121            self.start
122        } else {
123            let now = Instant::now();
124            let next = now + self.period
125                - Duration::from_nanos(
126                    ((now - self.start).as_nanos() % self.period.as_nanos()) as _,
127                );
128            sleep_until(next).await;
129            next
130        }
131    }
132}
133
134/// Creates new [`Interval`] that yields with interval of `period`. The first
135/// tick completes immediately.
136///
137/// An interval will tick indefinitely. At any time, the [`Interval`] value can
138/// be dropped. This cancels the interval.
139///
140/// This function is equivalent to
141/// [`interval_at(Instant::now(), period)`](interval_at).
142///
143/// # Panics
144///
145/// This function panics if `period` is zero.
146///
147/// # Examples
148///
149/// ```
150/// use std::time::Duration;
151///
152/// use compio_runtime::time::interval;
153///
154/// # compio_runtime::Runtime::new().unwrap().block_on(async {
155/// let mut interval = interval(Duration::from_millis(10));
156///
157/// interval.tick().await; // ticks immediately
158/// interval.tick().await; // ticks after 10ms
159/// interval.tick().await; // ticks after 10ms
160///
161/// // approximately 20ms have elapsed.
162/// # })
163/// ```
164///
165/// A simple example using [`interval`] to execute a task every two seconds.
166///
167/// The difference between [`interval`] and [`sleep`] is that an [`Interval`]
168/// measures the time since the last tick, which means that [`.tick().await`]
169/// may wait for a shorter time than the duration specified for the interval
170/// if some time has passed between calls to [`.tick().await`].
171///
172/// If the tick in the example below was replaced with [`sleep`], the task
173/// would only be executed once every three seconds, and not every two
174/// seconds.
175///
176/// ```no_run
177/// use std::time::Duration;
178///
179/// use compio_runtime::time::{interval, sleep};
180///
181/// async fn task_that_takes_a_second() {
182///     println!("hello");
183///     sleep(Duration::from_secs(1)).await
184/// }
185///
186/// # compio_runtime::Runtime::new().unwrap().block_on(async {
187/// let mut interval = interval(Duration::from_secs(2));
188/// for _i in 0..5 {
189///     interval.tick().await;
190///     task_that_takes_a_second().await;
191/// }
192/// # })
193/// ```
194///
195/// [`sleep`]: crate::time::sleep()
196/// [`.tick().await`]: Interval::tick
197pub fn interval(period: Duration) -> Interval {
198    interval_at(Instant::now(), period)
199}
200
201/// Creates new [`Interval`] that yields with interval of `period` with the
202/// first tick completing at `start`.
203///
204/// An interval will tick indefinitely. At any time, the [`Interval`] value can
205/// be dropped. This cancels the interval.
206///
207/// # Panics
208///
209/// This function panics if `period` is zero.
210///
211/// # Examples
212///
213/// ```
214/// use std::time::{Duration, Instant};
215///
216/// use compio_runtime::time::interval_at;
217///
218/// # compio_runtime::Runtime::new().unwrap().block_on(async {
219/// let start = Instant::now() + Duration::from_millis(50);
220/// let mut interval = interval_at(start, Duration::from_millis(10));
221///
222/// interval.tick().await; // ticks after 50ms
223/// interval.tick().await; // ticks after 10ms
224/// interval.tick().await; // ticks after 10ms
225///
226/// // approximately 70ms have elapsed.
227/// # });
228/// ```
229pub fn interval_at(start: Instant, period: Duration) -> Interval {
230    assert!(period > Duration::ZERO, "`period` must be non-zero.");
231    Interval::new(start, period)
232}