Skip to main content

nexus_rt/
clock.rs

1//! Clock abstractions for event-driven runtimes.
2//!
3//! `Clock` is a resource registered in the World. Handlers read it via
4//! `Res<Clock>`. Sync sources install into the World via the `Installer`
5//! trait and return pollers that sync the clock each poll loop iteration.
6//!
7//! Three sync sources:
8//! - `RealtimeClockInstaller` → `RealtimeClockPoller` — production
9//! - `TestClockInstaller` → `TestClockPoller` — deterministic testing
10//! - `HistoricalClockInstaller` → `HistoricalClockPoller` — replay
11//!
12//! The `RealtimeClockPoller` calibration design is inspired by Agrona's
13//! [`OffsetEpochNanoClock`](https://github.com/real-logic/agrona).
14
15use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16
17use crate::World;
18use crate::driver::Installer;
19use crate::world::{ResourceId, WorldBuilder};
20
21/// Configuration error from clock construction.
22#[derive(Debug, Clone, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum ConfigError {
25    /// A parameter value is invalid.
26    Invalid(&'static str),
27}
28
29impl std::fmt::Display for ConfigError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            Self::Invalid(msg) => write!(f, "clock configuration error: {msg}"),
33        }
34    }
35}
36
37impl std::error::Error for ConfigError {}
38
39// =============================================================================
40// Clock — the World resource
41// =============================================================================
42
43/// The current time — registered as a World resource.
44///
45/// Synced once per poll loop iteration by a clock poller. Handlers read
46/// via `Res<Clock>`.
47///
48/// # Example
49///
50/// ```ignore
51/// fn on_event(clock: Res<Clock>, event: SomeEvent) {
52///     let timestamp = clock.unix_nanos();
53///     let when = clock.instant();
54/// }
55/// ```
56#[derive(Debug, Clone, Copy)]
57pub struct Clock {
58    instant: Instant,
59    unix_nanos: i128,
60}
61
62impl crate::world::Resource for Clock {}
63
64impl Clock {
65    /// Monotonic instant from this poll iteration.
66    #[inline]
67    #[must_use]
68    pub fn instant(&self) -> Instant {
69        self.instant
70    }
71
72    /// UTC nanoseconds since Unix epoch (1970-01-01 00:00:00 UTC).
73    #[inline]
74    #[must_use]
75    pub fn unix_nanos(&self) -> i128 {
76        self.unix_nanos
77    }
78}
79
80impl Default for Clock {
81    fn default() -> Self {
82        Self {
83            instant: Instant::now(),
84            unix_nanos: 0,
85        }
86    }
87}
88
89// =============================================================================
90// RealtimeClock — installer + poller
91// =============================================================================
92
93#[cfg(debug_assertions)]
94const DEFAULT_THRESHOLD: Duration = Duration::from_micros(1);
95#[cfg(not(debug_assertions))]
96const DEFAULT_THRESHOLD: Duration = Duration::from_nanos(250);
97
98#[cfg(debug_assertions)]
99const MIN_THRESHOLD: Duration = Duration::from_micros(1);
100#[cfg(not(debug_assertions))]
101const MIN_THRESHOLD: Duration = Duration::from_nanos(100);
102
103/// Installer for the realtime clock. Consumed at setup.
104///
105/// Registers a [`Clock`] resource in the World, calibrates the
106/// monotonic-to-UTC offset, and returns a [`RealtimeClockPoller`].
107///
108/// # Example
109///
110/// ```ignore
111/// let mut wb = WorldBuilder::new();
112/// let mut clock_poller = wb.install_driver(
113///     RealtimeClockInstaller::builder().build().unwrap()
114/// );
115/// let mut world = wb.build();
116///
117/// loop {
118///     let now = Instant::now();
119///     clock_poller.sync(&mut world, now);
120///     // ...
121/// }
122/// ```
123pub struct RealtimeClockInstaller {
124    threshold: Duration,
125    max_retries: u32,
126    resync_interval: Duration,
127}
128
129/// Builder for [`RealtimeClockInstaller`].
130pub struct RealtimeClockInstallerBuilder {
131    threshold: Duration,
132    max_retries: u32,
133    resync_interval: Duration,
134}
135
136impl RealtimeClockInstaller {
137    /// Creates a builder with sensible defaults.
138    #[must_use]
139    pub fn builder() -> RealtimeClockInstallerBuilder {
140        RealtimeClockInstallerBuilder::default()
141    }
142}
143
144impl Installer for RealtimeClockInstaller {
145    type Poller = RealtimeClockPoller;
146
147    fn install(self, world: &mut WorldBuilder) -> RealtimeClockPoller {
148        let clock_id = world.register(Clock::default());
149
150        let (base_instant, base_nanos, gap) =
151            RealtimeClockPoller::calibrate(self.threshold, self.max_retries);
152        let accurate = gap <= self.threshold;
153
154        RealtimeClockPoller {
155            clock_id,
156            base_instant,
157            base_nanos,
158            last_resync: base_instant,
159            resync_interval: self.resync_interval,
160            threshold: self.threshold,
161            max_retries: self.max_retries,
162            calibration_gap: gap,
163            accurate,
164        }
165    }
166}
167
168impl Default for RealtimeClockInstaller {
169    fn default() -> Self {
170        Self::builder()
171            .build()
172            .expect("default config is always valid")
173    }
174}
175
176impl std::fmt::Debug for RealtimeClockInstaller {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        f.debug_struct("RealtimeClockInstaller")
179            .field("threshold", &self.threshold)
180            .field("max_retries", &self.max_retries)
181            .field("resync_interval", &self.resync_interval)
182            .finish()
183    }
184}
185
186/// Runtime poller for the realtime clock.
187///
188/// Holds a pre-resolved `ResourceId` for the `Clock` resource and
189/// calibration state. Call `sync()` once per poll loop iteration.
190pub struct RealtimeClockPoller {
191    clock_id: ResourceId,
192    base_instant: Instant,
193    base_nanos: i128,
194    last_resync: Instant,
195    resync_interval: Duration,
196    threshold: Duration,
197    max_retries: u32,
198    calibration_gap: Duration,
199    accurate: bool,
200}
201
202impl RealtimeClockPoller {
203    /// Sync the Clock resource with the current time.
204    #[inline]
205    pub fn sync(&mut self, world: &mut World, now: Instant) {
206        if now.saturating_duration_since(self.last_resync) >= self.resync_interval {
207            self.resync_at(now);
208        }
209
210        let elapsed = now.saturating_duration_since(self.base_instant);
211        let nanos = self.base_nanos + elapsed.as_nanos() as i128;
212
213        // SAFETY: clock_id was returned by register() during install()
214        let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
215        clock.instant = now;
216        clock.unix_nanos = nanos;
217    }
218
219    /// Force recalibration.
220    pub fn resync(&mut self) {
221        self.resync_at(Instant::now());
222    }
223
224    /// Whether calibration achieved the configured threshold.
225    #[inline]
226    #[must_use]
227    pub fn is_accurate(&self) -> bool {
228        self.accurate
229    }
230
231    /// Best measurement gap achieved during calibration.
232    #[inline]
233    #[must_use]
234    pub fn calibration_gap(&self) -> Duration {
235        self.calibration_gap
236    }
237
238    fn resync_at(&mut self, now: Instant) {
239        let (best_instant, base_nanos, gap) = Self::calibrate(self.threshold, self.max_retries);
240        let adjustment = now.saturating_duration_since(best_instant);
241        self.base_instant = now;
242        self.base_nanos = base_nanos + adjustment.as_nanos() as i128;
243        self.calibration_gap = gap;
244        self.accurate = gap <= self.threshold;
245        self.last_resync = now;
246    }
247
248    fn calibrate(threshold: Duration, max_retries: u32) -> (Instant, i128, Duration) {
249        let mut best_gap = Duration::MAX;
250        let mut best_instant = Instant::now();
251        let mut best_nanos = 0i128;
252
253        for _ in 0..max_retries {
254            let before = Instant::now();
255            let wall = SystemTime::now();
256            let after = Instant::now();
257
258            let gap = after.duration_since(before);
259            if gap < best_gap {
260                best_gap = gap;
261                best_instant = before + gap / 2;
262                best_nanos = match wall.duration_since(UNIX_EPOCH) {
263                    Ok(d) => d.as_nanos() as i128,
264                    Err(e) => -(e.duration().as_nanos() as i128),
265                };
266            }
267
268            if gap <= threshold {
269                break;
270            }
271        }
272
273        (best_instant, best_nanos, best_gap)
274    }
275}
276
277impl std::fmt::Debug for RealtimeClockPoller {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        f.debug_struct("RealtimeClockPoller")
280            .field("calibration_gap", &self.calibration_gap)
281            .field("accurate", &self.accurate)
282            .finish()
283    }
284}
285
286// -- Builder --
287
288impl RealtimeClockInstallerBuilder {
289    /// Target accuracy threshold. Clamped to platform minimum.
290    #[must_use]
291    pub fn threshold(mut self, threshold: Duration) -> Self {
292        self.threshold = threshold.max(MIN_THRESHOLD);
293        self
294    }
295
296    /// Maximum calibration attempts. Default: 20.
297    #[must_use]
298    pub fn max_retries(mut self, n: u32) -> Self {
299        self.max_retries = n;
300        self
301    }
302
303    /// Resync interval. Default: 1 hour.
304    #[must_use]
305    pub fn resync_interval(mut self, interval: Duration) -> Self {
306        self.resync_interval = interval;
307        self
308    }
309
310    /// Builds the installer.
311    ///
312    /// # Errors
313    ///
314    /// Returns `ConfigError::Invalid` if `max_retries` is 0.
315    pub fn build(self) -> Result<RealtimeClockInstaller, ConfigError> {
316        if self.max_retries == 0 {
317            return Err(ConfigError::Invalid("max_retries must be > 0"));
318        }
319        Ok(RealtimeClockInstaller {
320            threshold: self.threshold,
321            max_retries: self.max_retries,
322            resync_interval: self.resync_interval,
323        })
324    }
325}
326
327impl Default for RealtimeClockInstallerBuilder {
328    fn default() -> Self {
329        Self {
330            threshold: DEFAULT_THRESHOLD,
331            max_retries: 20,
332            resync_interval: Duration::from_secs(3600),
333        }
334    }
335}
336
337// =============================================================================
338// TestClock — installer + poller
339// =============================================================================
340
341/// Installer for the test clock.
342///
343/// Registers a [`Clock`] resource and returns a [`TestClockPoller`].
344#[derive(Debug)]
345pub struct TestClockInstaller {
346    base_nanos: i128,
347}
348
349impl TestClockInstaller {
350    /// Creates an installer starting at epoch (nanos = 0).
351    #[must_use]
352    pub fn new() -> Self {
353        Self { base_nanos: 0 }
354    }
355
356    /// Creates an installer starting at the given UTC nanos.
357    #[must_use]
358    pub fn starting_at(nanos: i128) -> Self {
359        Self { base_nanos: nanos }
360    }
361}
362
363impl Default for TestClockInstaller {
364    fn default() -> Self {
365        Self::new()
366    }
367}
368
369impl Installer for TestClockInstaller {
370    type Poller = TestClockPoller;
371
372    fn install(self, world: &mut WorldBuilder) -> TestClockPoller {
373        let clock_id = world.register(Clock::default());
374        TestClockPoller {
375            clock_id,
376            elapsed: Duration::ZERO,
377            base_nanos: self.base_nanos,
378            base_instant: Instant::now(),
379        }
380    }
381}
382
383/// Runtime poller for the test clock — manually controlled.
384///
385/// Use `advance()` to move time forward, then `sync()` to write into
386/// the `Clock` resource.
387pub struct TestClockPoller {
388    clock_id: ResourceId,
389    elapsed: Duration,
390    base_nanos: i128,
391    base_instant: Instant,
392}
393
394impl TestClockPoller {
395    /// Sync the Clock resource with the test clock's current state.
396    #[inline]
397    pub fn sync(&self, world: &mut World) {
398        // SAFETY: clock_id was returned by register() during install()
399        let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
400        clock.instant = self.base_instant + self.elapsed;
401        clock.unix_nanos = self.base_nanos + self.elapsed.as_nanos() as i128;
402    }
403
404    /// Advances time by the given duration.
405    #[inline]
406    pub fn advance(&mut self, duration: Duration) {
407        self.elapsed += duration;
408    }
409
410    /// Sets the elapsed time to an exact value.
411    #[inline]
412    pub fn set_elapsed(&mut self, elapsed: Duration) {
413        self.elapsed = elapsed;
414    }
415
416    /// Sets the UTC nanos directly (resets elapsed to zero).
417    #[inline]
418    pub fn set_nanos(&mut self, nanos: i128) {
419        self.base_nanos = nanos;
420        self.elapsed = Duration::ZERO;
421    }
422
423    /// Returns the current elapsed duration.
424    #[inline]
425    #[must_use]
426    pub fn elapsed(&self) -> Duration {
427        self.elapsed
428    }
429
430    /// Resets to zero elapsed, keeping the base nanos.
431    #[inline]
432    pub fn reset(&mut self) {
433        self.elapsed = Duration::ZERO;
434    }
435}
436
437impl std::fmt::Debug for TestClockPoller {
438    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439        f.debug_struct("TestClockPoller")
440            .field("elapsed", &self.elapsed)
441            .field("base_nanos", &self.base_nanos)
442            .finish()
443    }
444}
445
446// =============================================================================
447// HistoricalClock — installer + poller
448// =============================================================================
449
450/// Installer for the historical (replay) clock.
451///
452/// Registers a [`Clock`] resource and returns a [`HistoricalClockPoller`].
453pub struct HistoricalClockInstaller {
454    start_nanos: i128,
455    end_nanos: i128,
456    step: Duration,
457}
458
459impl HistoricalClockInstaller {
460    /// Creates an installer for replay.
461    ///
462    /// # Errors
463    ///
464    /// Returns `ConfigError::Invalid` if `start_nanos >= end_nanos` or
465    /// `step` is zero.
466    pub fn new(start_nanos: i128, end_nanos: i128, step: Duration) -> Result<Self, ConfigError> {
467        if start_nanos >= end_nanos {
468            return Err(ConfigError::Invalid("start_nanos must be < end_nanos"));
469        }
470        if step.is_zero() {
471            return Err(ConfigError::Invalid("step must be > 0"));
472        }
473        Ok(Self {
474            start_nanos,
475            end_nanos,
476            step,
477        })
478    }
479}
480
481impl std::fmt::Debug for HistoricalClockInstaller {
482    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483        f.debug_struct("HistoricalClockInstaller")
484            .field("start_nanos", &self.start_nanos)
485            .field("end_nanos", &self.end_nanos)
486            .field("step", &self.step)
487            .finish()
488    }
489}
490
491impl Installer for HistoricalClockInstaller {
492    type Poller = HistoricalClockPoller;
493
494    fn install(self, world: &mut WorldBuilder) -> HistoricalClockPoller {
495        let clock_id = world.register(Clock::default());
496        HistoricalClockPoller {
497            clock_id,
498            start_nanos: self.start_nanos,
499            end_nanos: self.end_nanos,
500            current_nanos: self.start_nanos,
501            step_nanos: self.step.as_nanos() as i128,
502            base_instant: Instant::now(),
503            exhausted: false,
504        }
505    }
506}
507
508/// Runtime poller for historical (replay) clock — auto-advances per sync.
509pub struct HistoricalClockPoller {
510    clock_id: ResourceId,
511    start_nanos: i128,
512    end_nanos: i128,
513    current_nanos: i128,
514    step_nanos: i128,
515    base_instant: Instant,
516    exhausted: bool,
517}
518
519impl HistoricalClockPoller {
520    /// Sync the Clock resource and auto-advance the replay position.
521    ///
522    /// Writes current position into Clock, then advances by one step.
523    #[inline]
524    pub fn sync(&mut self, world: &mut World) {
525        let elapsed = (self.current_nanos - self.start_nanos).max(0);
526        let elapsed_nanos = u64::try_from(elapsed).unwrap_or(u64::MAX);
527
528        // SAFETY: clock_id was returned by register() during install()
529        let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
530        clock.instant = self.base_instant + Duration::from_nanos(elapsed_nanos);
531        clock.unix_nanos = self.current_nanos;
532
533        if !self.exhausted {
534            self.current_nanos += self.step_nanos;
535            if self.current_nanos >= self.end_nanos {
536                self.current_nanos = self.end_nanos;
537                self.exhausted = true;
538            }
539        }
540    }
541
542    /// Whether the replay has reached `end_nanos`.
543    #[inline]
544    #[must_use]
545    pub fn is_exhausted(&self) -> bool {
546        self.exhausted
547    }
548
549    /// Current replay position in UTC nanos.
550    #[inline]
551    #[must_use]
552    pub fn current_nanos(&self) -> i128 {
553        self.current_nanos
554    }
555
556    /// Resets to start position.
557    #[inline]
558    pub fn reset(&mut self) {
559        self.current_nanos = self.start_nanos;
560        self.exhausted = false;
561    }
562}
563
564impl std::fmt::Debug for HistoricalClockPoller {
565    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566        f.debug_struct("HistoricalClockPoller")
567            .field("current_nanos", &self.current_nanos)
568            .field("exhausted", &self.exhausted)
569            .finish()
570    }
571}
572
573// =============================================================================
574// Tests
575// =============================================================================
576
577#[cfg(test)]
578mod tests {
579    use super::*;
580
581    // =========================================================================
582    // Clock struct
583    // =========================================================================
584
585    #[test]
586    fn clock_default() {
587        let clock = Clock::default();
588        assert_eq!(clock.unix_nanos(), 0);
589    }
590
591    // =========================================================================
592    // RealtimeClock
593    // =========================================================================
594
595    #[test]
596    fn realtime_install_and_sync() {
597        let mut wb = WorldBuilder::new();
598        let mut poller = wb.install_driver(RealtimeClockInstaller::default());
599        let mut world = wb.build();
600
601        let now = Instant::now();
602        poller.sync(&mut world, now);
603
604        let clock = world.resource::<Clock>();
605        assert_eq!(clock.instant(), now);
606
607        let expected = SystemTime::now()
608            .duration_since(UNIX_EPOCH)
609            .unwrap()
610            .as_nanos() as i128;
611        let diff = (clock.unix_nanos() - expected).unsigned_abs();
612        assert!(diff < 1_000_000_000, "nanos off by {diff}ns");
613    }
614
615    #[test]
616    fn realtime_nanos_increase() {
617        let mut wb = WorldBuilder::new();
618        let mut poller = wb.install_driver(RealtimeClockInstaller::default());
619        let mut world = wb.build();
620
621        poller.sync(&mut world, Instant::now());
622        let n1 = world.resource::<Clock>().unix_nanos();
623
624        std::thread::sleep(Duration::from_millis(1));
625        poller.sync(&mut world, Instant::now());
626        let n2 = world.resource::<Clock>().unix_nanos();
627
628        assert!(n2 > n1);
629    }
630
631    #[test]
632    fn realtime_resync_no_panic() {
633        let mut wb = WorldBuilder::new();
634        let installer = RealtimeClockInstaller::builder()
635            .resync_interval(Duration::ZERO)
636            .build()
637            .unwrap();
638        let mut poller = wb.install_driver(installer);
639        let mut world = wb.build();
640
641        poller.sync(&mut world, Instant::now());
642        assert!(world.resource::<Clock>().unix_nanos() > 0);
643    }
644
645    #[test]
646    fn realtime_zero_retries_rejected() {
647        let result = RealtimeClockInstaller::builder().max_retries(0).build();
648        assert!(matches!(result, Err(ConfigError::Invalid(_))));
649    }
650
651    // =========================================================================
652    // TestClock
653    // =========================================================================
654
655    #[test]
656    fn test_clock_install_and_sync() {
657        let mut wb = WorldBuilder::new();
658        let poller = wb.install_driver(TestClockInstaller::new());
659        let mut world = wb.build();
660
661        poller.sync(&mut world);
662        assert_eq!(world.resource::<Clock>().unix_nanos(), 0);
663    }
664
665    #[test]
666    fn test_clock_advance() {
667        let mut wb = WorldBuilder::new();
668        let mut poller = wb.install_driver(TestClockInstaller::new());
669        let mut world = wb.build();
670
671        poller.advance(Duration::from_millis(100));
672        poller.sync(&mut world);
673        assert_eq!(world.resource::<Clock>().unix_nanos(), 100_000_000);
674    }
675
676    #[test]
677    fn test_clock_starting_at() {
678        let mut wb = WorldBuilder::new();
679        let poller = wb.install_driver(TestClockInstaller::starting_at(1_000_000_000));
680        let mut world = wb.build();
681
682        poller.sync(&mut world);
683        assert_eq!(world.resource::<Clock>().unix_nanos(), 1_000_000_000);
684    }
685
686    #[test]
687    fn test_clock_set_nanos() {
688        let mut wb = WorldBuilder::new();
689        let mut poller = wb.install_driver(TestClockInstaller::new());
690        let mut world = wb.build();
691
692        poller.set_nanos(42);
693        poller.sync(&mut world);
694        assert_eq!(world.resource::<Clock>().unix_nanos(), 42);
695    }
696
697    #[test]
698    fn test_clock_reset() {
699        let mut wb = WorldBuilder::new();
700        let mut poller = wb.install_driver(TestClockInstaller::new());
701        let mut world = wb.build();
702
703        poller.advance(Duration::from_secs(10));
704        poller.reset();
705        poller.sync(&mut world);
706        assert_eq!(world.resource::<Clock>().unix_nanos(), 0);
707    }
708
709    #[test]
710    fn test_clock_instant_advances() {
711        let mut wb = WorldBuilder::new();
712        let mut poller = wb.install_driver(TestClockInstaller::new());
713        let mut world = wb.build();
714
715        poller.sync(&mut world);
716        let i1 = world.resource::<Clock>().instant();
717
718        poller.advance(Duration::from_millis(100));
719        poller.sync(&mut world);
720        let i2 = world.resource::<Clock>().instant();
721
722        assert_eq!(i2.duration_since(i1), Duration::from_millis(100));
723    }
724
725    // =========================================================================
726    // HistoricalClock
727    // =========================================================================
728
729    #[test]
730    fn historical_install_and_sync() {
731        let installer =
732            HistoricalClockInstaller::new(1000, 2000, Duration::from_nanos(100)).unwrap();
733        let mut wb = WorldBuilder::new();
734        let mut poller = wb.install_driver(installer);
735        let mut world = wb.build();
736
737        poller.sync(&mut world);
738        assert_eq!(world.resource::<Clock>().unix_nanos(), 1000); // writes before advancing
739        assert_eq!(poller.current_nanos(), 1100); // advanced after sync
740    }
741
742    #[test]
743    fn historical_exhausts() {
744        let installer = HistoricalClockInstaller::new(0, 200, Duration::from_nanos(100)).unwrap();
745        let mut wb = WorldBuilder::new();
746        let mut poller = wb.install_driver(installer);
747        let mut world = wb.build();
748
749        poller.sync(&mut world); // writes 0, advances to 100
750        poller.sync(&mut world); // writes 100, advances to 200 → exhausted
751
752        assert!(poller.is_exhausted());
753        poller.sync(&mut world); // writes 200, no further advance
754        assert_eq!(world.resource::<Clock>().unix_nanos(), 200);
755    }
756
757    #[test]
758    fn historical_reset() {
759        let installer = HistoricalClockInstaller::new(100, 500, Duration::from_nanos(100)).unwrap();
760        let mut wb = WorldBuilder::new();
761        let mut poller = wb.install_driver(installer);
762        let mut world = wb.build();
763
764        for _ in 0..10 {
765            poller.sync(&mut world);
766        }
767        assert!(poller.is_exhausted());
768
769        poller.reset();
770        assert!(!poller.is_exhausted());
771        assert_eq!(poller.current_nanos(), 100);
772    }
773
774    #[test]
775    fn historical_rejects_bad_config() {
776        assert!(HistoricalClockInstaller::new(1000, 1000, Duration::from_nanos(100)).is_err());
777        assert!(HistoricalClockInstaller::new(2000, 1000, Duration::from_nanos(100)).is_err());
778        assert!(HistoricalClockInstaller::new(0, 1000, Duration::ZERO).is_err());
779    }
780}