Skip to main content

connpool/core/
clock.rs

1//! Time abstraction so every time-dependent rule is deterministic in tests. Time is measured
2//! in `f64` seconds (like every sibling implementation).
3
4use std::sync::Mutex;
5use std::time::Instant;
6
7/// A monotonic time source. The pool uses the injected clock for *logical* time (connection
8/// age, idle time, breaker recovery, retry backoff); real-time blocking in the async pool is
9/// driven by the runtime, not the clock (exactly as Python's `asyncio.timeout` ignores the
10/// injected clock).
11pub trait Clock: Send + Sync {
12    /// Monotonically increasing time, in seconds.
13    fn monotonic(&self) -> f64;
14
15    /// Blocks for the given number of seconds (no-op for `seconds <= 0`).
16    fn sleep(&self, seconds: f64);
17
18    /// Async sleep for the given number of seconds. For [`SystemClock`] this awaits a real
19    /// timer; for [`ManualClock`] it advances logical time and returns immediately.
20    #[cfg(feature = "tokio")]
21    fn sleep_async<'a>(
22        &'a self,
23        seconds: f64,
24    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>>;
25}
26
27/// Real wall-clock time (the default). Anchored at construction so `monotonic` starts near 0.
28#[derive(Debug)]
29pub struct SystemClock {
30    start: Instant,
31}
32
33impl SystemClock {
34    /// Returns a clock anchored at the current instant.
35    pub fn new() -> Self {
36        Self {
37            start: Instant::now(),
38        }
39    }
40}
41
42impl Default for SystemClock {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48impl Clock for SystemClock {
49    fn monotonic(&self) -> f64 {
50        self.start.elapsed().as_secs_f64()
51    }
52
53    fn sleep(&self, seconds: f64) {
54        if seconds > 0.0 {
55            std::thread::sleep(std::time::Duration::from_secs_f64(seconds));
56        }
57    }
58
59    #[cfg(feature = "tokio")]
60    fn sleep_async<'a>(
61        &'a self,
62        seconds: f64,
63    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
64        Box::pin(async move {
65            if seconds > 0.0 {
66                tokio::time::sleep(std::time::Duration::from_secs_f64(seconds)).await;
67            }
68        })
69    }
70}
71
72/// A fake clock for tests: time moves only when you tell it to; sleeping just advances it.
73/// Use `1000.0` as the start to match the other languages' default.
74#[derive(Debug)]
75pub struct ManualClock {
76    now: Mutex<f64>,
77}
78
79impl ManualClock {
80    /// Returns a manual clock starting at `start` seconds.
81    pub fn new(start: f64) -> Self {
82        Self {
83            now: Mutex::new(start),
84        }
85    }
86
87    /// Moves the clock forward by `seconds`.
88    pub fn advance(&self, seconds: f64) {
89        *self.now.lock().unwrap() += seconds;
90    }
91}
92
93impl Clock for ManualClock {
94    fn monotonic(&self) -> f64 {
95        *self.now.lock().unwrap()
96    }
97
98    fn sleep(&self, seconds: f64) {
99        self.advance(seconds);
100    }
101
102    #[cfg(feature = "tokio")]
103    fn sleep_async<'a>(
104        &'a self,
105        seconds: f64,
106    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + 'a>> {
107        self.advance(seconds);
108        // Yield like asyncio.sleep(0): a cancellation point without real waiting.
109        Box::pin(async {})
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn system_clock_advances_and_sleeps() {
119        let c = SystemClock::new();
120        let t0 = c.monotonic();
121        assert!(t0 >= 0.0);
122        c.sleep(0.0); // no-op branch
123        c.sleep(-1.0); // no-op branch
124        c.sleep(0.01);
125        assert!(c.monotonic() >= t0);
126        // Default + Debug.
127        let _ = SystemClock::default();
128        assert!(format!("{c:?}").contains("SystemClock"));
129    }
130
131    #[test]
132    fn manual_clock_is_controllable() {
133        let c = ManualClock::new(1000.0);
134        assert_eq!(c.monotonic(), 1000.0);
135        c.advance(5.0);
136        assert_eq!(c.monotonic(), 1005.0);
137        c.sleep(2.5);
138        assert_eq!(c.monotonic(), 1007.5);
139        assert!(format!("{c:?}").contains("ManualClock"));
140    }
141
142    #[cfg(feature = "tokio")]
143    #[tokio::test]
144    async fn sleep_async_system_and_manual() {
145        let s = SystemClock::new();
146        s.sleep_async(0.0).await; // no-op branch
147        s.sleep_async(0.01).await; // real timer branch
148        let m = ManualClock::new(0.0);
149        m.sleep_async(5.0).await; // advances logical time, returns immediately
150        assert_eq!(m.monotonic(), 5.0);
151    }
152}