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