diff options
| -rw-r--r-- | crates/tor-rtmock/src/coarse_time.rs | 41 | ||||
| -rw-r--r-- | crates/tor-rtmock/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/tor-rtmock/src/simple_time.rs | 35 | ||||
| -rw-r--r-- | crates/tor-rtmock/src/time.rs | 49 | ||||
| -rw-r--r-- | crates/tor-rtmock/src/time_core.rs | 109 |
5 files changed, 147 insertions, 89 deletions
diff --git a/crates/tor-rtmock/src/coarse_time.rs b/crates/tor-rtmock/src/coarse_time.rs deleted file mode 100644 index b96cc9f0f..000000000 --- a/crates/tor-rtmock/src/coarse_time.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! [`MockCoarseTimeProvider`] - -use std::time::Duration; -use tor_rtcompat::{CoarseDuration, CoarseInstant}; -use tor_rtcompat::{CoarseTimeProvider, RealCoarseTimeProvider}; - -/// A mockable [`CoarseTimeProvider`] -#[derive(Clone, Debug)] -pub(crate) struct MockCoarseTimeProvider { - /// Starting point - started: CoarseInstant, - - /// How much we have advanced - /// - /// We track this as a `Duration`, not a [`CoarseDuration`] (or [`CoarseInstant`]) - /// to avoid accumulating rounding errors, - /// which might otherwise cause the mocked `Instant` and `CoarseInstant` - /// clocks to run at noticeably different *rates*. - elapsed: Duration, -} - -impl MockCoarseTimeProvider { - /// Start a new [`MockCoarseTimeProvider`] - pub(crate) fn new() -> Self { - MockCoarseTimeProvider { - started: RealCoarseTimeProvider::new().now_coarse(), - elapsed: Duration::ZERO, - } - } - - /// Advance the mocked coarse time by `dur` - pub(crate) fn advance(&mut self, dur: Duration) { - self.elapsed += dur; - } -} - -impl CoarseTimeProvider for MockCoarseTimeProvider { - fn now_coarse(&self) -> CoarseInstant { - self.started + CoarseDuration::from(self.elapsed) - } -} diff --git a/crates/tor-rtmock/src/lib.rs b/crates/tor-rtmock/src/lib.rs index 56685f691..e59d88ba2 100644 --- a/crates/tor-rtmock/src/lib.rs +++ b/crates/tor-rtmock/src/lib.rs @@ -52,10 +52,10 @@ pub mod simple_time; pub mod task; pub mod time; -mod coarse_time; mod net_runtime; mod runtime; mod sleep_runtime; +mod time_core; pub use net_runtime::MockNetRuntime; pub use runtime::MockRuntime; diff --git a/crates/tor-rtmock/src/simple_time.rs b/crates/tor-rtmock/src/simple_time.rs index afff87811..1b1c488de 100644 --- a/crates/tor-rtmock/src/simple_time.rs +++ b/crates/tor-rtmock/src/simple_time.rs @@ -17,7 +17,7 @@ use tor_rtcompat::CoarseInstant; use tor_rtcompat::CoarseTimeProvider; use tor_rtcompat::SleepProvider; -use crate::coarse_time::MockCoarseTimeProvider; +use crate::time_core::MockTimeCore; /// Simple provider of simulated time /// @@ -71,14 +71,8 @@ pub struct SleepFuture { /// | DROPPED | dropped | absent | absent | #[derive(Debug, AsMut)] struct State { - /// Current time - now: Instant, - /// Current time (coarse) - coarse: MockCoarseTimeProvider, - - /// Current wallclock time - wallclock: SystemTime, + core: MockTimeCore, /// Futures; record of every existing [`SleepFuture`], including any `Waker` /// @@ -113,9 +107,7 @@ impl Provider { /// Return a new mock time provider starting at a specified point in time pub fn new(now: Instant, wallclock: SystemTime) -> Self { let state = State { - now, - coarse: MockCoarseTimeProvider::new(), - wallclock, + core: MockTimeCore::new(now, wallclock), futures: Default::default(), unready: Default::default(), }; @@ -161,9 +153,7 @@ impl Provider { /// use [`MockExecutor::advance_*()`](crate::MockRuntime). pub fn advance(&self, d: Duration) { let mut state = self.lock(); - state.now += d; - state.coarse.advance(d); - state.wallclock += d; + state.core.advance(d); state.wake_any(); } @@ -172,8 +162,7 @@ impl Provider { /// This has no effect on any sleeping futures. /// It only affects the return value from [`.wallclock()`](Provider::wallclock). pub fn jump_wallclock(&self, new_wallclock: SystemTime) { - let mut state = self.lock(); - state.wallclock = new_wallclock; + self.lock().core.jump_wallclock(new_wallclock); // Really we ought to wake people up, here. // But absolutely every Rust API is wrong: none offer a way to sleep until a SystemTime. // (There might be some less-portable non-Rust APIs for that.) @@ -194,7 +183,7 @@ impl Provider { let Reverse(until) = state.unready.peek()?.1; // The invariant (see `State`) guarantees that entries in `unready` are always `> now`, // so we don't whether duration_since would panic or saturate. - let d = until.duration_since(state.now); + let d = until.duration_since(state.core.instant()); Some(d) } @@ -209,7 +198,7 @@ impl SleepProvider for Provider { fn sleep(&self, d: Duration) -> SleepFuture { let mut state = self.lock(); - let until = state.now + d; + let until = state.core.instant() + d; let id = state.futures.insert(None); state.unready.push(id, Reverse(until)); @@ -231,16 +220,16 @@ impl SleepProvider for Provider { } fn now(&self) -> Instant { - self.lock().now + self.lock().core.instant() } fn wallclock(&self) -> SystemTime { - self.lock().wallclock + self.lock().core.wallclock() } } impl CoarseTimeProvider for Provider { fn now_coarse(&self) -> CoarseInstant { - self.lock().coarse.now_coarse() + self.lock().core.coarse().now_coarse() } } @@ -251,7 +240,7 @@ impl Future for SleepFuture { let mut state = self.prov.lock(); if let Some((_, Reverse(scheduled))) = state.unready.get(&self.id) { // Presence of this entry implies scheduled > now: we are UNPOLLED or WAITING - assert!(*scheduled > state.now); + assert!(*scheduled > state.core.instant()); let waker = Some(cx.waker().clone()); // Make this be WAITING. (If we're re-polled, we simply drop any previous waker.) *state @@ -276,7 +265,7 @@ impl State { loop { match self.unready.peek() { // Keep picking off entries with scheduled <= now - Some((_, Reverse(scheduled))) if *scheduled <= self.now => { + Some((_, Reverse(scheduled))) if *scheduled <= self.core.instant() => { let (id, _) = self.unready.pop().expect("vanished"); // We can .take() the waker since this can only ever run once // per sleep future (since it happens when we pop it from unready). diff --git a/crates/tor-rtmock/src/time.rs b/crates/tor-rtmock/src/time.rs index 1f3a9623f..58272fac6 100644 --- a/crates/tor-rtmock/src/time.rs +++ b/crates/tor-rtmock/src/time.rs @@ -29,7 +29,7 @@ use std::collections::HashSet; use std::fmt::Formatter; use tor_rtcompat::{CoarseInstant, CoarseTimeProvider, SleepProvider}; -use crate::coarse_time::MockCoarseTimeProvider; +use crate::time_core::MockTimeCore; /// A dummy [`SleepProvider`] instance for testing. /// @@ -161,14 +161,8 @@ impl fmt::Debug for MockSleepProvider { /// Shared backend for sleep provider and Sleeping futures. struct SleepSchedule { - /// What time do we pretend it is (monotonic)? This value only - /// moves forward. - instant: Instant, - /// Coarse time tracker - coarse: MockCoarseTimeProvider, - /// What time do we pretend it is (wall clock)? This value can move - /// in any way, but usually moves in step with `instant`. - wallclock: SystemTime, + /// What time do we pretend it is? + core: MockTimeCore, /// Priority queue of events, in the order that we should wake them. sleepers: BinaryHeap<SleepEntry>, /// If the mock time system is being driven by a `WaitFor`, holds a `Waker` to wake up that @@ -216,10 +210,9 @@ impl MockSleepProvider { pub fn new(wallclock: SystemTime) -> Self { let instant = Instant::now(); let sleepers = BinaryHeap::new(); + let core = MockTimeCore::new(instant, wallclock); let state = SleepSchedule { - instant, - coarse: MockCoarseTimeProvider::new(), - wallclock, + core, sleepers, waitfor_waker: None, sleepers_made: 0, @@ -258,9 +251,7 @@ impl MockSleepProvider { // It's not so great to unwrap here in general, but since this is // only testing code we don't really care. let mut state = self.state.lock().expect("Poisoned lock for state"); - state.wallclock += dur; - state.instant += dur; - state.coarse.advance(dur); + state.core.advance(dur); state.fire(); } @@ -273,7 +264,7 @@ impl MockSleepProvider { /// the internal timer state, and the lock is poisoned. pub fn jump_to(&self, new_wallclock: SystemTime) { let mut state = self.state.lock().expect("Poisoned lock for state"); - state.wallclock = new_wallclock; + state.core.jump_wallclock(new_wallclock); } /// Return the amount of virtual time until the next timeout @@ -283,7 +274,7 @@ impl MockSleepProvider { /// timeout should elapse right now, return Some(0). pub(crate) fn time_until_next_timeout(&self) -> Option<Duration> { let state = self.state.lock().expect("Poisoned lock for state"); - let now = state.instant; + let now = state.core.instant(); state .sleepers .peek() @@ -327,7 +318,7 @@ impl MockSleepProvider { // we have quota to advance up to a certain time while advances are blocked. // Let's see when the next timeout is, and whether it falls within that quota. let next_timeout = { - let now = state.instant; + let now = state.core.instant(); state .sleepers .peek() @@ -391,7 +382,7 @@ impl SleepSchedule { fn fire(&mut self) { use std::collections::binary_heap::PeekMut; - let now = self.instant; + let now = self.core.instant(); while let Some(top) = self.sleepers.peek_mut() { if now < top.when { return; @@ -436,7 +427,7 @@ impl SleepProvider for MockSleepProvider { type SleepFuture = Sleeping; fn sleep(&self, duration: Duration) -> Self::SleepFuture { let mut provider = self.state.lock().expect("Poisoned lock for state"); - let when = provider.instant + duration; + let when = provider.core.instant() + duration; // We're making a new sleeper, so register this in the state. provider.sleepers_made += 1; trace!( @@ -481,20 +472,30 @@ impl SleepProvider for MockSleepProvider { } fn now(&self) -> Instant { - self.state.lock().expect("Poisoned lock for state").instant + self.state + .lock() + .expect("Poisoned lock for state") + .core + .instant() } fn wallclock(&self) -> SystemTime { self.state .lock() .expect("Poisoned lock for state") - .wallclock + .core + .wallclock() } } impl CoarseTimeProvider for MockSleepProvider { fn now_coarse(&self) -> CoarseInstant { - self.state.lock().expect("poisoned").coarse.now_coarse() + self.state + .lock() + .expect("poisoned") + .core + .coarse() + .now_coarse() } } @@ -535,7 +536,7 @@ impl Future for Sleeping { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { if let Some(provider) = Weak::upgrade(&self.provider) { let mut provider = provider.lock().expect("Poisoned lock for provider"); - let now = provider.instant; + let now = provider.core.instant(); if now >= self.when { // The sleep time's elapsed. diff --git a/crates/tor-rtmock/src/time_core.rs b/crates/tor-rtmock/src/time_core.rs new file mode 100644 index 000000000..e4cabe30d --- /dev/null +++ b/crates/tor-rtmock/src/time_core.rs @@ -0,0 +1,109 @@ +//! [`MockTimeCore`] and [`MockCoarseTimeProvider`] + +use derive_adhoc::{define_derive_adhoc, Adhoc}; +use std::time::{Duration, Instant, SystemTime}; +use tor_rtcompat::{CoarseDuration, CoarseInstant}; +use tor_rtcompat::{CoarseTimeProvider, RealCoarseTimeProvider}; + +define_derive_adhoc! { + /// Derive getters for struct fields. + /// + /// Like `amplify::Getters` but `pub(crate)`. + /// + /// TODO add this feature to `amplify`. + CrateGetters = + ${define REF ${if not(fmeta(getter_copy)) { & }}} + $( + impl $ttype { + ${fattrs doc} + pub(crate) fn $fname(&self) -> $REF $ftype { + $REF self.$fname + } + } + ) +} + +/// Mock time, as a value +/// +/// Contains an `Instant`, `SystemTime` and `CoarseInstant`. +/// +/// Arranges that they are all moved in step, +/// unless explicitly requested otherwise. +#[derive(Clone, Debug, Adhoc)] +#[derive_adhoc(CrateGetters)] +pub(crate) struct MockTimeCore { + /// Current time (monotonic clock) + #[adhoc(getter_copy)] + instant: Instant, + + /// Current wallclock time + #[adhoc(getter_copy)] + wallclock: SystemTime, + + /// Coarse time tracking + coarse: MockCoarseTimeProvider, +} + +impl MockTimeCore { + /// Create a new `MockTimeCore` + pub(crate) fn new(instant: Instant, wallclock: SystemTime) -> Self { + MockTimeCore { + instant, + coarse: MockCoarseTimeProvider::new(), + wallclock, + } + } + + /// Advance by a duration + /// + /// All three time values are advanced in step. + pub(crate) fn advance(&mut self, d: Duration) { + self.instant += d; + self.wallclock += d; + self.coarse.advance(d); + } + + /// Warp the wallclock (only) + // + // We *could* just expose the field for mutable access, + // but this way seems more regular. + pub(crate) fn jump_wallclock(&mut self, new_wallclock: SystemTime) { + self.wallclock = new_wallclock; + } +} + +/// A mockable [`CoarseTimeProvider`] +#[derive(Clone, Debug)] +pub(crate) struct MockCoarseTimeProvider { + /// Starting point + started: CoarseInstant, + + /// How much we have advanced + /// + /// We track this as a `Duration`, not a [`CoarseDuration`] (or [`CoarseInstant`]) + /// to avoid accumulating rounding errors, + /// which might otherwise cause the mocked `Instant` and `CoarseInstant` + /// clocks to run at noticeably different *rates*. + elapsed: Duration, +} + +impl MockCoarseTimeProvider { + /// Start a new [`MockCoarseTimeProvider`] + pub(crate) fn new() -> Self { + MockCoarseTimeProvider { + started: RealCoarseTimeProvider::new().now_coarse(), + elapsed: Duration::ZERO, + } + } + + /// Advance the mocked coarse time by `dur` + pub(crate) fn advance(&mut self, dur: Duration) { + self.elapsed += dur; + } +} + +impl CoarseTimeProvider for MockCoarseTimeProvider { + fn now_coarse(&self) -> CoarseInstant { + self.started + CoarseDuration::from(self.elapsed) + } +} |
