Skip to main content

dnsy_core/
clock.rs

1//! The one time source of the workspace: wall-clock Unix time, a monotonic instant and a
2//! sleep, behind a trait so tests drive time by hand.
3
4use std::future::Future;
5use std::pin::Pin;
6#[cfg(feature = "test-util")]
7use std::sync::Arc;
8#[cfg(feature = "test-util")]
9use std::sync::atomic::{AtomicI64, Ordering};
10use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
11
12/// Source of time. Production code uses [`SystemClock`]; tests use `ManualClock` (feature
13/// `test-util`).
14pub trait Clock: Send + Sync + 'static {
15    /// Unix seconds, 0 for a clock before 1970.
16    fn unix_secs(&self) -> i64;
17    /// Unix milliseconds, 0 for a clock before 1970.
18    fn unix_millis(&self) -> i64;
19    /// A monotonic instant.
20    fn instant(&self) -> Instant;
21    /// Completes after `d` on this clock.
22    fn sleep(&self, d: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>>;
23}
24
25/// The system clock.
26#[derive(Debug, Clone, Copy, Default)]
27pub struct SystemClock;
28
29impl Clock for SystemClock {
30    fn unix_secs(&self) -> i64 {
31        SystemTime::now()
32            .duration_since(UNIX_EPOCH)
33            .map_or(0, |d| i64::try_from(d.as_secs()).unwrap_or(i64::MAX))
34    }
35
36    fn unix_millis(&self) -> i64 {
37        SystemTime::now()
38            .duration_since(UNIX_EPOCH)
39            .map_or(0, |d| i64::try_from(d.as_millis()).unwrap_or(i64::MAX))
40    }
41
42    fn instant(&self) -> Instant {
43        Instant::now()
44    }
45
46    fn sleep(&self, d: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
47        Box::pin(tokio::time::sleep(d))
48    }
49}
50
51/// A clock that only moves when told to; clones share the same time. `sleep` completes at once.
52#[cfg(feature = "test-util")]
53#[derive(Debug, Clone)]
54pub struct ManualClock(Arc<ManualInner>);
55
56#[cfg(feature = "test-util")]
57#[derive(Debug)]
58struct ManualInner {
59    /// Unix nanoseconds.
60    nanos: AtomicI64,
61    start_nanos: i64,
62    base: Instant,
63}
64
65#[cfg(feature = "test-util")]
66impl ManualClock {
67    /// A clock at `unix_secs` seconds since the epoch.
68    #[must_use]
69    pub fn new(unix_secs: i64) -> ManualClock {
70        let nanos = unix_secs.saturating_mul(1_000_000_000);
71        ManualClock(Arc::new(ManualInner {
72            nanos: AtomicI64::new(nanos),
73            start_nanos: nanos,
74            base: Instant::now(),
75        }))
76    }
77
78    /// Jumps to `unix_secs` seconds since the epoch.
79    pub fn set(&self, unix_secs: i64) {
80        self.0
81            .nanos
82            .store(unix_secs.saturating_mul(1_000_000_000), Ordering::SeqCst);
83    }
84
85    /// Moves forward (or back, when negative) by `secs` seconds.
86    pub fn advance(&self, secs: i64) {
87        self.0
88            .nanos
89            .fetch_add(secs.saturating_mul(1_000_000_000), Ordering::SeqCst);
90    }
91
92    /// Moves forward by `d`.
93    pub fn advance_by(&self, d: Duration) {
94        self.0.nanos.fetch_add(
95            i64::try_from(d.as_nanos()).unwrap_or(i64::MAX),
96            Ordering::SeqCst,
97        );
98    }
99}
100
101#[cfg(feature = "test-util")]
102impl Default for ManualClock {
103    fn default() -> Self {
104        Self::new(0)
105    }
106}
107
108#[cfg(feature = "test-util")]
109impl Clock for ManualClock {
110    fn unix_secs(&self) -> i64 {
111        self.0
112            .nanos
113            .load(Ordering::SeqCst)
114            .div_euclid(1_000_000_000)
115    }
116
117    fn unix_millis(&self) -> i64 {
118        self.0.nanos.load(Ordering::SeqCst).div_euclid(1_000_000)
119    }
120
121    fn instant(&self) -> Instant {
122        let elapsed = self.0.nanos.load(Ordering::SeqCst) - self.0.start_nanos;
123        if elapsed >= 0 {
124            self.0.base + Duration::from_nanos(elapsed.unsigned_abs())
125        } else {
126            self.0
127                .base
128                .checked_sub(Duration::from_nanos(elapsed.unsigned_abs()))
129                .unwrap_or(self.0.base)
130        }
131    }
132
133    fn sleep(&self, _: Duration) -> Pin<Box<dyn Future<Output = ()> + Send>> {
134        Box::pin(std::future::ready(()))
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn system_clock_is_plausible_and_monotonic() {
144        let c = SystemClock;
145        assert!(c.unix_secs() > 1_700_000_000);
146        assert!(c.unix_millis() / 1000 >= c.unix_secs() - 1);
147        let a = c.instant();
148        assert!(c.instant() >= a);
149    }
150
151    #[tokio::test]
152    async fn system_clock_sleeps() {
153        let t = Instant::now();
154        SystemClock.sleep(Duration::from_millis(10)).await;
155        assert!(t.elapsed() >= Duration::from_millis(9));
156    }
157
158    #[cfg(feature = "test-util")]
159    #[tokio::test]
160    async fn manual_clock_moves_only_when_told_and_clones_share_time() {
161        let c = ManualClock::new(1_000);
162        let twin = c.clone();
163        let t0 = c.instant();
164        assert_eq!((c.unix_secs(), c.unix_millis()), (1_000, 1_000_000));
165        twin.advance(5);
166        assert_eq!(c.unix_secs(), 1_005);
167        assert_eq!(c.instant() - t0, Duration::from_secs(5));
168        c.advance_by(Duration::from_millis(1_500));
169        assert_eq!(twin.unix_millis(), 1_006_500);
170        assert_eq!(twin.unix_secs(), 1_006);
171        c.set(10);
172        assert_eq!(twin.unix_secs(), 10);
173        assert!(c.instant() < t0);
174        c.sleep(Duration::from_secs(3600)).await;
175        assert_eq!(ManualClock::default().unix_secs(), 0);
176    }
177}