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::time::{SystemTime, UNIX_EPOCH};
20
21/// Where a clock takes its readings from.
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23enum Source {
24 /// The operating system, read on every [`Clock::refresh`].
25 System,
26 /// Whatever the owner last set, and nothing else.
27 Fixed,
28}
29
30/// A millisecond clock that only moves when it is told to.
31#[derive(Debug, Clone, Copy)]
32pub struct Clock {
33 now_ms: u64,
34 source: Source,
35}
36
37impl Clock {
38 /// A clock that follows the system, read once now.
39 pub fn system() -> Clock {
40 Clock {
41 now_ms: Clock::fine_now_ms(),
42 source: Source::System,
43 }
44 }
45
46 /// A clock that reads `ms` until somebody moves it.
47 pub const fn fixed(ms: u64) -> Clock {
48 Clock {
49 now_ms: ms,
50 source: Source::Fixed,
51 }
52 }
53
54 /// The current reading, in milliseconds since the unix epoch.
55 #[inline]
56 pub const fn now_ms(&self) -> u64 {
57 self.now_ms
58 }
59
60 /// Take a new reading, which a system clock does from the operating system
61 /// and a fixed clock does not do at all.
62 ///
63 /// Called once per turn of the shard loop, from the maintenance slice.
64 #[inline]
65 pub fn refresh(&mut self) {
66 if self.source == Source::System {
67 self.now_ms = Clock::fine_now_ms();
68 }
69 }
70
71 /// Move the clock to `ms` by hand.
72 ///
73 /// On a system clock the next [`Clock::refresh`] will overwrite this, so it
74 /// is only meaningful on a fixed one.
75 #[inline]
76 pub const fn set(&mut self, ms: u64) {
77 self.now_ms = ms;
78 }
79
80 /// Move a clock forward by `ms`.
81 #[inline]
82 pub const fn advance(&mut self, ms: u64) {
83 self.now_ms = self.now_ms.saturating_add(ms);
84 }
85
86 /// Read the operating system's clock right now.
87 ///
88 /// A time before the unix epoch reads as zero rather than failing. There is
89 /// nothing useful a database can do about a machine whose clock says 1969,
90 /// and every key expiring immediately is a more honest outcome than a panic
91 /// on a path that has no error to return.
92 #[inline]
93 pub fn fine_now_ms() -> u64 {
94 SystemTime::now()
95 .duration_since(UNIX_EPOCH)
96 .map_or(0, |d| d.as_millis() as u64)
97 }
98}
99
100impl Default for Clock {
101 fn default() -> Clock {
102 Clock::system()
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn a_fixed_clock_stays_where_it_is_put() {
112 let mut c = Clock::fixed(1_000);
113 assert_eq!(c.now_ms(), 1_000);
114 c.refresh();
115 assert_eq!(c.now_ms(), 1_000, "refresh moved a fixed clock");
116 c.advance(500);
117 assert_eq!(c.now_ms(), 1_500);
118 c.set(7);
119 assert_eq!(c.now_ms(), 7);
120 }
121
122 #[test]
123 fn a_system_clock_reads_a_plausible_time() {
124 let c = Clock::system();
125 // 2020-01-01, which this build is comfortably after.
126 assert!(c.now_ms() > 1_577_836_800_000, "clock read {}", c.now_ms());
127 }
128
129 #[test]
130 fn a_system_clock_does_not_move_until_it_is_refreshed() {
131 let mut c = Clock::system();
132 let first = c.now_ms();
133 // Busy work rather than a sleep, because the point is that the reading
134 // is stable across it and a sleep would only make the test slow.
135 let mut spin = 0u64;
136 for i in 0..200_000u64 {
137 spin = spin.wrapping_add(i);
138 }
139 assert_eq!(c.now_ms(), first, "the clock moved on its own {spin}");
140 c.refresh();
141 assert!(c.now_ms() >= first);
142 }
143
144 #[test]
145 fn advancing_past_the_end_of_time_stops_there() {
146 let mut c = Clock::fixed(u64::MAX - 1);
147 c.advance(10);
148 assert_eq!(c.now_ms(), u64::MAX);
149 }
150}