Skip to main content

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