1#[cfg(any(test, feature = "test-support"))]
12pub mod test_support;
13
14use std::fmt;
15use std::sync::Arc;
16use std::time::Duration;
17
18use async_trait::async_trait;
19use parking_lot::Mutex;
20use serde::{Deserialize, Serialize};
21use time::OffsetDateTime;
22use tokio::sync::Notify;
23
24#[async_trait]
31pub trait Clock: Send + Sync + fmt::Debug {
32 fn now_utc(&self) -> OffsetDateTime;
34
35 fn monotonic_ms(&self) -> i64;
41
42 async fn sleep(&self, duration: Duration);
44
45 async fn sleep_until_utc(&self, deadline: OffsetDateTime);
48}
49
50pub fn offset_datetime_to_ms(ts: OffsetDateTime) -> i64 {
54 (ts.unix_timestamp_nanos() / 1_000_000) as i64
55}
56
57pub fn now_wall_ms(clock: &dyn Clock) -> i64 {
59 offset_datetime_to_ms(clock.now_utc())
60}
61
62const EPOCH_RFC3339: &str = "1970-01-01T00:00:00Z";
68
69pub fn format_rfc3339(ts: OffsetDateTime) -> String {
78 ts.format(&time::format_description::well_known::Rfc3339)
79 .unwrap_or_else(|_| EPOCH_RFC3339.to_string())
80}
81
82pub fn now_rfc3339(clock: &dyn Clock) -> String {
87 format_rfc3339(clock.now_utc())
88}
89
90pub fn system_now_rfc3339() -> String {
97 format_rfc3339(OffsetDateTime::now_utc())
98}
99
100pub struct RealClock {
106 monotonic_origin: tokio::time::Instant,
107}
108
109impl Default for RealClock {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115impl fmt::Debug for RealClock {
116 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117 f.debug_struct("RealClock").finish()
118 }
119}
120
121impl RealClock {
122 pub fn new() -> Self {
123 Self {
124 monotonic_origin: tokio::time::Instant::now(),
125 }
126 }
127
128 pub fn arc() -> Arc<dyn Clock> {
130 Arc::new(Self::new())
131 }
132}
133
134#[async_trait]
135impl Clock for RealClock {
136 fn now_utc(&self) -> OffsetDateTime {
137 OffsetDateTime::now_utc()
138 }
139
140 fn monotonic_ms(&self) -> i64 {
141 let elapsed = tokio::time::Instant::now().saturating_duration_since(self.monotonic_origin);
142 elapsed.as_millis() as i64
143 }
144
145 async fn sleep(&self, duration: Duration) {
146 if duration.is_zero() {
147 return;
148 }
149 tokio::time::sleep(duration).await;
150 }
151
152 async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
153 let now = self.now_utc();
154 if deadline <= now {
155 return;
156 }
157 let delta = deadline - now;
158 let Ok(duration) = Duration::try_from(delta) else {
159 return;
160 };
161 tokio::time::sleep(duration).await;
162 }
163}
164
165pub struct PausedClock {
178 state: Mutex<PausedState>,
179 notify: Notify,
180}
181
182struct PausedState {
183 wall: OffsetDateTime,
184 monotonic: Duration,
185}
186
187impl fmt::Debug for PausedClock {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 let state = self.state.lock();
190 f.debug_struct("PausedClock")
191 .field("wall", &state.wall)
192 .field("monotonic_ms", &state.monotonic.as_millis())
193 .finish()
194 }
195}
196
197impl PausedClock {
198 pub fn new(origin: OffsetDateTime) -> Arc<Self> {
200 Arc::new(Self {
201 state: Mutex::new(PausedState {
202 wall: origin,
203 monotonic: Duration::ZERO,
204 }),
205 notify: Notify::new(),
206 })
207 }
208
209 pub fn advance(&self, duration: Duration) {
212 let delta = match time::Duration::try_from(duration) {
213 Ok(value) => value,
214 Err(_) => return,
215 };
216 {
217 let mut state = self.state.lock();
218 state.wall += delta;
219 state.monotonic = state.monotonic.saturating_add(duration);
220 }
221 self.notify.notify_waiters();
222 }
223
224 pub fn advance_time(&self, duration: time::Duration) {
226 let Ok(positive) = Duration::try_from(duration) else {
227 return;
228 };
229 self.advance(positive);
230 }
231
232 pub fn advance_ticks(&self, ticks: u32, tick: Duration) {
235 for _ in 0..ticks {
236 self.advance(tick);
237 }
238 }
239
240 pub fn set(&self, wall: OffsetDateTime) {
243 {
244 let mut state = self.state.lock();
245 let delta = wall - state.wall;
246 state.wall = wall;
247 if delta.is_positive() {
248 if let Ok(positive) = Duration::try_from(delta) {
249 state.monotonic = state.monotonic.saturating_add(positive);
250 }
251 }
252 }
253 self.notify.notify_waiters();
254 }
255}
256
257#[async_trait]
258impl Clock for PausedClock {
259 fn now_utc(&self) -> OffsetDateTime {
260 self.state.lock().wall
261 }
262
263 fn monotonic_ms(&self) -> i64 {
264 self.state.lock().monotonic.as_millis() as i64
265 }
266
267 async fn sleep(&self, duration: Duration) {
268 if duration.is_zero() {
269 return;
270 }
271 let deadline = self.state.lock().wall
272 + time::Duration::try_from(duration).unwrap_or(time::Duration::ZERO);
273 self.sleep_until_utc(deadline).await;
274 }
275
276 async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
277 loop {
278 let notified = self.notify.notified();
279 if self.state.lock().wall >= deadline {
280 return;
281 }
282 notified.await;
283 }
284 }
285}
286
287#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
291#[serde(tag = "kind", rename_all = "snake_case")]
292pub enum ClockEvent {
293 NowUtc { wall_ns: i128 },
294 MonotonicMs { value: i64 },
295 Sleep { duration_ms: u64 },
296 SleepUntil { wall_ns: i128 },
297}
298
299#[derive(Debug, Default)]
301pub struct ClockEventLog {
302 events: Mutex<Vec<ClockEvent>>,
303}
304
305impl ClockEventLog {
306 pub fn new() -> Self {
307 Self::default()
308 }
309
310 pub fn push(&self, event: ClockEvent) {
313 self.events.lock().push(event);
314 }
315
316 pub fn snapshot(&self) -> Vec<ClockEvent> {
318 self.events.lock().clone()
319 }
320
321 pub fn len(&self) -> usize {
323 self.events.lock().len()
324 }
325
326 pub fn is_empty(&self) -> bool {
327 self.len() == 0
328 }
329}
330
331pub struct RecordedClock {
338 inner: Arc<dyn Clock>,
339 log: Arc<ClockEventLog>,
340}
341
342impl fmt::Debug for RecordedClock {
343 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344 f.debug_struct("RecordedClock")
345 .field("inner", &self.inner)
346 .field("events", &self.log.len())
347 .finish()
348 }
349}
350
351impl RecordedClock {
352 pub fn new(inner: Arc<dyn Clock>, log: Arc<ClockEventLog>) -> Self {
353 Self { inner, log }
354 }
355
356 pub fn log(&self) -> Arc<ClockEventLog> {
357 self.log.clone()
358 }
359}
360
361#[async_trait]
362impl Clock for RecordedClock {
363 fn now_utc(&self) -> OffsetDateTime {
364 let value = self.inner.now_utc();
365 self.log.push(ClockEvent::NowUtc {
366 wall_ns: value.unix_timestamp_nanos(),
367 });
368 value
369 }
370
371 fn monotonic_ms(&self) -> i64 {
372 let value = self.inner.monotonic_ms();
373 self.log.push(ClockEvent::MonotonicMs { value });
374 value
375 }
376
377 async fn sleep(&self, duration: Duration) {
378 self.log.push(ClockEvent::Sleep {
379 duration_ms: duration.as_millis() as u64,
380 });
381 self.inner.sleep(duration).await;
382 }
383
384 async fn sleep_until_utc(&self, deadline: OffsetDateTime) {
385 self.log.push(ClockEvent::SleepUntil {
386 wall_ns: deadline.unix_timestamp_nanos(),
387 });
388 self.inner.sleep_until_utc(deadline).await;
389 }
390}
391
392#[cfg(test)]
395mod tests {
396 use super::*;
397
398 fn epoch() -> OffsetDateTime {
399 OffsetDateTime::from_unix_timestamp(1_700_000_000).unwrap()
400 }
401
402 #[tokio::test]
403 async fn real_clock_returns_increasing_monotonic() {
404 let clock = RealClock::new();
405 let a = clock.monotonic_ms();
406 tokio::task::yield_now().await;
407 let b = clock.monotonic_ms();
408 assert!(b >= a, "monotonic must not move backwards");
409 }
410
411 #[tokio::test]
412 async fn paused_clock_pins_wall_and_monotonic_until_advanced() {
413 let clock = PausedClock::new(epoch());
414 assert_eq!(clock.now_utc(), epoch());
415 assert_eq!(clock.monotonic_ms(), 0);
416 clock.advance(Duration::from_millis(250));
417 assert_eq!(clock.monotonic_ms(), 250);
418 assert_eq!(clock.now_utc(), epoch() + time::Duration::milliseconds(250));
419 }
420
421 #[tokio::test]
422 async fn paused_clock_sleep_resumes_after_advance() {
423 let clock = PausedClock::new(epoch());
424 let clock_for_sleep = clock.clone();
425 let task = tokio::spawn(async move {
426 clock_for_sleep.sleep(Duration::from_secs(5)).await;
427 });
428 tokio::task::yield_now().await;
429 assert!(!task.is_finished(), "sleep should still be pending");
430 clock.advance(Duration::from_secs(10));
431 task.await.expect("sleep task panicked");
432 }
433
434 #[tokio::test]
435 async fn paused_clock_sleep_until_returns_immediately_for_past_deadline() {
436 let clock = PausedClock::new(epoch());
437 clock.advance(Duration::from_mins(1));
438 clock.sleep_until_utc(epoch()).await;
439 }
440
441 #[tokio::test]
442 async fn recorded_clock_appends_one_event_per_call() {
443 let log = Arc::new(ClockEventLog::new());
444 let clock = RecordedClock::new(PausedClock::new(epoch()), log.clone());
445 let _ = clock.now_utc();
446 let _ = clock.monotonic_ms();
447 clock.sleep(Duration::ZERO).await;
448 clock.sleep_until_utc(epoch()).await;
449 let events = log.snapshot();
450 assert_eq!(events.len(), 4);
451 assert!(matches!(events[0], ClockEvent::NowUtc { .. }));
452 assert!(matches!(events[1], ClockEvent::MonotonicMs { .. }));
453 assert!(matches!(events[2], ClockEvent::Sleep { duration_ms: 0 }));
454 assert!(matches!(events[3], ClockEvent::SleepUntil { .. }));
455 }
456
457 #[tokio::test]
458 async fn now_wall_ms_helper_matches_clock_observation() {
459 let clock = PausedClock::new(epoch());
460 let observed = now_wall_ms(clock.as_ref());
461 let expected = epoch().unix_timestamp_nanos() / 1_000_000;
462 assert_eq!(observed, expected as i64);
463 }
464
465 #[tokio::test]
466 async fn now_rfc3339_is_deterministic_under_a_paused_clock() {
467 let clock = PausedClock::new(epoch());
470 assert_eq!(now_rfc3339(clock.as_ref()), "2023-11-14T22:13:20Z");
471 clock.advance(Duration::from_secs(1));
472 assert_eq!(now_rfc3339(clock.as_ref()), "2023-11-14T22:13:21Z");
473 }
474
475 #[test]
476 fn system_now_rfc3339_is_parseable_back() {
477 let rendered = system_now_rfc3339();
478 OffsetDateTime::parse(&rendered, &time::format_description::well_known::Rfc3339)
479 .expect("system_now_rfc3339 must emit valid RFC3339");
480 }
481}