Skip to content

Commit

Permalink
clock: new clock implementation, with some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
da2ce7 committed Sep 10, 2022
1 parent e622c13 commit 79768b0
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

9 changes: 8 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ lto = "fat"
strip = true

[dependencies]
tokio = { version = "1.7", features = ["rt-multi-thread", "net", "sync", "macros", "signal"] }
tokio = { version = "1.7", features = [
"rt-multi-thread",
"net",
"sync",
"macros",
"signal",
] }

serde = { version = "1.0", features = ["derive"] }
serde_bencode = "^0.2.3"
Expand All @@ -28,6 +34,7 @@ serde_with = "2.0.0"
hex = "0.4.3"
percent-encoding = "2.1.0"
binascii = "0.1"
lazy_static = "1.4.0"

openssl = { version = "0.10.41", features = ["vendored"] }

Expand Down
11 changes: 11 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ pub mod protocol;
pub mod setup;
pub mod tracker;
pub mod udp;

#[macro_use]
extern crate lazy_static;

pub mod static_time {
use std::time::SystemTime;

lazy_static! {
pub static ref TIME_AT_APP_START: SystemTime = SystemTime::now();
}
}
5 changes: 4 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ use std::sync::Arc;

use log::info;
use torrust_tracker::tracker::tracker::TorrentTracker;
use torrust_tracker::{logging, setup, Configuration};
use torrust_tracker::{logging, setup, static_time, Configuration};

#[tokio::main]
async fn main() {
const CONFIG_PATH: &str = "config.toml";

// Set the time of Torrust app starting
lazy_static::initialize(&static_time::TIME_AT_APP_START);

// Initialize Torrust config
let config = match Configuration::load_from_file(CONFIG_PATH) {
Ok(config) => Arc::new(config),
Expand Down
222 changes: 222 additions & 0 deletions src/protocol/clock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
use std::cell::RefCell;
pub use std::time::Duration;
use std::time::SystemTime;

use thiserror::Error;

use crate::static_time;

pub type SinceUnixEpoch = Duration;

#[derive(Error, Debug)]
pub enum ClockError {
#[error("overflow error")]
OverflowError,
}

pub trait Time: Sized {
fn now() -> SinceUnixEpoch;

fn now_add(duration: &Duration) -> Option<SinceUnixEpoch> {
Self::now().checked_add(*duration)
}
fn now_sub(duration: &Duration) -> Option<SinceUnixEpoch> {
Self::now().checked_sub(*duration)
}
fn now_periods(period: &Duration) -> Option<u128> {
Self::now().as_nanos().checked_div(period.as_nanos())
}
fn now_add_periods(duration: &Duration, period: &Duration) -> Option<u128> {
match Self::now_add(duration) {
None => None,
Some(time) => time.as_nanos().checked_div(period.as_nanos()),
}
}
fn now_sub_periods(duration: &Duration, period: &Duration) -> Option<u128> {
match Self::now_sub(duration) {
None => None,
Some(time) => time.as_nanos().checked_div(period.as_nanos()),
}
}
}

pub trait StoppedTime: Time {
fn local_set(unix_time: &SinceUnixEpoch);
fn local_set_to_unix_epoch() {
Self::local_set(&SinceUnixEpoch::ZERO)
}

fn local_set_to_app_start_time();
fn local_set_to_system_time_now();
fn local_add(duration: &Duration) -> Result<(), ClockError>;
fn local_sub(duration: &Duration) -> Result<(), ClockError>;
fn local_reset();
}

pub struct Clock<const T: usize>;

pub enum ClockType {
WorkingClock,
StoppedClock,
}

pub type WorkingClock = Clock<{ ClockType::WorkingClock as usize }>;
pub type StoppedClock = Clock<{ ClockType::StoppedClock as usize }>;

#[cfg(not(test))]
pub type DefaultClock = WorkingClock;

#[cfg(test)]
pub type DefaultClock = StoppedClock;

mod detail {
use self::super::*;

pub fn get_app_start_time() -> SinceUnixEpoch {
(*static_time::TIME_AT_APP_START)
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
}

#[cfg(not(test))]
pub fn get_default_fixed_time() -> SinceUnixEpoch {
get_app_start_time()
}

#[cfg(test)]
pub fn get_default_fixed_time() -> SinceUnixEpoch {
Duration::ZERO
}

thread_local!(pub static FIXED_TIME: RefCell<SinceUnixEpoch> = RefCell::new(get_default_fixed_time()));
}

impl Time for WorkingClock {
fn now() -> SinceUnixEpoch {
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap()
}
}

impl Time for StoppedClock {
fn now() -> SinceUnixEpoch {
detail::FIXED_TIME.with(|time| {
return *time.borrow();
})
}
}

impl StoppedTime for StoppedClock {
fn local_set(unix_time: &SinceUnixEpoch) {
detail::FIXED_TIME.with(|time| {
*time.borrow_mut() = *unix_time;
})
}

fn local_set_to_app_start_time() {
Self::local_set(&detail::get_app_start_time())
}

fn local_set_to_system_time_now() {
Self::local_set(&detail::get_app_start_time())
}

fn local_add(duration: &Duration) -> Result<(), ClockError> {
detail::FIXED_TIME.with(|time| {
let time_borrowed = *time.borrow();
*time.borrow_mut() = match time_borrowed.checked_add(*duration) {
Some(time) => time,
None => {
return Err(ClockError::OverflowError);
}
};
Ok(())
})
}

fn local_sub(duration: &Duration) -> Result<(), ClockError> {
detail::FIXED_TIME.with(|time| {
let time_borrowed = *time.borrow();
*time.borrow_mut() = match time_borrowed.checked_sub(*duration) {
Some(time) => time,
None => {
return Err(ClockError::OverflowError);
}
};
Ok(())
})
}

fn local_reset() {
Self::local_set(&detail::get_default_fixed_time())
}
}

#[cfg(test)]
mod tests {
use std::thread;

use super::*;

#[test]
fn the_stopped_time_and_default_time_should_be_the_same_when_testing() {
// We are testing, so we should default to the fixed time.
assert_eq!(StoppedClock::now(), DefaultClock::now())
}

#[test]
fn the_stopped_time_and_working_time_should_be_different() {
assert_ne!(StoppedClock::now(), WorkingClock::now())
}

#[test]
fn the_stopped_time_should_default_to_unix_epoch_when_testing() {
assert_eq!(StoppedClock::now(), SinceUnixEpoch::ZERO)
}

#[test]
fn the_stopped_time_should_be_modifiable() {
// Check we start with ZERO.
assert_eq!(StoppedClock::now(), Duration::ZERO);

// Set to Current Time and Check
let timestamp = WorkingClock::now();
StoppedClock::local_set(&timestamp);
assert_eq!(StoppedClock::now(), timestamp);

// Elapse the Current Time and Check
StoppedClock::local_add(&timestamp).unwrap();
assert_eq!(StoppedClock::now(), timestamp + timestamp);

// Reset to ZERO and Check
StoppedClock::local_reset();
assert_eq!(StoppedClock::now(), Duration::ZERO);
}

#[test]
fn fixed_time_should_default_to_zero_on_new_and_thread_exit() {
assert_eq!(StoppedClock::now(), Duration::ZERO);
let after5 = WorkingClock::now_add(&Duration::from_secs(5)).unwrap();
StoppedClock::local_set(&after5);
assert_eq!(StoppedClock::now(), after5);

let t = thread::spawn(move || {
// each thread starts out with the initial value of ZERO
assert_eq!(StoppedClock::now(), Duration::ZERO);

// and gets set to the current time.
let timestamp = WorkingClock::now();
StoppedClock::local_set(&timestamp);
assert_eq!(StoppedClock::now(), timestamp);
});

// wait for the thread to complete and bail out on panic
t.join().unwrap();

// we retain our original value of current time + 5sec despite the child thread
assert_eq!(StoppedClock::now(), after5);

// Reset to ZERO and Check
StoppedClock::local_reset();
assert_eq!(StoppedClock::now(), Duration::ZERO);
}
}

0 comments on commit 79768b0

Please sign in to comment.