cratefield_core/ports/clock.rs
1//! The `Clock` port (architecture section 5): wall time plus a timeout
2//! facility so core can bound work without depending on a timer runtime.
3//!
4//! The timeout is supplied by the runtime's clock because core has no
5//! timer of its own: Workers implements it with `setTimeout`, the native
6//! runtime (phase 3) with tokio's timer. The default implementation runs
7//! the future to completion and ignores the deadline — acceptable only in
8//! tests, and documented as such.
9
10use async_trait::async_trait;
11use futures_core::future::BoxFuture;
12use std::any::Any;
13use std::time::Duration;
14
15#[async_trait]
16pub trait Clock: Send + Sync {
17 fn now(&self) -> time::OffsetDateTime;
18
19 /// Runs `fut` to completion, abandoning it after `after` elapses.
20 /// Returns `None` on timeout. Default: runs to completion (no timer;
21 /// test-only — runtimes override).
22 async fn timeout_any(
23 &self,
24 fut: BoxFuture<'static, Box<dyn Any + Send>>,
25 after: Duration,
26 ) -> Option<Box<dyn Any + Send>> {
27 let _ = after;
28 Some(fut.await)
29 }
30}
31
32/// Typed wrapper over [`Clock::timeout_any`]. `None` means the clock
33/// abandoned the future after `after`.
34pub async fn timeout<T: Send + 'static>(
35 clock: &dyn Clock,
36 fut: impl Future<Output = T> + Send + 'static,
37 after: Duration,
38) -> Option<T> {
39 let boxed: BoxFuture<'static, Box<dyn Any + Send>> =
40 Box::pin(async move { Box::new(fut.await) as Box<dyn Any + Send> });
41 match clock.timeout_any(boxed, after).await {
42 Some(any) => any.downcast::<T>().ok().map(|v| *v),
43 None => None,
44 }
45}
46
47/// Real wall clock (`time` crate). Its timeout runs futures to completion —
48/// tests that need a real timeout supply their own clock.
49#[derive(Debug, Clone, Copy, Default)]
50pub struct SystemClock;
51
52#[async_trait]
53impl Clock for SystemClock {
54 fn now(&self) -> time::OffsetDateTime {
55 time::OffsetDateTime::now_utc()
56 }
57}