Skip to main content

sequoia_async/
time.rs

1#![forbid(unsafe_code)]
2
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6use std::time::{Duration, Instant};
7
8use futures_core::Stream;
9use pin_project_lite::pin_project;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct TimeoutError;
13
14impl std::fmt::Display for TimeoutError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        write!(f, "operation timed out")
17    }
18}
19
20impl std::error::Error for TimeoutError {}
21
22pub type Elapsed = TimeoutError;
23
24pub async fn sleep(duration: Duration) {
25    if duration.is_zero() {
26        return;
27    }
28    std::thread::sleep(duration);
29}
30
31pin_project! {
32
33    pub struct Timeout<F> {
34        #[pin]
35        future: F,
36        deadline: Instant,
37    }
38}
39
40impl<F> Timeout<F> {
41    fn new(future: F, duration: Duration) -> Self {
42        Self {
43            future,
44            deadline: Instant::now() + duration,
45        }
46    }
47}
48
49impl<F: Future> Future for Timeout<F> {
50    type Output = Result<F::Output, TimeoutError>;
51
52    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
53        let this = self.project();
54        if let Poll::Ready(v) = this.future.poll(cx) {
55            return Poll::Ready(Ok(v));
56        }
57        if Instant::now() >= *this.deadline {
58            return Poll::Ready(Err(TimeoutError));
59        }
60
61        let waker = cx.waker().clone();
62        let wait = (*this.deadline).saturating_duration_since(Instant::now());
63        let wait = wait.min(Duration::from_millis(10));
64        std::thread::spawn(move || {
65            std::thread::sleep(wait);
66            waker.wake();
67        });
68        Poll::Pending
69    }
70}
71
72pub fn timeout<F>(duration: Duration, future: F) -> Timeout<F>
73where
74    F: Future,
75{
76    Timeout::new(future, duration)
77}
78
79pub fn with_timeout<F>(duration: Duration, future: F) -> Timeout<F>
80where
81    F: Future,
82{
83    timeout(duration, future)
84}
85
86#[derive(Clone, Copy, Debug)]
87pub struct Interval {
88    period: Duration,
89    next: Instant,
90}
91
92impl Interval {
93    pub fn new(period: Duration) -> Self {
94        Self {
95            period: period.max(Duration::from_millis(1)),
96            next: Instant::now() + period.max(Duration::from_millis(1)),
97        }
98    }
99
100    pub async fn tick(&mut self) {
101        let now = Instant::now();
102        if self.next > now {
103            std::thread::sleep(self.next - now);
104        }
105        self.next = Instant::now() + self.period;
106    }
107
108    pub fn period(&self) -> Duration {
109        self.period
110    }
111}
112
113pin_project! {
114
115    pub struct Ticks {
116        interval: Interval,
117    }
118}
119
120impl Ticks {
121    pub fn new(period: Duration) -> Self {
122        Self {
123            interval: Interval::new(period),
124        }
125    }
126}
127
128impl Stream for Ticks {
129    type Item = Instant;
130
131    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
132        let this = self.project();
133        let now = Instant::now();
134        if now >= this.interval.next {
135            this.interval.next = now + this.interval.period;
136            return Poll::Ready(Some(now));
137        }
138        let waker = cx.waker().clone();
139        let wait = this.interval.next.saturating_duration_since(now);
140        std::thread::spawn(move || {
141            std::thread::sleep(wait.min(Duration::from_millis(50)));
142            waker.wake();
143        });
144        Poll::Pending
145    }
146}
147
148pub fn ticks(period: Duration) -> Ticks {
149    Ticks::new(period)
150}
151
152#[derive(Clone, Debug, Default)]
153pub struct Debouncer {
154    generation: std::rc::Rc<std::cell::Cell<u64>>,
155}
156
157impl Debouncer {
158    pub fn new() -> Self {
159        Self {
160            generation: std::rc::Rc::new(std::cell::Cell::new(0)),
161        }
162    }
163
164    pub fn request(&self) -> u64 {
165        let next = self.generation.get() + 1;
166        self.generation.set(next);
167        next
168    }
169
170    pub fn is_latest(&self, generation: u64) -> bool {
171        generation == self.generation.get()
172    }
173
174    pub fn invalidate(&self) {
175        self.generation.set(self.generation.get() + 1);
176    }
177}
178
179#[derive(Clone, Debug)]
180pub struct Throttler {
181    min_interval: Duration,
182    last: std::rc::Rc<std::cell::Cell<Option<Instant>>>,
183}
184
185impl Throttler {
186    pub fn new(min_interval: Duration) -> Self {
187        Self {
188            min_interval,
189            last: std::rc::Rc::new(std::cell::Cell::new(None)),
190        }
191    }
192
193    pub fn should_run(&self) -> bool {
194        let now = Instant::now();
195        let last = self.last.get();
196        match last {
197            None => {
198                self.last.set(Some(now));
199                true
200            }
201            Some(t) if now.duration_since(t) >= self.min_interval => {
202                self.last.set(Some(now));
203                true
204            }
205            _ => false,
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn sleep_zero_returns_fast() {
216        let t = Instant::now();
217        pollster::block_on(sleep(Duration::ZERO));
218        assert!(t.elapsed() < Duration::from_secs(1));
219    }
220
221    #[test]
222    fn timeout_ok_when_fast() {
223        let out = pollster::block_on(timeout(Duration::from_secs(2), async { 7 }));
224        assert_eq!(out, Ok(7));
225    }
226
227    #[test]
228    fn timeout_errors_when_slow() {
229        let out = pollster::block_on(timeout(
230            Duration::from_millis(20),
231            std::future::pending::<u32>(),
232        ));
233        assert_eq!(out, Err(TimeoutError));
234    }
235
236    #[test]
237    fn interval_ticks_advance() {
238        let mut iv = Interval::new(Duration::from_millis(5));
239        assert_eq!(iv.period(), Duration::from_millis(5));
240        pollster::block_on(iv.tick());
241        pollster::block_on(iv.tick());
242    }
243}