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