use std::cell::{Cell, RefCell};
use std::future::Future;
use std::pin::Pin;
use std::time::{Duration, Instant};
pub type Sleep = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
thread_local! {
static SYSTEM_BIRTH: Cell<Instant> = Cell::new(Instant::now());
static CLOCK_OVERRIDE: RefCell<Option<ClockHandle>> = const { RefCell::new(None) };
}
#[derive(Clone)]
enum ClockHandle {
System,
#[cfg(any(test, feature = "test-utils"))]
Sim(std::sync::Arc<SimClockInner>),
}
fn current_clock() -> ClockHandle {
CLOCK_OVERRIDE.with(|clock| clock.borrow().clone().unwrap_or(ClockHandle::System))
}
#[must_use]
pub fn now() -> Instant {
match current_clock() {
ClockHandle::System => Instant::now(),
#[cfg(any(test, feature = "test-utils"))]
ClockHandle::Sim(clock) => clock.now(),
}
}
#[must_use]
pub fn nanos_since_birth() -> u64 {
match current_clock() {
ClockHandle::System => SYSTEM_BIRTH.with(|birth| {
Instant::now()
.duration_since(birth.get())
.as_nanos()
.saturating_add(1) as u64
}),
#[cfg(any(test, feature = "test-utils"))]
ClockHandle::Sim(clock) => clock.nanos_since_birth(),
}
}
#[must_use]
pub fn sleep_until(deadline: Instant) -> Sleep {
match current_clock() {
ClockHandle::System => Box::pin(tokio::time::sleep_until(tokio::time::Instant::from_std(
deadline,
))),
#[cfg(any(test, feature = "test-utils"))]
ClockHandle::Sim(clock) => clock.sleep_until(deadline),
}
}
#[must_use]
pub fn sleep(duration: Duration) -> Sleep {
sleep_until(now() + duration)
}
#[cfg(any(test, feature = "test-utils"))]
#[derive(Clone, Debug)]
pub struct SimClock {
inner: std::sync::Arc<SimClockInner>,
}
#[cfg(any(test, feature = "test-utils"))]
#[derive(Debug)]
struct SimClockInner {
state: std::sync::Mutex<SimClockState>,
}
#[cfg(any(test, feature = "test-utils"))]
#[derive(Debug)]
struct SimClockState {
birth: Instant,
now: Instant,
waiters: Vec<Waiter>,
}
#[cfg(any(test, feature = "test-utils"))]
#[derive(Debug)]
struct Waiter {
deadline: Instant,
tx: tokio::sync::oneshot::Sender<()>,
}
#[cfg(any(test, feature = "test-utils"))]
impl SimClock {
#[must_use]
pub fn new() -> Self {
let now = Instant::now();
Self {
inner: std::sync::Arc::new(SimClockInner {
state: std::sync::Mutex::new(SimClockState {
birth: now,
now,
waiters: Vec::new(),
}),
}),
}
}
#[must_use]
pub fn now(&self) -> Instant {
self.inner.now()
}
pub fn advance(&self, duration: Duration) {
self.inner.advance(self.now() + duration);
}
pub fn advance_to(&self, deadline: Instant) {
self.inner.advance(deadline);
}
#[must_use]
pub fn install(&self) -> ClockOverrideGuard {
let previous =
CLOCK_OVERRIDE.with(|clock| clock.replace(Some(ClockHandle::Sim(self.inner.clone()))));
ClockOverrideGuard { previous }
}
}
#[cfg(any(test, feature = "test-utils"))]
impl Default for SimClock {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(test, feature = "test-utils"))]
impl SimClockInner {
fn now(&self) -> Instant {
self.state.lock().expect("sim clock poisoned").now
}
fn nanos_since_birth(&self) -> u64 {
let state = self.state.lock().expect("sim clock poisoned");
state
.now
.duration_since(state.birth)
.as_nanos()
.saturating_add(1) as u64
}
fn sleep_until(self: std::sync::Arc<Self>, deadline: Instant) -> Sleep {
let maybe_rx = {
let mut state = self.state.lock().expect("sim clock poisoned");
if deadline <= state.now {
None
} else {
let (tx, rx) = tokio::sync::oneshot::channel();
state.waiters.push(Waiter { deadline, tx });
Some(rx)
}
};
match maybe_rx {
None => Box::pin(std::future::ready(())),
Some(rx) => Box::pin(async move {
let _ = rx.await;
}),
}
}
fn advance(&self, deadline: Instant) {
let due = {
let mut state = self.state.lock().expect("sim clock poisoned");
if deadline <= state.now {
return;
}
state.now = deadline;
let now = state.now;
let mut pending = Vec::new();
let mut due = Vec::new();
for waiter in state.waiters.drain(..) {
if waiter.deadline <= now {
due.push(waiter.tx);
} else {
pending.push(waiter);
}
}
state.waiters = pending;
due
};
for waiter in due {
let _ = waiter.send(());
}
}
}
#[cfg(any(test, feature = "test-utils"))]
pub struct ClockOverrideGuard {
previous: Option<ClockHandle>,
}
#[cfg(any(test, feature = "test-utils"))]
impl Drop for ClockOverrideGuard {
fn drop(&mut self) {
let previous = self.previous.take();
CLOCK_OVERRIDE.with(|clock| {
let _ = clock.replace(previous);
});
}
}