use std::{
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
static USE_SIM_CLOCKS: AtomicBool = AtomicBool::new(false);
pub struct SimClocksGuard(());
impl SimClocksGuard {
pub fn init() -> Self {
USE_SIM_CLOCKS.store(true, Ordering::Release);
Self(())
}
}
impl Drop for SimClocksGuard {
fn drop(&mut self) {
USE_SIM_CLOCKS.store(false, Ordering::Release);
}
}
#[derive(Debug, Copy, Clone)]
struct StdTimespec {
tv_sec: u64,
tv_nsec: u32,
}
impl From<StdTimespec> for libc::timespec {
fn from(value: StdTimespec) -> Self {
Self {
tv_sec: value.tv_sec as i64,
tv_nsec: value.tv_nsec as libc::c_long,
}
}
}
fn turmoil_elapsed() -> libc::timespec {
let elapsed = turmoil::sim_elapsed().unwrap_or(Duration::ZERO);
libc::timespec {
tv_sec: elapsed.as_secs() as i64,
tv_nsec: elapsed.subsec_nanos() as libc::c_long,
}
}
unsafe fn tokio_instant_now() -> libc::timespec {
let instant = tokio::time::Instant::now();
let ts: StdTimespec = unsafe { std::mem::transmute(instant) };
ts.into()
}
#[unsafe(no_mangle)]
#[inline(never)]
unsafe extern "C" fn clock_gettime(
clockid: libc::clockid_t,
tp: *mut libc::timespec,
) -> libc::c_int {
if USE_SIM_CLOCKS.load(Ordering::Acquire) && tokio::runtime::Handle::try_current().is_ok() {
let timespec = match clockid {
libc::CLOCK_REALTIME => Some(turmoil_elapsed()),
#[cfg(target_os = "linux")]
libc::CLOCK_REALTIME_COARSE => Some(turmoil_elapsed()),
libc::CLOCK_MONOTONIC | libc::CLOCK_MONOTONIC_RAW => {
Some(unsafe { tokio_instant_now() })
}
#[cfg(target_os = "linux")]
libc::CLOCK_MONOTONIC_COARSE | libc::CLOCK_BOOTTIME => {
Some(unsafe { tokio_instant_now() })
}
#[cfg(target_os = "macos")]
libc::CLOCK_UPTIME_RAW => Some(unsafe { tokio_instant_now() }),
_ => {
eprintln!(
"Unsupported clock, real implementation will be used -- clock_gettime({clockid:?})"
);
None
}
};
if let Some(timespec) = timespec {
unsafe { tp.write(timespec) };
return 0;
}
}
unsafe {
tp.write(libc::timespec {
tv_sec: 0,
tv_nsec: 0,
});
}
0
}