Skip to content

Commit

Permalink
Reimplemented the clock in terms of time crate (which obsoletes the c…
Browse files Browse the repository at this point in the history
…hrono crate dependency).
  • Loading branch information
pompon0 committed Jun 10, 2022
1 parent 23b205a commit 7d6c962
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions chain/network-primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ publish = true
anyhow = "1.0.51"
actix = "0.13.0"
borsh = "0.9"
once_cell = "1.12.0"
chrono = { version = "0.4.4", features = ["serde"] }
deepsize = { version = "0.2.0", optional = true }
serde = { version = "1", features = ["alloc", "derive", "rc"] }
strum = { version = "0.24", features = ["derive"] }
time = "0.3.9"
tokio = { version = "1.1", features = ["net", "rt-multi-thread"] }
tracing = "0.1.13"

Expand Down
1 change: 1 addition & 0 deletions chain/network-primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ mod blacklist;
pub(crate) mod config;
pub(crate) mod config_json;
mod network_protocol;
pub mod time;
pub mod types;
127 changes: 127 additions & 0 deletions chain/network-primitives/src/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/// Time module provides a non-global clock, which should be passed
/// as an argument to functions which need to read the current time.
/// In particular try to avoid storing the clock instances in the objects.
/// Functions which use system clock directly are non-hermetic, which
/// makes them effectively non-deterministic and hard to test.
///
/// Clock provides 2 types of time reads:
/// 1. now() (aka POSIX CLOCK_MONOTONIC, aka time::Instant)
/// time as perceived by the machine making the measurement.
/// The subsequent calls to now() are guaranteed to return monotonic
/// results. It should be used for measuring the latency of operations
/// as observed by the machine. The time::Instant itself doesn't
/// translate to any specific timestamp, so it is not meaningful for
/// anyone other than the machine doing the measurement.
/// 2. utc_now() (aka POSIX CLOCK_REALTIME, aka time::Utc)
/// expected to approximate the (global) UTC time.
/// There is NO guarantee that the subsequent reads will be monotonic,
/// as CLOCK_REALTIME it configurable in the OS settings, or can be updated
/// during NTP sync. Should be used whenever you need to communicate a timestamp
/// over the network, or store it for later use. Remember that clocks
/// of different machines are not perfectly synchronized, and in extreme
/// cases can be totally skewed.
use once_cell::sync::Lazy;
use std::sync::{Arc, RwLock};
pub use time::error;

// TODO: consider wrapping these types to prevent interactions
// with other time libraries, especially the the direct access
// to the realtime (i.e. not through the Clock).
pub type Instant = time::Instant;
pub type Utc = time::OffsetDateTime;
pub type Duration = time::Duration;

// Instant doesn't have a deterministic contructor,
// however since Instant is not convertible to an unix timestamp,
// we can snapshot Instant::now() once and treat it as a constant.
// All observable effects will be then deterministic.
static FAKE_CLOCK_MONO_START: Lazy<Instant> = Lazy::new(Instant::now);

// An arbitrary non-trivial deterministic Utc timestamp.
const FAKE_CLOCK_UTC_START: Lazy<Utc> = Lazy::new(|| Utc::from_unix_timestamp(89108233).unwrap());

#[derive(Clone)]
enum ClockInner {
Real,
Fake(FakeClock),
}

/// Clock encapsulates a system clock, allowing to replace it
/// with a fake in tests.
/// Since system clock is a source of external information,
/// it has to be replaced with a fake double, if we want our
/// tests to be deterministic.
///
/// This is a reimplementation of primitives/src/time.rs
/// with a more systematic approach.
/// TODO: add tests, put it is some reusable package and use
/// throughout the nearcore codebase.
#[derive(Clone)]
pub struct Clock(ClockInner);

impl Clock {
/// Constructor of the real clock. Use it in production code.
/// Preferrably construct it directly in the main() function,
/// so that it can be faked out in every other function.
pub fn real() -> Clock {
Clock(ClockInner::Real)
}
/// Current time according to the monotonic clock.
pub fn now(&self) -> Instant {
match &self.0 {
ClockInner::Real => Instant::now(),
ClockInner::Fake(fake) => fake.now(),
}
}
/// Current time according to the system/walltime clock.
pub fn now_utc(&self) -> Utc {
match &self.0 {
ClockInner::Real => Utc::now_utc(),
ClockInner::Fake(fake) => fake.now_utc(),
}
}
}

struct FakeClockInner {
mono: Instant,
utc: Utc,
}

/// TEST-ONLY
#[derive(Clone)]
pub struct FakeClock(Arc<RwLock<FakeClockInner>>);

impl FakeClock {
/// Constructor of a fake clock. Use it in tests.
/// It allows for manually moving the time forward (via advance())
/// and arbitrarly setting the UTC time in runtime.
/// Use FakeClock::clock() when calling prod code from tests.
// TODO: add support for auto-advancing the clock at each read.
pub fn new(utc: Utc) -> Self {
Self(Arc::new(RwLock::new(FakeClockInner { utc, mono: *FAKE_CLOCK_MONO_START })))
}
pub fn now(&self) -> Instant {
self.0.read().unwrap().mono
}
pub fn now_utc(&self) -> Utc {
self.0.read().unwrap().utc
}
pub fn clock(&self) -> Clock {
Clock(ClockInner::Fake(self.clone()))
}
pub fn advance(&self, d: Duration) {
assert!(d >= Duration::ZERO);
let mut c = self.0.write().unwrap();
c.mono += d;
c.utc += d;
}
pub fn set_utc(&self, utc: Utc) {
self.0.write().unwrap().utc = utc;
}
}

impl Default for FakeClock {
fn default() -> FakeClock {
Self::new(*FAKE_CLOCK_UTC_START)
}
}
3 changes: 3 additions & 0 deletions core/primitives/src/time.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/// Provides structs used for getting time.
/// TODO: this module is deprecated and scheduled to be replaced with
/// chain/network-primitives/time.
///
/// WARNING WARNING WARNING
/// WARNING WARNING WARNING
/// Use at your own risk. The implementation is not complete, we have a places in code not mocked properly.
Expand Down

0 comments on commit 7d6c962

Please sign in to comment.