Skip to main content

cove_runtime/
clock.rs

1//! `clock`: monotonic time, and waiting.
2//!
3//! The Language Card lists the clock among the operations that are typed Host
4//! APIs rather than ambient authority, and says a host "may provide real,
5//! fake, filtered, remote, or denied implementations". This module is where
6//! that becomes two implementations of one Host API: [`Clock::real`] reads the
7//! platform's monotonic clock, and [`Clock::virtual_clock`] reads a counter
8//! that moves only when the host moves it. Cove code cannot tell them apart,
9//! which is what makes a program that observes time testable.
10//!
11//! Time is a `Duration` since an origin the host picks, never a wall-clock
12//! date. A `Duration` subtracts, so `clock.now() - startedAt` is the elapsed
13//! time of a piece of work, and no program can accidentally depend on the
14//! origin itself.
15
16use std::sync::atomic::{AtomicBool, Ordering};
17use std::sync::{Arc, Mutex};
18
19use crate::budget::Cancellation;
20use crate::error::RuntimeError;
21use crate::host::{HostApi, Reentry};
22use crate::schema::ModuleSchema;
23use crate::value::{Repr, Value};
24use crate::wallclock::Instant;
25
26/// What a real clock answers when it is asked to wait on a target that
27/// cannot.
28///
29/// `wasm32-unknown-unknown` has no way to block: `std::thread::sleep` traps
30/// and `std::thread::spawn` traps, so neither `sleep` nor the watchdog behind
31/// `timeout` can be honoured. This is said in the vocabulary the rest of this
32/// module already uses for a request it will not carry out — an `Err` value
33/// the program can match on, like a negative duration — rather than by
34/// returning at once, which would report a wait that did not happen.
35///
36/// `clock.now()` is unaffected and keeps working: reading a clock is not
37/// waiting on one, and [`crate::wallclock`] is where the reading comes from.
38#[cfg(target_arch = "wasm32")]
39const CANNOT_WAIT: &str =
40    "clock: this environment cannot wait, so a real clock has no `sleep`, `timeout` or `every`";
41
42/// How often a watchdog looks at the work it is bounding.
43///
44/// A timeout is a bound, not a stopwatch, so the granularity only decides how
45/// long past the bound a body may run before its next safepoint sees the
46/// flag.
47const WATCH_INTERVAL: std::time::Duration = std::time::Duration::from_millis(2);
48
49/// The current time of a virtual clock, shared between the host and whoever
50/// moves it.
51///
52/// Cloning shares the same counter: advancing one handle advances every clone,
53/// including the one already given to a [`Clock`]. The counter is
54/// synchronized because a host is reachable from every task of a run, so two
55/// tasks may read or advance this clock at the same time.
56#[derive(Clone, Debug, Default)]
57pub struct VirtualTime(Arc<Mutex<i64>>);
58
59impl VirtualTime {
60    /// A clock sitting at its origin.
61    pub fn new() -> Self {
62        VirtualTime::default()
63    }
64
65    /// Nanoseconds since the origin.
66    pub fn nanos(&self) -> i64 {
67        *self
68            .0
69            .lock()
70            .unwrap_or_else(|poisoned| poisoned.into_inner())
71    }
72
73    /// Moves time forward by `nanos`.
74    ///
75    /// A monotonic clock never runs backwards, so a negative `nanos` moves
76    /// nothing. Time saturates at [`i64::MAX`] rather than overflowing, since
77    /// a clock that wrapped would report a time before one it already
78    /// reported.
79    pub fn advance(&self, nanos: i64) {
80        if nanos > 0 {
81            let mut now = self
82                .0
83                .lock()
84                .unwrap_or_else(|poisoned| poisoned.into_inner());
85            *now = now.saturating_add(nanos);
86        }
87    }
88}
89
90/// `clock`: how much time has passed, and waiting for more of it to pass.
91pub struct Clock {
92    source: ClockSource,
93}
94
95enum ClockSource {
96    /// Real monotonic time, measured from the instant this host was built.
97    Real(Instant),
98    /// Time that moves only when the host moves it.
99    Virtual(VirtualTime),
100}
101
102/// What `clock` declares about itself.
103///
104/// The table is [`cove_schema::hosts::CLOCK`], so the description the
105/// compiler checks a call against and the one the boundary dispatches through
106/// are the same bytes.
107const SCHEMA: ModuleSchema = cove_schema::hosts::CLOCK;
108
109impl Clock {
110    /// Real monotonic time, measured from the instant this host is built.
111    ///
112    /// The origin is the host's own construction rather than the Unix epoch,
113    /// so granting `clock` never discloses the wall-clock date.
114    pub fn real() -> Self {
115        Clock {
116            source: ClockSource::Real(Instant::now()),
117        }
118    }
119
120    /// A clock whose time moves only when `time` is advanced.
121    ///
122    /// `sleep` on a virtual clock advances `time` by the requested duration
123    /// and returns immediately: the host satisfies the wait by moving its own
124    /// clock instead of by blocking. A program cannot observe the difference
125    /// except that it finishes at once, which is what makes a test that
126    /// depends on elapsed time deterministic.
127    pub fn virtual_clock(time: VirtualTime) -> Self {
128        Clock {
129            source: ClockSource::Virtual(time),
130        }
131    }
132
133    /// Nanoseconds since this clock's origin.
134    ///
135    /// A real clock saturates at [`i64::MAX`], which no process reaches: it is
136    /// roughly 292 years of uptime.
137    fn now_nanos(&self) -> i64 {
138        match &self.source {
139            ClockSource::Real(origin) => {
140                i64::try_from(origin.elapsed().as_nanos()).unwrap_or(i64::MAX)
141            }
142            ClockSource::Virtual(time) => time.nanos(),
143        }
144    }
145
146    /// Whether this clock's time is the machine's.
147    fn is_real(&self) -> bool {
148        matches!(&self.source, ClockSource::Real(_))
149    }
150
151    /// Runs `body` and answers what it produced, unless it took longer than
152    /// `nanos`.
153    ///
154    /// A real clock bounds the body while it runs: a watchdog thread raises a
155    /// flag when the bound is reached, and the body stops at its next
156    /// safepoint. That is a timeout rather than a measurement — the work
157    /// stops, and the caller is told it did.
158    ///
159    /// A virtual clock has no thread to raise anything, because it has no
160    /// time of its own: it moves only when something moves it, and the only
161    /// thing that moves it during the body is the body's own `sleep`. So a
162    /// virtual clock judges afterwards, by how far the body pushed it. The
163    /// answer is the same one — a body that slept past the bound timed out —
164    /// and it is deterministic, which is what a virtual clock is for.
165    fn timeout(
166        &self,
167        nanos: i64,
168        body: &Value,
169        back: &mut dyn Reentry,
170    ) -> Result<Value, RuntimeError> {
171        if nanos < 0 {
172            return Ok(Value::err(Value::error(
173                "clock: a timeout must not be negative",
174            )));
175        }
176        let expired = |value: Value| {
177            let _ = value;
178            Value::err(Value::error(format!(
179                "clock: timed out after {}",
180                Value(Repr::Duration(nanos))
181            )))
182        };
183        #[cfg(target_arch = "wasm32")]
184        if self.is_real() {
185            return Ok(Value::err(Value::error(CANNOT_WAIT)));
186        }
187        match &self.source {
188            ClockSource::Real(_) => {
189                let stop = Cancellation::new();
190                let watch = Watchdog::start(nanos, stop.clone());
191                let outcome = back.call_until(body, Vec::new(), &stop);
192                drop(watch);
193                match outcome {
194                    Ok(value) if stop.is_cancelled() => Ok(expired(value)),
195                    Ok(value) => Ok(Value::ok(value)),
196                    // A body stopped by this bound reports the bound, not
197                    // whatever the safepoint happened to say.
198                    Err(_) if stop.is_cancelled() => Ok(expired(Value(Repr::Unit))),
199                    Err(error) => Err(error),
200                }
201            }
202            ClockSource::Virtual(time) => {
203                let before = time.nanos();
204                let value = back.call(body, Vec::new())?;
205                if time.nanos().saturating_sub(before) > nanos {
206                    return Ok(expired(value));
207                }
208                Ok(Value::ok(value))
209            }
210        }
211    }
212
213    /// Runs `body` every `nanos` until the task holding the timer is
214    /// cancelled, or until `body` fails.
215    ///
216    /// A real clock repeats: that is what a timer is. A virtual clock fires
217    /// once, because it has no time of its own — its `sleep` moves the clock
218    /// instead of waiting, so a repeating timer on it would be a loop with
219    /// nothing to wait for. One round is what a clock that only moves when
220    /// the host moves it can honestly give, and it is what makes a program
221    /// with a timer testable without one.
222    ///
223    /// The flag is read before the first round, so a timer whose task is
224    /// cancelled before its thread gets this far runs nothing at all. How many
225    /// rounds such a timer completed is therefore decided by neither this
226    /// clock nor the program that spawned it, and ADR 0008's amendment records
227    /// why nothing orders the two.
228    fn every(
229        &self,
230        nanos: i64,
231        body: &Value,
232        back: &mut dyn Reentry,
233    ) -> Result<Value, RuntimeError> {
234        if nanos < 0 {
235            return Ok(Value::err(Value::error(
236                "clock: a timer period must not be negative",
237            )));
238        }
239        // Before the loop rather than inside it: a real timer whose `sleep`
240        // refuses would otherwise run its body as fast as the fuel budget
241        // allowed, which is not the period it was asked for.
242        #[cfg(target_arch = "wasm32")]
243        if self.is_real() {
244            return Ok(Value::err(Value::error(CANNOT_WAIT)));
245        }
246        loop {
247            if back.is_cancelled() {
248                return Ok(Value::ok(Value(Repr::Unit)));
249            }
250            self.sleep(nanos);
251            if back.is_cancelled() {
252                return Ok(Value::ok(Value(Repr::Unit)));
253            }
254            let answered = back.call(body, Vec::new())?;
255            // The body reports failure the way every Cove function does, and
256            // a timer whose body failed stops rather than failing again every
257            // period from now on.
258            if answered.is_err() {
259                return Ok(answered);
260            }
261            if !self.is_real() {
262                return Ok(Value::ok(Value(Repr::Unit)));
263            }
264        }
265    }
266
267    /// Waits `nanos`, or reports why it will not.
268    fn sleep(&self, nanos: i64) -> Value {
269        if nanos < 0 {
270            return Value::err(Value::error("clock: a sleep duration must not be negative"));
271        }
272        match &self.source {
273            #[cfg(target_arch = "wasm32")]
274            ClockSource::Real(_) => return Value::err(Value::error(CANNOT_WAIT)),
275            #[cfg(not(target_arch = "wasm32"))]
276            ClockSource::Real(_) => {
277                std::thread::sleep(std::time::Duration::from_nanos(nanos as u64))
278            }
279            ClockSource::Virtual(time) => time.advance(nanos),
280        }
281        Value::ok(Value(Repr::Unit))
282    }
283}
284
285impl HostApi for Clock {
286    fn module_schema(&self) -> ModuleSchema {
287        SCHEMA
288    }
289
290    fn call_with(
291        &self,
292        op: &str,
293        args: Vec<Value>,
294        back: &mut dyn Reentry,
295    ) -> Result<Value, RuntimeError> {
296        match op {
297            "timeout" => {
298                let [Value(Repr::Duration(nanos)), body] = args.as_slice() else {
299                    unreachable!("checked by HostRegistry::call")
300                };
301                self.timeout(*nanos, body, back)
302            }
303            "every" => {
304                let [Value(Repr::Duration(nanos)), body] = args.as_slice() else {
305                    unreachable!("checked by HostRegistry::call")
306                };
307                self.every(*nanos, body, back)
308            }
309            _ => self.call(op, args),
310        }
311    }
312
313    fn call(&self, op: &str, args: Vec<Value>) -> Result<Value, RuntimeError> {
314        match op {
315            "now" => Ok(Value(Repr::Duration(self.now_nanos()))),
316            "sleep" => {
317                let [Value(Repr::Duration(nanos))] = args.as_slice() else {
318                    unreachable!("checked by HostRegistry::call")
319                };
320                Ok(self.sleep(*nanos))
321            }
322            _ => unreachable!("checked by HostRegistry::call"),
323        }
324    }
325}
326
327/// A thread that raises a flag once a bound is reached, and stops when the
328/// work it was watching is done.
329///
330/// It polls rather than sleeping the whole bound, so a body that finishes
331/// early does not leave a thread asleep for a minute behind it.
332struct Watchdog {
333    finished: Arc<AtomicBool>,
334    thread: Option<std::thread::JoinHandle<()>>,
335}
336
337impl Watchdog {
338    fn start(nanos: i64, stop: Cancellation) -> Watchdog {
339        let finished = Arc::new(AtomicBool::new(false));
340        let done = Arc::clone(&finished);
341        let deadline = Instant::now() + std::time::Duration::from_nanos(nanos as u64);
342        let thread = std::thread::spawn(move || {
343            while !done.load(Ordering::Relaxed) {
344                if Instant::now() >= deadline {
345                    stop.cancel();
346                    return;
347                }
348                std::thread::sleep(WATCH_INTERVAL);
349            }
350        });
351        Watchdog {
352            finished,
353            thread: Some(thread),
354        }
355    }
356}
357
358impl Drop for Watchdog {
359    fn drop(&mut self) {
360        self.finished.store(true, Ordering::Relaxed);
361        if let Some(thread) = self.thread.take() {
362            let _ = thread.join();
363        }
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370    use crate::host::{Grants, HostRegistry};
371    use std::time::Duration;
372
373    fn nanos(value: Value) -> i64 {
374        match value {
375            Value(Repr::Duration(nanos)) => nanos,
376            other => panic!("expected a `Duration`, found {other}"),
377        }
378    }
379
380    fn is_ok(value: &Value) -> bool {
381        value.is_ok()
382    }
383
384    fn err_message(value: Value) -> String {
385        match value.err_payload() {
386            Some(payload) => payload.first().map(ToString::to_string).unwrap_or_default(),
387            None => panic!("expected `Err(...)`, found {value}"),
388        }
389    }
390
391    fn ok_int(value: Value) -> i64 {
392        match value.ok_payload() {
393            Some(payload) => match payload.first() {
394                Some(Value(Repr::Int(n))) => *n,
395                other => panic!("expected `Ok(Int)`, found {other:?}"),
396            },
397            None => panic!("expected `Ok(...)`, found {value}"),
398        }
399    }
400
401    /// What a [`StubReentry`] runs in place of a Cove callback.
402    type StubBody = Box<dyn FnMut(&Cancellation) -> Result<Value, RuntimeError>>;
403
404    /// A stub [`Reentry`] for tests, standing in for the interpreter: it runs
405    /// the boxed closure it was built with instead of dispatching into Cove
406    /// code, and hands the closure whichever [`Cancellation`] the call
407    /// carried, so a body that wants to observe a bound can.
408    struct StubReentry {
409        calls: usize,
410        /// Whether the task holding this reentry has been asked to stop.
411        ///
412        /// Shared rather than owned so a body can raise it while the host is
413        /// looping, which is how a test ends a repeating timer the way a
414        /// cancelled task ends one.
415        cancelled: Arc<AtomicBool>,
416        body: StubBody,
417    }
418
419    impl StubReentry {
420        fn new(body: impl FnMut(&Cancellation) -> Result<Value, RuntimeError> + 'static) -> Self {
421            StubReentry {
422                calls: 0,
423                cancelled: Arc::new(AtomicBool::new(false)),
424                body: Box::new(body),
425            }
426        }
427
428        /// Reports the task holding this reentry as already cancelled.
429        fn cancelled(self) -> Self {
430            self.cancelled.store(true, Ordering::Relaxed);
431            self
432        }
433
434        /// Reports the task holding this reentry as stopped when `flag` is
435        /// raised, so a body can end a repeating timer from inside a round
436        /// the way a cancelled task ends one.
437        fn stopped_by(mut self, flag: Arc<AtomicBool>) -> Self {
438            self.cancelled = flag;
439            self
440        }
441    }
442
443    impl Reentry for StubReentry {
444        fn call(&mut self, _callee: &Value, _args: Vec<Value>) -> Result<Value, RuntimeError> {
445            self.calls += 1;
446            (self.body)(&Cancellation::new())
447        }
448
449        fn call_until(
450            &mut self,
451            _callee: &Value,
452            _args: Vec<Value>,
453            stop: &Cancellation,
454        ) -> Result<Value, RuntimeError> {
455            self.calls += 1;
456            (self.body)(stop)
457        }
458
459        fn is_cancelled(&self) -> bool {
460            self.cancelled.load(Ordering::Relaxed)
461        }
462
463        /// Neither `timeout` nor `every` reads the run's deadline — a bound
464        /// the program wrote is the only clock either of them keeps — so this
465        /// stub has none to report.
466        fn time_left(&self) -> Option<std::time::Duration> {
467            None
468        }
469
470        /// A stub stands in for the entry's own way back, which is the task a
471        /// call made outside any spawned task belongs to.
472        fn task(&self) -> u64 {
473            crate::runtime::ENTRY_TASK
474        }
475    }
476
477    #[test]
478    fn a_virtual_clock_starts_at_its_origin_and_stands_still() {
479        let time = VirtualTime::new();
480        let clock = Clock::virtual_clock(time.clone());
481
482        assert_eq!(nanos(clock.call("now", Vec::new()).unwrap()), 0);
483        assert_eq!(nanos(clock.call("now", Vec::new()).unwrap()), 0);
484        assert_eq!(time.nanos(), 0);
485    }
486
487    #[test]
488    fn a_virtual_clock_moves_only_when_the_host_moves_it() {
489        let time = VirtualTime::new();
490        let clock = Clock::virtual_clock(time.clone());
491
492        time.advance(1_500_000_000);
493        assert_eq!(nanos(clock.call("now", Vec::new()).unwrap()), 1_500_000_000);
494
495        time.advance(500_000_000);
496        assert_eq!(nanos(clock.call("now", Vec::new()).unwrap()), 2_000_000_000);
497    }
498
499    #[test]
500    fn a_virtual_clock_never_runs_backwards() {
501        let time = VirtualTime::new();
502        time.advance(1_000);
503        time.advance(-1_000);
504        assert_eq!(time.nanos(), 1_000);
505    }
506
507    #[test]
508    fn sleeping_a_virtual_clock_advances_it_instead_of_waiting() {
509        let time = VirtualTime::new();
510        let clock = Clock::virtual_clock(time.clone());
511
512        let started = Instant::now();
513        let slept = clock
514            .call("sleep", vec![Value(Repr::Duration(3_600_000_000_000))])
515            .unwrap();
516        assert!(is_ok(&slept), "{slept}");
517        assert_eq!(time.nanos(), 3_600_000_000_000);
518        assert!(started.elapsed() < std::time::Duration::from_secs(1));
519    }
520
521    #[test]
522    fn sleeping_a_negative_duration_is_an_error_on_either_clock() {
523        for clock in [Clock::real(), Clock::virtual_clock(VirtualTime::new())] {
524            let slept = clock
525                .call("sleep", vec![Value(Repr::Duration(-1))])
526                .unwrap();
527            assert_eq!(
528                err_message(slept),
529                "clock: a sleep duration must not be negative"
530            );
531        }
532    }
533
534    #[test]
535    fn a_real_clock_never_reports_an_earlier_time_than_it_already_did() {
536        let clock = Clock::real();
537
538        let first = nanos(clock.call("now", Vec::new()).unwrap());
539        let second = nanos(clock.call("now", Vec::new()).unwrap());
540        assert!(first >= 0, "{first}");
541        assert!(second >= first, "{second} < {first}");
542    }
543
544    #[test]
545    fn a_real_clock_observes_its_own_sleep() {
546        let clock = Clock::real();
547
548        let before = nanos(clock.call("now", Vec::new()).unwrap());
549        let slept = clock
550            .call("sleep", vec![Value(Repr::Duration(1_000_000))])
551            .unwrap();
552        assert!(is_ok(&slept), "{slept}");
553        let after = nanos(clock.call("now", Vec::new()).unwrap());
554        assert!(after - before >= 1_000_000, "{after} - {before}");
555    }
556
557    #[test]
558    fn a_run_without_the_clock_grant_cannot_read_the_time() {
559        let mut hosts = HostRegistry::new(Grants::new(["console"]));
560        hosts.register(Box::new(Clock::real()));
561
562        let error = hosts
563            .call("clock", "now", Vec::new())
564            .expect_err("the call should be rejected");
565        assert_eq!(
566            error.message,
567            "`clock.now` requires the `clock` capability, which this run was not granted"
568        );
569    }
570
571    #[test]
572    fn a_granted_virtual_clock_is_reachable_through_the_registry() {
573        let time = VirtualTime::new();
574        let mut hosts = HostRegistry::new(Grants::new(["clock"]));
575        hosts.register(Box::new(Clock::virtual_clock(time.clone())));
576
577        hosts
578            .call("clock", "sleep", vec![Value(Repr::Duration(250_000_000))])
579            .expect("the call should be allowed");
580        let now = hosts
581            .call("clock", "now", Vec::new())
582            .expect("the call should be allowed");
583        assert_eq!(nanos(now), 250_000_000);
584    }
585
586    #[test]
587    fn timeout_on_a_virtual_clock_answers_ok_when_the_body_does_not_oversleep() {
588        let clock = Clock::virtual_clock(VirtualTime::new());
589        let mut back = StubReentry::new(|_stop| Ok(Value(Repr::Int(42))));
590
591        let answer = clock
592            .call_with(
593                "timeout",
594                vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
595                &mut back,
596            )
597            .unwrap();
598        assert!(is_ok(&answer), "{answer}");
599        assert_eq!(ok_int(answer), 42);
600    }
601
602    /// A virtual clock has no time of its own, so `timeout` judges afterwards
603    /// by how far the body's own `sleep` pushed the shared clock, rather than
604    /// by racing a watchdog thread against it.
605    #[test]
606    fn timeout_on_a_virtual_clock_times_out_when_the_body_sleeps_past_the_bound() {
607        let time = VirtualTime::new();
608        let clock = Clock::virtual_clock(time.clone());
609        let sleeper = Clock::virtual_clock(time);
610        let mut back = StubReentry::new(move |_stop| {
611            sleeper.call("sleep", vec![Value(Repr::Duration(2_000_000_000))])
612        });
613
614        let answer = clock
615            .call_with(
616                "timeout",
617                vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
618                &mut back,
619            )
620            .unwrap();
621        assert_eq!(
622            err_message(answer),
623            format!(
624                "clock: timed out after {}",
625                Value(Repr::Duration(1_000_000_000))
626            )
627        );
628    }
629
630    #[test]
631    fn timeout_on_a_real_clock_answers_ok_when_the_body_finishes_before_the_bound() {
632        let clock = Clock::real();
633        let mut back = StubReentry::new(|_stop| Ok(Value(Repr::Int(7))));
634
635        let answer = clock
636            .call_with(
637                "timeout",
638                vec![Value(Repr::Duration(200_000_000)), Value(Repr::Unit)],
639                &mut back,
640            )
641            .unwrap();
642        assert!(is_ok(&answer), "{answer}");
643        assert_eq!(ok_int(answer), 7);
644    }
645
646    /// The bound is kept to a few milliseconds so the test finishes quickly,
647    /// and the body's own loop watches `stop` directly so nothing can hang:
648    /// the watchdog is what raises the flag, and the body is what has to
649    /// notice it.
650    #[test]
651    fn timeout_on_a_real_clock_times_out_a_body_that_spins_past_the_bound() {
652        let clock = Clock::real();
653        let mut back = StubReentry::new(|stop| {
654            while !stop.is_cancelled() {
655                std::thread::sleep(std::time::Duration::from_millis(1));
656            }
657            Ok(Value(Repr::Unit))
658        });
659
660        let answer = clock
661            .call_with(
662                "timeout",
663                vec![Value(Repr::Duration(5_000_000)), Value(Repr::Unit)],
664                &mut back,
665            )
666            .unwrap();
667        assert_eq!(
668            err_message(answer),
669            format!(
670                "clock: timed out after {}",
671                Value(Repr::Duration(5_000_000))
672            )
673        );
674    }
675
676    #[test]
677    fn a_negative_timeout_bound_is_an_error_on_either_clock() {
678        for clock in [Clock::real(), Clock::virtual_clock(VirtualTime::new())] {
679            let mut back = StubReentry::new(|_stop| Ok(Value(Repr::Unit)));
680            let answer = clock
681                .call_with(
682                    "timeout",
683                    vec![Value(Repr::Duration(-1)), Value(Repr::Unit)],
684                    &mut back,
685                )
686                .unwrap();
687            assert_eq!(err_message(answer), "clock: a timeout must not be negative");
688            assert_eq!(
689                back.calls, 0,
690                "a bound this obviously bad never runs the body"
691            );
692        }
693    }
694
695    #[test]
696    fn a_negative_timer_period_is_an_error_on_either_clock() {
697        for clock in [Clock::real(), Clock::virtual_clock(VirtualTime::new())] {
698            let mut back = StubReentry::new(|_stop| Ok(Value(Repr::Unit)));
699            let answer = clock
700                .call_with(
701                    "every",
702                    vec![Value(Repr::Duration(-1)), Value(Repr::Unit)],
703                    &mut back,
704                )
705                .unwrap();
706            assert_eq!(
707                err_message(answer),
708                "clock: a timer period must not be negative"
709            );
710            assert_eq!(
711                back.calls, 0,
712                "a period this obviously bad never runs the body"
713            );
714        }
715    }
716
717    /// A virtual clock has no time of its own to repeat a timer with, so
718    /// `every` gives the one round it honestly can rather than looping
719    /// forever with nothing to wait for.
720    #[test]
721    fn every_on_a_virtual_clock_fires_exactly_once() {
722        let clock = Clock::virtual_clock(VirtualTime::new());
723        let mut back = StubReentry::new(|_stop| Ok(Value::ok(Value(Repr::Unit))));
724
725        let answer = clock
726            .call_with(
727                "every",
728                vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
729                &mut back,
730            )
731            .unwrap();
732        assert!(is_ok(&answer), "{answer}");
733        assert_eq!(back.calls, 1);
734    }
735
736    #[test]
737    fn every_hands_back_a_failing_bodys_err_instead_of_repeating() {
738        let clock = Clock::virtual_clock(VirtualTime::new());
739        let mut back = StubReentry::new(|_stop| Ok(Value::err(Value::error("boom"))));
740
741        let answer = clock
742            .call_with(
743                "every",
744                vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
745                &mut back,
746            )
747            .unwrap();
748        assert_eq!(err_message(answer), "boom");
749        assert_eq!(back.calls, 1, "a failing round is not retried");
750    }
751
752    /// Runs `body` on a thread of its own and fails if it has not finished
753    /// within `limit`.
754    ///
755    /// The rule below is one a host breaks by deadlocking, which is a way of
756    /// failing that a test asserting on a result never reaches. This makes a
757    /// regression a failure with a message rather than a suite that never
758    /// ends.
759    fn within<T: Send + 'static>(limit: Duration, body: impl FnOnce() -> T + Send + 'static) -> T {
760        let (finished, done) = std::sync::mpsc::channel();
761        std::thread::spawn(move || {
762            let _ = finished.send(body());
763        });
764        done.recv_timeout(limit)
765            .unwrap_or_else(|_| panic!("this did not finish within {limit:?}"))
766    }
767
768    /// A host may run the callback it was handed as many times as its
769    /// operation means, and a timer means once a period. Nothing counts the
770    /// invocations and none of them is cheaper than the first: what ends the
771    /// loop is the run being stopped, which the host reads between rounds.
772    #[test]
773    fn every_on_a_real_clock_runs_the_body_once_a_round_until_the_run_is_stopped() {
774        let rounds = within(Duration::from_secs(10), || {
775            let clock = Clock::real();
776            let stop = Arc::new(AtomicBool::new(false));
777            let raise = Arc::clone(&stop);
778            let mut rounds = 0;
779            let mut back = StubReentry::new(move |_stop| {
780                rounds += 1;
781                if rounds >= 3 {
782                    raise.store(true, Ordering::Relaxed);
783                }
784                Ok(Value::ok(Value(Repr::Unit)))
785            })
786            .stopped_by(stop);
787
788            let answer = clock
789                .call_with(
790                    "every",
791                    vec![Value(Repr::Duration(0)), Value(Repr::Unit)],
792                    &mut back,
793                )
794                .unwrap();
795            assert!(is_ok(&answer), "{answer}");
796            back.calls
797        });
798        assert_eq!(rounds, 3, "the timer ran a round each period until stopped");
799    }
800
801    /// A host must hold no lock of its own while it runs a Cove callback,
802    /// because the callback is Cove code and Cove code may call the same host
803    /// again. `clock`'s only state is the virtual clock's counter, and it is
804    /// held for a read and a write and nothing else — so a timer's body may
805    /// read and move the very clock that is running it.
806    ///
807    /// Held across the round, this would deadlock the task on a mutex three
808    /// frames up its own stack, which is why it runs under a bound.
809    #[test]
810    fn a_timer_body_may_read_and_move_the_clock_that_is_running_it() {
811        let moved = within(Duration::from_secs(10), || {
812            let time = VirtualTime::new();
813            let clock = Clock::virtual_clock(time.clone());
814            let inside = Clock::virtual_clock(time.clone());
815            let mut back = StubReentry::new(move |_stop| {
816                inside.call("now", Vec::new())?;
817                inside.call("sleep", vec![Value(Repr::Duration(5))])?;
818                Ok(Value::ok(Value(Repr::Unit)))
819            });
820
821            let answer = clock
822                .call_with(
823                    "every",
824                    vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
825                    &mut back,
826                )
827                .unwrap();
828            assert!(is_ok(&answer), "{answer}");
829            time.nanos()
830        });
831        assert_eq!(
832            moved, 1_000_000_005,
833            "the period the timer slept, plus what its body slept from inside the round"
834        );
835    }
836
837    #[test]
838    fn every_answers_ok_without_running_the_body_when_the_task_is_already_cancelled() {
839        let clock = Clock::virtual_clock(VirtualTime::new());
840        let mut back = StubReentry::new(|_stop| panic!("the body must not run")).cancelled();
841
842        let answer = clock
843            .call_with(
844                "every",
845                vec![Value(Repr::Duration(1_000_000_000)), Value(Repr::Unit)],
846                &mut back,
847            )
848            .unwrap();
849        assert!(is_ok(&answer), "{answer}");
850        assert_eq!(back.calls, 0);
851    }
852}