Skip to main content

vertep_runtime/
shell.rs

1//! The shell traits: the only way the runtime spawns work, reads time or waits.
2//!
3//! The core (`vertep-core`) reads no clock and spawns nothing. The runtime shell does both, but
4//! only through [`Spawn`], [`Clock`] and [`Sleep`], so that the same runtime runs on tokio
5//! ([`Shell::tokio`]), on a deterministic virtual clock in tests ([`Shell::virtual_time`]) and,
6//! later, in the browser.
7
8use std::future::Future;
9use std::pin::Pin;
10use std::sync::{Arc, Mutex};
11use std::task::{Context, Poll, Waker};
12use std::time::{Duration, SystemTime, UNIX_EPOCH};
13
14/// A boxed, sendable future.
15pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
16
17/// Runs a future to completion in the background.
18pub trait Spawn: Send + Sync {
19    /// Starts `fut`; its output is discarded.
20    fn spawn(&self, fut: BoxFuture<()>);
21}
22
23/// The time source of the shell. Only the shell reads it: a macrostep sees the time stamped on
24/// its input when the input was dequeued, which is journaled.
25pub trait Clock: Send + Sync {
26    /// Milliseconds since the Unix epoch.
27    fn now_ms(&self) -> i64;
28}
29
30/// Waits until a point in time of the shell's [`Clock`].
31pub trait Sleep: Send + Sync {
32    /// A future that completes once the clock reads at least `at_ms`.
33    fn sleep_until(&self, at_ms: i64) -> BoxFuture<()>;
34}
35
36/// The three shell services a runtime node uses.
37#[derive(Clone)]
38pub struct Shell {
39    /// Background work: function actors, stream actors and stateless workers.
40    pub spawn: Arc<dyn Spawn>,
41    /// The time source.
42    pub clock: Arc<dyn Clock>,
43    /// Waiting for timers, idle timeouts and ask timeouts.
44    pub sleep: Arc<dyn Sleep>,
45}
46
47impl std::fmt::Debug for Shell {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("Shell")
50            .field("now_ms", &self.clock.now_ms())
51            .finish_non_exhaustive()
52    }
53}
54
55impl Shell {
56    /// The tokio shell: `tokio::spawn`, the system clock and `tokio::time::sleep`. Spawning
57    /// needs a running tokio runtime.
58    pub fn tokio() -> Self {
59        let clock: Arc<dyn Clock> = Arc::new(SystemClock);
60        Shell {
61            spawn: Arc::new(TokioSpawn),
62            sleep: Arc::new(TokioSleep {
63                clock: clock.clone(),
64            }),
65            clock,
66        }
67    }
68
69    /// A deterministic shell for tests: tokio spawning and a [`VirtualClock`] starting at
70    /// `start_ms` that moves only when advanced.
71    pub fn virtual_time(start_ms: i64) -> (Self, VirtualClock) {
72        let clock = VirtualClock::new(start_ms);
73        let shell = Shell {
74            spawn: Arc::new(TokioSpawn),
75            clock: Arc::new(clock.clone()),
76            sleep: Arc::new(clock.clone()),
77        };
78        (shell, clock)
79    }
80}
81
82/// Spawns on the current tokio runtime.
83#[derive(Clone, Copy, Debug, Default)]
84pub struct TokioSpawn;
85
86impl Spawn for TokioSpawn {
87    fn spawn(&self, fut: BoxFuture<()>) {
88        tokio::spawn(fut);
89    }
90}
91
92/// The system wall clock.
93#[derive(Clone, Copy, Debug, Default)]
94pub struct SystemClock;
95
96impl Clock for SystemClock {
97    fn now_ms(&self) -> i64 {
98        SystemTime::now()
99            .duration_since(UNIX_EPOCH)
100            .map(|d| i64::try_from(d.as_millis()).unwrap_or(i64::MAX))
101            .unwrap_or(0)
102    }
103}
104
105/// Sleeps with `tokio::time::sleep` until a time of the given clock.
106#[derive(Clone)]
107pub struct TokioSleep {
108    clock: Arc<dyn Clock>,
109}
110
111impl TokioSleep {
112    /// A sleeper measuring against `clock`.
113    pub fn new(clock: Arc<dyn Clock>) -> Self {
114        TokioSleep { clock }
115    }
116}
117
118impl std::fmt::Debug for TokioSleep {
119    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120        f.debug_struct("TokioSleep").finish_non_exhaustive()
121    }
122}
123
124impl Sleep for TokioSleep {
125    fn sleep_until(&self, at_ms: i64) -> BoxFuture<()> {
126        let wait = at_ms.saturating_sub(self.clock.now_ms()).max(0);
127        Box::pin(tokio::time::sleep(Duration::from_millis(
128            u64::try_from(wait).unwrap_or(0),
129        )))
130    }
131}
132
133#[derive(Default)]
134struct VirtualInner {
135    now: i64,
136    waiters: Vec<(i64, Waker)>,
137}
138
139/// A clock that moves only when [`VirtualClock::advance`] or [`VirtualClock::set`] is called,
140/// and wakes the sleepers whose time has come. Cloning shares the clock.
141#[derive(Clone, Default)]
142pub struct VirtualClock {
143    inner: Arc<Mutex<VirtualInner>>,
144}
145
146impl std::fmt::Debug for VirtualClock {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        f.debug_struct("VirtualClock")
149            .field("now", &self.now_ms())
150            .finish()
151    }
152}
153
154impl VirtualClock {
155    /// A clock reading `start_ms`.
156    pub fn new(start_ms: i64) -> Self {
157        VirtualClock {
158            inner: Arc::new(Mutex::new(VirtualInner {
159                now: start_ms,
160                waiters: Vec::new(),
161            })),
162        }
163    }
164
165    fn lock(&self) -> std::sync::MutexGuard<'_, VirtualInner> {
166        self.inner
167            .lock()
168            .unwrap_or_else(std::sync::PoisonError::into_inner)
169    }
170
171    /// Moves the clock forward by `d`.
172    pub fn advance(&self, d: Duration) {
173        let ms = i64::try_from(d.as_millis()).unwrap_or(i64::MAX);
174        let now = self.now_ms().saturating_add(ms);
175        self.set(now);
176    }
177
178    /// Sets the clock to `at_ms` (never backwards) and wakes due sleepers.
179    pub fn set(&self, at_ms: i64) {
180        let due: Vec<Waker> = {
181            let mut g = self.lock();
182            g.now = g.now.max(at_ms);
183            let now = g.now;
184            let (ready, wait): (Vec<_>, Vec<_>) = g.waiters.drain(..).partition(|(t, _)| *t <= now);
185            g.waiters = wait;
186            ready.into_iter().map(|(_, w)| w).collect()
187        };
188        for w in due {
189            w.wake();
190        }
191    }
192}
193
194impl Clock for VirtualClock {
195    fn now_ms(&self) -> i64 {
196        self.lock().now
197    }
198}
199
200impl Sleep for VirtualClock {
201    fn sleep_until(&self, at_ms: i64) -> BoxFuture<()> {
202        Box::pin(VirtualSleep {
203            clock: self.clone(),
204            at: at_ms,
205        })
206    }
207}
208
209struct VirtualSleep {
210    clock: VirtualClock,
211    at: i64,
212}
213
214impl Future for VirtualSleep {
215    type Output = ();
216
217    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
218        let mut g = self.clock.lock();
219        if g.now >= self.at {
220            Poll::Ready(())
221        } else {
222            g.waiters.push((self.at, cx.waker().clone()));
223            Poll::Pending
224        }
225    }
226}
227
228#[cfg(test)]
229mod tests {
230    use super::*;
231
232    #[test]
233    fn virtual_clock_moves_only_when_advanced() {
234        let c = VirtualClock::new(1_000);
235        assert_eq!(c.now_ms(), 1_000);
236        c.advance(Duration::from_millis(250));
237        assert_eq!(c.now_ms(), 1_250);
238        c.set(100);
239        assert_eq!(c.now_ms(), 1_250, "never backwards");
240    }
241
242    #[tokio::test]
243    async fn virtual_sleep_completes_after_advance() {
244        let c = VirtualClock::new(0);
245        let s = c.sleep_until(50);
246        let h = tokio::spawn(s);
247        tokio::task::yield_now().await;
248        assert!(!h.is_finished());
249        c.advance(Duration::from_millis(50));
250        h.await.unwrap();
251    }
252}