Skip to main content

yo_kv/
clock.rs

1//! The coarse clock expiry compares against.
2//!
3//! `04` section 5 is explicit about this: there is no global clock read on the
4//! data path. The shard reads the clock once per turn of the loop and every
5//! command in that batch of 64 compares against the same number. A clock read
6//! is a vDSO call, so it is not a syscall, but it is still tens of nanoseconds
7//! against a budget of a hundred and fifty for the whole command, and paying it
8//! per command would mean paying it 64 times for one answer that did not change.
9//!
10//! `TIME` and `EXPIRETIME` read the fine clock instead, because their contract
11//! is to report the time and not to compare against it. That is what
12//! [`Clock::fine_now_ms`] is for and it is the only thing that should call it.
13//!
14//! A fixed clock is not a testing convenience bolted on the side. Expiry is the
15//! one part of a database whose behaviour is a function of the wall clock, and a
16//! test that sleeps to move time forward is a test that is slow and flaky at the
17//! same time. Every expiry test in this crate drives a fixed clock instead.
18
19use std::sync::Arc;
20use std::sync::atomic::AtomicU64;
21use std::sync::atomic::Ordering::Relaxed;
22use std::time::{SystemTime, UNIX_EPOCH};
23
24/// Where a clock takes its readings from.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26enum Source {
27    /// The operating system, read on every [`Clock::refresh`].
28    System,
29    /// Whatever the owner last set, and nothing else.
30    Fixed,
31}
32
33/// A millisecond clock that only moves when it is told to.
34///
35/// A handle and not a value. Cloning one gives a second handle onto the same
36/// reading, which is what lets every stripe of every database on a server read
37/// the time a command is being judged against without any of them being told
38/// separately. Moving the clock is one store however many stripes there are.
39///
40/// # Why a shared reading is not a shared line
41///
42/// It is read on every command on every thread, so the obvious worry is a line
43/// that bounces between cores. It does not, because a refresh only writes when
44/// the millisecond has actually changed and a turn of the loop is a few
45/// microseconds. The reading changes about a thousand times a second and is
46/// read millions of times, so the line sits in every core's cache in the shared
47/// state and the writer disturbs it about as often as a timer would.
48#[derive(Debug, Clone)]
49pub struct Clock {
50    now_ms: Arc<AtomicU64>,
51    source: Source,
52}
53
54impl Clock {
55    /// A clock that follows the system, read once now.
56    pub fn system() -> Clock {
57        Clock::at(Clock::fine_now_ms(), Source::System)
58    }
59
60    /// A clock that reads `ms` until somebody moves it.
61    pub fn fixed(ms: u64) -> Clock {
62        Clock::at(ms, Source::Fixed)
63    }
64
65    /// A clock reading `ms` from `source`.
66    fn at(ms: u64, source: Source) -> Clock {
67        Clock {
68            now_ms: Arc::new(AtomicU64::new(ms)),
69            source,
70        }
71    }
72
73    /// The current reading, in milliseconds since the unix epoch.
74    #[inline]
75    pub fn now_ms(&self) -> u64 {
76        self.now_ms.load(Relaxed)
77    }
78
79    /// Take a new reading, which a system clock does from the operating system
80    /// and a fixed clock does not do at all.
81    ///
82    /// Called once per turn of the shard loop, from the maintenance slice. The
83    /// store only happens when the millisecond has changed, which is what keeps
84    /// a reading every thread is looking at from being a line every thread is
85    /// fighting over.
86    ///
87    /// Shared and not exclusive, because on a server running more than one
88    /// thread every one of them turns a loop and every one of them refreshes.
89    /// Two threads that read the operating system a nanosecond apart write the
90    /// same millisecond, and a thread that reads the field between the two
91    /// stores gets that millisecond either way.
92    #[inline]
93    pub fn refresh(&self) {
94        if self.source == Source::System {
95            let ms = Clock::fine_now_ms();
96            if self.now_ms() != ms {
97                self.now_ms.store(ms, Relaxed);
98            }
99        }
100    }
101
102    /// Move the clock to `ms` by hand.
103    ///
104    /// On a system clock the next [`Clock::refresh`] will overwrite this, so it
105    /// is only meaningful on a fixed one.
106    #[inline]
107    pub fn set(&self, ms: u64) {
108        self.now_ms.store(ms, Relaxed);
109    }
110
111    /// Move a clock forward by `ms`.
112    ///
113    /// A read and a store and not an add, because it saturates. Nothing moves a
114    /// clock this way but a test, which is one thread.
115    #[inline]
116    pub fn advance(&self, ms: u64) {
117        self.set(self.now_ms().saturating_add(ms));
118    }
119
120    /// The time now in microseconds, which is what a report of the time needs
121    /// and what a comparison against it does not.
122    ///
123    /// A system clock reads the operating system, because the whole point of
124    /// asking for microseconds is that the coarse reading is not precise enough
125    /// to be worth having. A fixed clock answers its own reading scaled up, so
126    /// that a test which put the clock somewhere sees it there and every line
127    /// stamped in one batch carries the same time.
128    #[inline]
129    pub fn now_us(&self) -> u64 {
130        match self.source {
131            Source::System => SystemTime::now()
132                .duration_since(UNIX_EPOCH)
133                .map_or(0, |d| d.as_micros() as u64),
134            Source::Fixed => self.now_ms().saturating_mul(1_000),
135        }
136    }
137
138    /// Read the operating system's clock right now.
139    ///
140    /// A time before the unix epoch reads as zero rather than failing. There is
141    /// nothing useful a database can do about a machine whose clock says 1969,
142    /// and every key expiring immediately is a more honest outcome than a panic
143    /// on a path that has no error to return.
144    #[inline]
145    pub fn fine_now_ms() -> u64 {
146        SystemTime::now()
147            .duration_since(UNIX_EPOCH)
148            .map_or(0, |d| d.as_millis() as u64)
149    }
150}
151
152impl Default for Clock {
153    fn default() -> Clock {
154        Clock::system()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn a_fixed_clock_stays_where_it_is_put() {
164        let c = Clock::fixed(1_000);
165        assert_eq!(c.now_ms(), 1_000);
166        c.refresh();
167        assert_eq!(c.now_ms(), 1_000, "refresh moved a fixed clock");
168        c.advance(500);
169        assert_eq!(c.now_ms(), 1_500);
170        c.set(7);
171        assert_eq!(c.now_ms(), 7);
172    }
173
174    #[test]
175    fn a_fixed_clock_reads_microseconds_off_its_own_reading() {
176        let c = Clock::fixed(1_700_000_000_123);
177        assert_eq!(c.now_us(), 1_700_000_000_123_000);
178        c.advance(1);
179        assert_eq!(c.now_us(), 1_700_000_000_124_000);
180    }
181
182    #[test]
183    fn a_system_clock_reads_microseconds_around_where_its_milliseconds_are() {
184        let c = Clock::system();
185        c.refresh();
186        let us = c.now_us();
187        assert!(
188            us / 1_000 >= c.now_ms() && us / 1_000 <= c.now_ms() + 1_000,
189            "{us} microseconds against {} milliseconds",
190            c.now_ms()
191        );
192    }
193
194    #[test]
195    fn a_system_clock_reads_a_plausible_time() {
196        let c = Clock::system();
197        // 2020-01-01, which this build is comfortably after.
198        assert!(c.now_ms() > 1_577_836_800_000, "clock read {}", c.now_ms());
199    }
200
201    #[test]
202    fn a_system_clock_does_not_move_until_it_is_refreshed() {
203        let c = Clock::system();
204        let first = c.now_ms();
205        // Busy work rather than a sleep, because the point is that the reading
206        // is stable across it and a sleep would only make the test slow.
207        let mut spin = 0u64;
208        for i in 0..200_000u64 {
209            spin = spin.wrapping_add(i);
210        }
211        assert_eq!(c.now_ms(), first, "the clock moved on its own {spin}");
212        c.refresh();
213        assert!(c.now_ms() >= first);
214    }
215
216    /// Two handles onto one clock are one clock, which is what lets a database
217    /// hand the same reading to every stripe it was cut into.
218    #[test]
219    fn a_cloned_clock_reads_what_the_original_was_moved_to() {
220        let one = Clock::fixed(1_000);
221        let two = one.clone();
222        one.advance(500);
223        assert_eq!(two.now_ms(), 1_500);
224        two.set(9);
225        assert_eq!(one.now_ms(), 9);
226    }
227
228    #[test]
229    fn advancing_past_the_end_of_time_stops_there() {
230        let c = Clock::fixed(u64::MAX - 1);
231        c.advance(10);
232        assert_eq!(c.now_ms(), u64::MAX);
233    }
234}