kayrx_timer/clock.rs
1//! Source of time abstraction.
2//!
3//! By default, `std::time::Instant::now()` is used.
4
5use crate::Instant;
6
7#[derive(Debug, Clone)]
8pub(crate) struct Clock {}
9
10pub(crate) fn now() -> Instant {
11 Instant::from_std(std::time::Instant::now())
12}
13
14impl Clock {
15 pub(crate) fn new() -> Clock {
16 Clock {}
17 }
18
19 pub(crate) fn now(&self) -> Instant {
20 now()
21 }
22
23 pub(crate) fn enter<F, R>(&self, f: F) -> R
24 where
25 F: FnOnce() -> R,
26 {
27 f()
28 }
29}
30
31
32// Test Utils
33
34pub mod clock_util {
35 use crate::{Duration, Instant};
36
37 use std::cell::Cell;
38 use std::sync::{Arc, Mutex};
39
40 /// A handle to a source of time.
41 #[derive(Debug, Clone)]
42 pub(crate) struct Clock {
43 inner: Arc<Inner>,
44 }
45
46 #[derive(Debug)]
47 struct Inner {
48 /// Instant at which the clock was created
49 start: std::time::Instant,
50
51 /// Current, "frozen" time as an offset from `start`.
52 frozen: Mutex<Option<Duration>>,
53 }
54
55 thread_local! {
56 /// Thread-local tracking the current clock
57 static CLOCK: Cell<Option<*const Clock>> = Cell::new(None)
58 }
59
60 /// Pause time
61 ///
62 /// The current value of `Instant::now()` is saved and all subsequent calls
63 /// to `Instant::now()` will return the saved value. This is useful for
64 /// running tests that are dependent on time.
65 ///
66 /// # Panics
67 ///
68 /// Panics if time is already frozen or if called from outside of the kayrx::krse
69 /// runtime.
70 pub fn pause() {
71 CLOCK.with(|cell| {
72 let ptr = match cell.get() {
73 Some(ptr) => ptr,
74 None => panic!("time cannot be frozen from outside the kayrx::krse runtime"),
75 };
76
77 let clock = unsafe { &*ptr };
78 let mut frozen = clock.inner.frozen.lock().unwrap();
79
80 if frozen.is_some() {
81 panic!("time is already frozen");
82 }
83
84 *frozen = Some(clock.inner.start.elapsed());
85 })
86 }
87
88 /// Resume time
89 ///
90 /// Clears the saved `Instant::now()` value. Subsequent calls to
91 /// `Instant::now()` will return the value returned by the system call.
92 ///
93 /// # Panics
94 ///
95 /// Panics if time is not frozen or if called from outside of the kayrx::krse
96 /// runtime.
97 pub fn resume() {
98 CLOCK.with(|cell| {
99 let ptr = match cell.get() {
100 Some(ptr) => ptr,
101 None => panic!("time cannot be frozen from outside the kayrx::krse runtime"),
102 };
103
104 let clock = unsafe { &*ptr };
105 let mut frozen = clock.inner.frozen.lock().unwrap();
106
107 if frozen.is_none() {
108 panic!("time is not frozen");
109 }
110
111 *frozen = None;
112 })
113 }
114
115 /// Advance time
116 ///
117 /// Increments the saved `Instant::now()` value by `duration`. Subsequent
118 /// calls to `Instant::now()` will return the result of the increment.
119 ///
120 /// # Panics
121 ///
122 /// Panics if time is not frozen or if called from outside of the kayrx::krse
123 /// runtime.
124 // pub async fn advance(duration: Duration) {
125 // CLOCK.with(|cell| {
126 // let ptr = match cell.get() {
127 // Some(ptr) => ptr,
128 // None => panic!("time cannot be frozen from outside the kayrx::krse runtime"),
129 // };
130
131 // let clock = unsafe { &*ptr };
132 // clock.advance(duration);
133 // });
134
135 // crate::task::yield_now().await;
136 // }
137
138 /// Return the current instant, factoring in frozen time.
139 pub(crate) fn now() -> Instant {
140 CLOCK.with(|cell| {
141 Instant::from_std(match cell.get() {
142 Some(ptr) => {
143 let clock = unsafe { &*ptr };
144
145 if let Some(frozen) = *clock.inner.frozen.lock().unwrap() {
146 clock.inner.start + frozen
147 } else {
148 std::time::Instant::now()
149 }
150 }
151 None => std::time::Instant::now(),
152 })
153 })
154 }
155
156 impl Clock {
157 /// Return a new `Clock` instance that uses the current execution context's
158 /// source of time.
159 pub(crate) fn new() -> Clock {
160 Clock {
161 inner: Arc::new(Inner {
162 start: std::time::Instant::now(),
163 frozen: Mutex::new(None),
164 }),
165 }
166 }
167
168 // TODO: delete this. Some tests rely on this
169 /// Return a new `Clock` instance that uses the current execution context's
170 /// source of time.
171 pub(crate) fn new_frozen() -> Clock {
172 Clock {
173 inner: Arc::new(Inner {
174 start: std::time::Instant::now(),
175 frozen: Mutex::new(Some(Duration::from_millis(0))),
176 }),
177 }
178 }
179
180 pub(crate) fn advance(&self, duration: Duration) {
181 let mut frozen = self.inner.frozen.lock().unwrap();
182
183 if let Some(ref mut elapsed) = *frozen {
184 *elapsed += duration;
185 } else {
186 panic!("time is not frozen");
187 }
188 }
189
190 // TODO: delete this as well
191 pub(crate) fn advanced(&self) -> Duration {
192 self.inner.frozen.lock().unwrap().unwrap()
193 }
194
195 pub(crate) fn now(&self) -> Instant {
196 Instant::from_std(if let Some(frozen) = *self.inner.frozen.lock().unwrap() {
197 self.inner.start + frozen
198 } else {
199 std::time::Instant::now()
200 })
201 }
202
203 /// Set the clock as the default source of time for the duration of the
204 /// closure
205 pub(crate) fn enter<F, R>(&self, f: F) -> R
206 where
207 F: FnOnce() -> R,
208 {
209 CLOCK.with(|cell| {
210 assert!(
211 cell.get().is_none(),
212 "default clock already set for execution context"
213 );
214
215 // Ensure that the clock is removed from the thread-local context
216 // when leaving the scope. This handles cases that involve panicking.
217 struct Reset<'a>(&'a Cell<Option<*const Clock>>);
218
219 impl Drop for Reset<'_> {
220 fn drop(&mut self) {
221 self.0.set(None);
222 }
223 }
224
225 let _reset = Reset(cell);
226
227 cell.set(Some(self as *const Clock));
228
229 f()
230 })
231 }
232 }
233}