1use 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#[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
42const WATCH_INTERVAL: std::time::Duration = std::time::Duration::from_millis(2);
48
49#[derive(Clone, Debug, Default)]
57pub struct VirtualTime(Arc<Mutex<i64>>);
58
59impl VirtualTime {
60 pub fn new() -> Self {
62 VirtualTime::default()
63 }
64
65 pub fn nanos(&self) -> i64 {
67 *self
68 .0
69 .lock()
70 .unwrap_or_else(|poisoned| poisoned.into_inner())
71 }
72
73 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
90pub struct Clock {
92 source: ClockSource,
93}
94
95enum ClockSource {
96 Real(Instant),
98 Virtual(VirtualTime),
100}
101
102const SCHEMA: ModuleSchema = cove_schema::hosts::CLOCK;
108
109impl Clock {
110 pub fn real() -> Self {
115 Clock {
116 source: ClockSource::Real(Instant::now()),
117 }
118 }
119
120 pub fn virtual_clock(time: VirtualTime) -> Self {
128 Clock {
129 source: ClockSource::Virtual(time),
130 }
131 }
132
133 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 fn is_real(&self) -> bool {
148 matches!(&self.source, ClockSource::Real(_))
149 }
150
151 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 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 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 #[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 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 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
327struct 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 type StubBody = Box<dyn FnMut(&Cancellation) -> Result<Value, RuntimeError>>;
403
404 struct StubReentry {
409 calls: usize,
410 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 fn cancelled(self) -> Self {
430 self.cancelled.store(true, Ordering::Relaxed);
431 self
432 }
433
434 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 fn time_left(&self) -> Option<std::time::Duration> {
467 None
468 }
469
470 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 #[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 #[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 #[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 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 #[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 #[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}