pub use std::time::Instant;
use std::{
sync::{Condvar, Mutex, OnceLock},
thread,
time::Duration,
};
use rayon::prelude::*;
const SPIN_THRESHOLD: Duration = Duration::from_millis(2);
struct TimerEntry {
next_fire: Instant,
period: Duration,
count: u64,
tick: Box<dyn FnMut(u64) -> bool + Send>,
}
struct Reactor {
entries: Mutex<Vec<TimerEntry>>,
wake: Condvar,
}
fn reactor() -> &'static Reactor {
static REACTOR: OnceLock<&'static Reactor> = OnceLock::new();
REACTOR.get_or_init(|| {
let reactor: &'static Reactor = Box::leak(Box::new(Reactor {
entries: Mutex::new(Vec::new()),
wake: Condvar::new(),
}));
thread::Builder::new()
.name("hyphae-timer-reactor".into())
.spawn(move || run_reactor(reactor))
.expect("failed to spawn hyphae timer reactor thread");
reactor
})
}
fn register(entry: TimerEntry) {
let reactor = reactor();
reactor.entries.lock().unwrap().push(entry);
reactor.wake.notify_all();
}
fn run_reactor(reactor: &'static Reactor) -> ! {
loop {
let due = wait_for_due(reactor);
for mut entry in due {
let keep = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(entry.tick)(entry.count)
}))
.unwrap_or(false);
if !keep {
continue;
}
entry.count += 1;
entry.next_fire += entry.period;
let now = Instant::now();
if entry.next_fire < now {
let missed =
((now - entry.next_fire).as_nanos() / entry.period.as_nanos().max(1)) as u64;
entry.count += missed;
entry.next_fire = now + entry.period;
}
reactor.entries.lock().unwrap().push(entry);
}
}
}
fn wait_for_due(reactor: &'static Reactor) -> Vec<TimerEntry> {
let mut guard = reactor.entries.lock().unwrap();
loop {
if guard.is_empty() {
guard = reactor.wake.wait(guard).unwrap();
continue;
}
let now = Instant::now();
let next_deadline = guard.iter().map(|e| e.next_fire).min().expect("non-empty");
if next_deadline <= now {
let now = Instant::now();
let mut due = Vec::new();
let mut idx = 0;
while idx < guard.len() {
if guard[idx].next_fire <= now {
due.push(guard.swap_remove(idx));
} else {
idx += 1;
}
}
return due;
}
let wait_for = next_deadline - now;
if wait_for > SPIN_THRESHOLD {
let (g, _timed_out) = reactor
.wake
.wait_timeout(guard, wait_for - SPIN_THRESHOLD)
.unwrap();
guard = g;
continue;
}
drop(guard);
spin_sleep::sleep(wait_for);
guard = reactor.entries.lock().unwrap();
}
}
pub fn spawn_delayed(delay: Duration, f: impl FnOnce() + Send + 'static) {
let mut f = Some(f);
register(TimerEntry {
next_fire: Instant::now() + delay,
period: delay,
count: 1,
tick: Box::new(move |_count| {
if let Some(f) = f.take() {
f();
}
false
}),
});
}
pub fn spawn_interval(
period: Duration,
_precise: bool,
tick: impl FnMut(u64) -> bool + Send + 'static,
) {
register(TimerEntry {
next_fire: Instant::now() + period,
period,
count: 1,
tick: Box::new(tick),
});
}
pub fn par_for_each<T: Sync>(items: &[T], f: impl Fn(&T) + Send + Sync) {
items.par_iter().for_each(f);
}