pub(crate) struct Quartz;
impl Quartz {
#[inline]
pub(crate) fn now() -> u64 {
read()
}
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
#[inline]
fn read() -> u64 {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let rc = unsafe { libc::clock_gettime(clock(), &mut ts) };
if rc != 0 {
return 0;
}
nanos(&ts)
}
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn nanos(ts: &libc::timespec) -> u64 {
(ts.tv_sec as u64)
.wrapping_mul(1_000_000_000)
.wrapping_add(ts.tv_nsec as u64)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[inline]
fn clock() -> libc::clockid_t {
use core::sync::atomic::{AtomicI32, Ordering};
static CLOCK: AtomicI32 = AtomicI32::new(-1);
let clock = CLOCK.load(Ordering::Relaxed);
if clock >= 0 {
return clock;
}
let mut tick = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
let known = unsafe { libc::clock_getres(libc::CLOCK_MONOTONIC_COARSE, &mut tick) } == 0;
let chosen = if known && nanos(&tick) <= super::tuning::DRIVE_BOUND {
libc::CLOCK_MONOTONIC_COARSE
} else {
libc::CLOCK_MONOTONIC
};
CLOCK.store(chosen, Ordering::Relaxed);
chosen
}
#[cfg(target_os = "macos")]
#[inline]
fn clock() -> libc::clockid_t {
libc::CLOCK_UPTIME_RAW
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_os = "macos")))]
fn read() -> u64 {
use std::sync::OnceLock;
use std::time::Instant;
static START: OnceLock<Instant> = OnceLock::new();
let start = *START.get_or_init(Instant::now);
u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX)
}
#[cfg(test)]
mod tests {
use super::Quartz;
use crate::rt::tuning::DRIVE_BOUND;
use std::time::{Duration, Instant};
#[test]
fn it_steps_by_no_more_than_the_drive_bound() {
let deadline = Instant::now() + Duration::from_millis(200);
let mut last = Quartz::now();
let mut step = u64::MAX;
while Instant::now() < deadline {
let now = Quartz::now();
if now != last {
step = step.min(now - last);
last = now;
}
}
assert!(
step <= DRIVE_BOUND + DRIVE_BOUND / 10,
"the clock steps by {step} ns"
);
}
#[test]
fn it_never_goes_backwards_and_advances() {
let first = Quartz::now();
let mut last = first;
let deadline = Instant::now() + Duration::from_secs(5);
while last.wrapping_sub(first) < 30_000_000 {
assert!(Instant::now() < deadline, "the clock never advanced");
let now = Quartz::now();
assert!(now >= last, "the clock went backwards");
last = now;
std::thread::yield_now();
}
}
}