use core::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering};
use std::sync::OnceLock;
use std::time::{Duration, Instant};
static ALTERED: AtomicUsize = AtomicUsize::new(0);
pub(crate) fn origin() -> Instant {
static ORIGIN: OnceLock<Instant> = OnceLock::new();
*ORIGIN.get_or_init(Instant::now)
}
fn nanos(at: Instant) -> u64 {
u64::try_from(at.saturating_duration_since(origin()).as_nanos()).unwrap_or(u64::MAX)
}
fn instant(nanos: u64) -> Instant {
origin() + Duration::from_nanos(nanos)
}
pub(crate) struct Clock {
paused: AtomicBool,
base: AtomicU64,
skew: AtomicI64,
last: AtomicU64,
altered: AtomicBool,
advancing: AtomicBool,
members: AtomicU64,
}
impl Clock {
pub(crate) fn new(start_paused: bool) -> Self {
let clock = Self {
paused: AtomicBool::new(false),
base: AtomicU64::new(0),
skew: AtomicI64::new(0),
last: AtomicU64::new(0),
altered: AtomicBool::new(false),
advancing: AtomicBool::new(false),
members: AtomicU64::new(0),
};
if start_paused {
clock.pause();
}
clock
}
pub(crate) fn now(&self) -> Instant {
if !self.altered.load(Ordering::Acquire) {
return Instant::now();
}
instant(self.now_nanos())
}
fn now_nanos(&self) -> u64 {
let read = if self.paused.load(Ordering::Acquire) {
self.base.load(Ordering::Acquire)
} else {
nanos(Instant::now()).saturating_add_signed(self.skew.load(Ordering::Acquire))
};
let last = self.last.fetch_max(read, Ordering::AcqRel);
read.max(last)
}
fn alter(&self) {
if !self.altered.swap(true, Ordering::AcqRel) {
ALTERED.fetch_add(1, Ordering::AcqRel);
}
}
pub(crate) fn is_paused(&self) -> bool {
self.paused.load(Ordering::Acquire)
}
pub(crate) fn is_altered(&self) -> bool {
self.altered.load(Ordering::Acquire)
}
pub(crate) fn claim_advance(&self) -> bool {
self.advancing
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
.is_ok()
}
pub(crate) fn release_advance(&self) {
self.advancing.store(false, Ordering::SeqCst);
}
pub(crate) fn wait_advance(&self) {
while self.advancing.load(Ordering::SeqCst) {
core::hint::spin_loop();
}
}
pub(crate) fn members(&self) -> u64 {
self.members.load(Ordering::SeqCst)
}
pub(crate) fn member_changed(&self) {
self.members.fetch_add(1, Ordering::SeqCst);
}
pub(crate) fn pause(&self) {
let now = if self.altered.load(Ordering::Acquire) {
self.now_nanos()
} else {
nanos(Instant::now())
};
self.alter();
if self.paused.load(Ordering::Acquire) {
return;
}
let now = now.div_ceil(1_000_000).saturating_mul(1_000_000);
self.base.store(now, Ordering::Release);
self.paused.store(true, Ordering::Release);
self.base
.fetch_max(self.last.load(Ordering::Acquire), Ordering::AcqRel);
self.last.fetch_max(now, Ordering::AcqRel);
}
pub(crate) fn resume(&self) {
if !self.paused.load(Ordering::Acquire) {
return;
}
let base = self.base.load(Ordering::Acquire);
let skew = i128::from(base) - i128::from(nanos(Instant::now()));
let skew = i64::try_from(skew).unwrap_or(if skew < 0 { i64::MIN } else { i64::MAX });
self.skew.store(skew, Ordering::Release);
self.paused.store(false, Ordering::Release);
}
pub(crate) fn advance(&self, by: Duration) {
self.alter();
let by = u64::try_from(by.as_nanos()).unwrap_or(u64::MAX);
if self.paused.load(Ordering::Acquire) {
let _ = self
.base
.try_update(Ordering::AcqRel, Ordering::Acquire, |b| {
Some(b.saturating_add(by))
});
} else {
let by = i64::try_from(by).unwrap_or(i64::MAX);
let _ = self
.skew
.try_update(Ordering::AcqRel, Ordering::Acquire, |s| {
Some(s.saturating_add(by))
});
}
}
pub(crate) fn advance_to_nanos(&self, at: u64) {
self.base.fetch_max(at, Ordering::AcqRel);
}
}
impl Drop for Clock {
fn drop(&mut self) {
if *self.altered.get_mut() {
ALTERED.fetch_sub(1, Ordering::AcqRel);
}
}
}
pub(crate) fn any_altered() -> bool {
ALTERED.load(Ordering::Relaxed) != 0
}
pub(crate) fn to_nanos(at: Instant) -> u64 {
nanos(at)
}
pub(crate) fn now() -> Instant {
if ALTERED.load(Ordering::Relaxed) == 0 {
return Instant::now();
}
crate::rt::runtime::with_clock(|clock| clock.map_or_else(Instant::now, Clock::now))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_paused_clock_stands_still_until_advanced() {
let clock = Clock::new(true);
let a = clock.now();
std::thread::sleep(Duration::from_millis(5));
assert_eq!(clock.now(), a);
clock.advance(Duration::from_secs(3));
assert_eq!(clock.now(), a + Duration::from_secs(3));
clock.advance_to_nanos(to_nanos(a + Duration::from_secs(2)));
assert_eq!(clock.now(), a + Duration::from_secs(3), "never back");
clock.advance_to_nanos(to_nanos(a + Duration::from_secs(5)));
assert_eq!(clock.now(), a + Duration::from_secs(5));
}
#[test]
fn a_resumed_clock_runs_on_from_where_it_stood() {
let clock = Clock::new(true);
let stood = clock.now();
std::thread::sleep(Duration::from_millis(20));
clock.resume();
let after = clock.now();
assert!(after >= stood);
assert!(
after - stood < Duration::from_millis(20),
"the time paused is not counted"
);
std::thread::sleep(Duration::from_millis(5));
assert!(clock.now() >= after + Duration::from_millis(5));
}
#[test]
fn reads_never_go_backwards_across_pauses_on_many_threads() {
let clock = std::sync::Arc::new(Clock::new(false));
let readers: Vec<_> = (0..4)
.map(|_| {
let clock = std::sync::Arc::clone(&clock);
std::thread::spawn(move || {
let mut last = clock.now();
for _ in 0..20_000 {
let now = clock.now();
assert!(now >= last, "the clock went back");
last = now;
}
})
})
.collect();
for i in 0..200 {
if i % 2 == 0 {
clock.pause();
clock.advance(Duration::from_micros(3));
} else {
clock.resume();
}
}
for reader in readers {
reader.join().unwrap();
}
}
#[test]
fn an_advance_while_running_moves_the_clock_ahead() {
let clock = Clock::new(false);
let before = clock.now();
clock.advance(Duration::from_secs(60));
assert!(clock.now() >= before + Duration::from_secs(60));
assert!(!clock.is_paused());
}
}