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