moka/common/time/
clock.rs

1use std::time::{Duration, Instant as StdInstant};
2
3#[cfg(test)]
4use std::sync::Arc;
5
6#[cfg(test)]
7use parking_lot::RwLock;
8
9// This is `moka`'s `Instant` struct.
10use super::Instant;
11
12#[derive(Default, Clone)]
13pub(crate) struct Clock {
14    ty: ClockType,
15}
16
17#[derive(Clone)]
18enum ClockType {
19    /// A clock that uses `std::time::Instant` as the source of time.
20    Standard { origin: StdInstant },
21    #[cfg(feature = "quanta")]
22    /// A clock that uses both `std::time::Instant` and `quanta::Instant` as the
23    /// sources of time.
24    Hybrid {
25        std_origin: StdInstant,
26        quanta_origin: quanta::Instant,
27    },
28    #[cfg(test)]
29    /// A clock that uses a mocked source of time.
30    Mocked { mock: Arc<Mock> },
31}
32
33impl Default for ClockType {
34    /// Create a new `ClockType` with the current time as the origin.
35    ///
36    /// If the `quanta` feature is enabled, `Hybrid` will be used. Otherwise,
37    /// `Standard` will be used.
38    fn default() -> Self {
39        #[cfg(feature = "quanta")]
40        {
41            return ClockType::Hybrid {
42                std_origin: StdInstant::now(),
43                quanta_origin: quanta::Instant::now(),
44            };
45        }
46
47        #[allow(unreachable_code)]
48        ClockType::Standard {
49            origin: StdInstant::now(),
50        }
51    }
52}
53
54impl Clock {
55    #[cfg(test)]
56    /// Creates a new `Clock` with a mocked source of time.
57    pub(crate) fn mock() -> (Clock, Arc<Mock>) {
58        let mock = Arc::new(Mock::default());
59        let clock = Clock {
60            ty: ClockType::Mocked {
61                mock: Arc::clone(&mock),
62            },
63        };
64        (clock, mock)
65    }
66
67    /// Returns the current time using a reliable source of time.
68    ///
69    /// When the the type is `Standard` or `Hybrid`, the time is based on
70    /// `std::time::Instant`. When the type is `Mocked`, the time is based on the
71    /// mocked source of time.
72    pub(crate) fn now(&self) -> Instant {
73        match &self.ty {
74            ClockType::Standard { origin } => {
75                Instant::from_duration_since_clock_start(origin.elapsed())
76            }
77            #[cfg(feature = "quanta")]
78            ClockType::Hybrid { std_origin, .. } => {
79                Instant::from_duration_since_clock_start(std_origin.elapsed())
80            }
81            #[cfg(test)]
82            ClockType::Mocked { mock } => Instant::from_duration_since_clock_start(mock.elapsed()),
83        }
84    }
85
86    /// Returns the current time _maybe_ using a fast but less reliable source of
87    /// time. The time may drift from the time returned by `now`, or not be
88    /// monotonically increasing.
89    ///
90    /// This is useful for performance critical code that does not require the same
91    /// level of precision as `now`. (e.g. measuring the time between two events for
92    /// metrics)
93    ///
94    /// When the type is `Standard` or `Mocked`, `now` is internally called. So there
95    /// is no performance benefit.
96    ///
97    /// When the type is `Hybrid`, the time is based on `quanta::Instant`, which can
98    /// be faster than `std::time::Instant`, depending on the CPU architecture.
99    pub(crate) fn fast_now(&self) -> Instant {
100        match &self.ty {
101            #[cfg(feature = "quanta")]
102            ClockType::Hybrid { quanta_origin, .. } => {
103                Instant::from_duration_since_clock_start(quanta_origin.elapsed())
104            }
105            ClockType::Standard { .. } => self.now(),
106            #[cfg(test)]
107            ClockType::Mocked { .. } => self.now(),
108        }
109    }
110
111    /// Converts an `Instant` to a `std::time::Instant`.
112    ///
113    /// **IMPORTANT**: The caller must ensure that the `Instant` was created by this
114    /// `Clock`, otherwise the resulting `std::time::Instant` will be incorrect.
115    pub(crate) fn to_std_instant(&self, instant: Instant) -> StdInstant {
116        match &self.ty {
117            ClockType::Standard { origin } => {
118                let duration = Duration::from_nanos(instant.as_nanos());
119                *origin + duration
120            }
121            #[cfg(feature = "quanta")]
122            ClockType::Hybrid { std_origin, .. } => {
123                let duration = Duration::from_nanos(instant.as_nanos());
124                *std_origin + duration
125            }
126            #[cfg(test)]
127            ClockType::Mocked { mock } => {
128                let duration = Duration::from_nanos(instant.as_nanos());
129                mock.origin + duration
130            }
131        }
132    }
133}
134
135#[cfg(test)]
136pub(crate) struct Mock {
137    origin: StdInstant,
138    now: RwLock<StdInstant>,
139}
140
141#[cfg(test)]
142impl Default for Mock {
143    fn default() -> Self {
144        let origin = StdInstant::now();
145        Self {
146            origin,
147            now: RwLock::new(origin),
148        }
149    }
150}
151
152#[cfg(test)]
153impl Mock {
154    pub(crate) fn increment(&self, amount: Duration) {
155        *self.now.write() += amount;
156    }
157
158    pub(crate) fn elapsed(&self) -> Duration {
159        self.now.read().duration_since(self.origin)
160    }
161}