use std::ops::ControlFlow;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::time::{Duration, Instant};
use crate::locks::Relock;
struct Task {
due: Instant,
period: Option<Duration>,
run: Box<dyn FnMut() -> ControlFlow<()> + Send>,
}
struct Queue {
ticking: AtomicBool,
next_due: AtomicU64,
epoch: Instant,
tasks: Mutex<Vec<Task>>,
spare: Mutex<Vec<Task>>,
}
const IDLE: u64 = u64::MAX;
impl Queue {
fn new() -> Self {
Self {
ticking: AtomicBool::new(false),
next_due: AtomicU64::new(IDLE),
epoch: Instant::now(),
tasks: Mutex::new(Vec::new()),
spare: Mutex::new(Vec::new()),
}
}
fn nanos(&self, at: Instant) -> u64 {
u64::try_from(at.saturating_duration_since(self.epoch).as_nanos()).unwrap_or(IDLE - 1)
}
fn due_by(&self, due: Instant) {
self.next_due.fetch_min(self.nanos(due), Ordering::AcqRel);
}
}
static QUEUE: crate::fresh::Fresh<Queue> = crate::fresh::Fresh::new();
#[derive(Clone, Copy)]
pub struct Scheduler {
queue: &'static Queue,
}
impl core::fmt::Debug for Scheduler {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Scheduler")
.field(
"pending",
&(self.queue.next_due.load(Ordering::Relaxed) != IDLE),
)
.finish()
}
}
impl Scheduler {
pub(crate) fn get() -> (Self, bool) {
let queue = QUEUE.get_or_init(Queue::new);
let subscribe = !queue.ticking.swap(true, Ordering::AcqRel);
(Self { queue }, subscribe)
}
pub(crate) fn unsubscribed(self) {
self.queue.ticking.store(false, Ordering::Release);
}
pub fn next_tick(self, task: impl FnOnce() + Send + 'static) {
self.after(Duration::ZERO, task);
}
pub fn after(self, delay: Duration, task: impl FnOnce() + Send + 'static) {
let mut task = Some(task);
self.push(delay, None, move || {
if let Some(task) = task.take() {
task();
}
ControlFlow::Break(())
});
}
pub fn every(self, period: Duration, task: impl FnMut() -> ControlFlow<()> + Send + 'static) {
self.push(period, Some(period), task);
}
fn push(
self,
delay: Duration,
period: Option<Duration>,
run: impl FnMut() -> ControlFlow<()> + Send + 'static,
) {
let Some(due) = Instant::now().checked_add(delay) else {
return;
};
let task = Task {
due,
period,
run: Box::new(run),
};
let mut tasks = self.queue.tasks.relock();
self.queue.due_by(task.due);
tasks.push(task);
}
#[inline]
pub(crate) fn run_due(self) {
let next_due = self.queue.next_due.load(Ordering::Acquire);
if next_due == IDLE {
return;
}
let now = Instant::now();
if self.queue.nanos(now) >= next_due {
self.run_due_at(now);
}
}
#[cold]
fn run_due_at(self, now: Instant) {
let mut due = core::mem::take(&mut *self.queue.spare.relock());
{
let mut tasks = self.queue.tasks.relock();
due.extend(tasks.extract_if(.., |task| task.due <= now));
let next = tasks.iter().map(|task| task.due).min();
self.queue.next_due.store(
next.map_or(IDLE, |at| self.queue.nanos(at)),
Ordering::Release,
);
}
due.retain_mut(|task| match ((task.run)(), task.period) {
(ControlFlow::Continue(()), Some(period)) => match now.checked_add(period) {
Some(at) => {
task.due = at;
true
}
None => false,
},
_ => false,
});
{
let mut tasks = self.queue.tasks.relock();
for task in &due {
self.queue.due_by(task.due);
}
tasks.append(&mut due);
}
*self.queue.spare.relock() = due;
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::AtomicU32;
use super::*;
#[test]
fn tasks_run_when_due_and_repeat_until_they_stop() {
let (scheduler, _) = Scheduler::get();
let once = Arc::new(AtomicU32::new(0));
let repeated = Arc::new(AtomicU32::new(0));
let later = Arc::new(AtomicU32::new(0));
let count = Arc::clone(&once);
scheduler.next_tick(move || {
count.fetch_add(1, Ordering::Relaxed);
});
let count = Arc::clone(&repeated);
scheduler.every(Duration::ZERO, move || {
if count.fetch_add(1, Ordering::Relaxed) + 1 < 3 {
ControlFlow::Continue(())
} else {
ControlFlow::Break(())
}
});
let count = Arc::clone(&later);
scheduler.after(Duration::from_secs(3600), move || {
count.fetch_add(1, Ordering::Relaxed);
});
for _ in 0..5 {
scheduler.run_due();
}
assert_eq!(once.load(Ordering::Relaxed), 1);
assert_eq!(repeated.load(Ordering::Relaxed), 3);
assert_eq!(later.load(Ordering::Relaxed), 0);
assert_ne!(scheduler.queue.next_due.load(Ordering::Relaxed), IDLE);
}
#[test]
fn runs_keep_their_buffers_between_steps() {
let (scheduler, _) = Scheduler::get();
scheduler.every(Duration::ZERO, || ControlFlow::Continue(()));
scheduler.run_due();
let lock = |m: &'static Mutex<Vec<Task>>| m.relock();
let spare = lock(&scheduler.queue.spare).capacity();
let tasks = lock(&scheduler.queue.tasks).capacity();
assert!(spare >= 1);
scheduler.run_due();
assert_eq!(lock(&scheduler.queue.spare).capacity(), spare);
assert_eq!(lock(&scheduler.queue.tasks).capacity(), tasks);
assert_eq!(lock(&scheduler.queue.tasks).len(), 1);
}
#[test]
fn a_delay_past_what_time_holds_never_runs() {
let (scheduler, _) = Scheduler::get();
scheduler.after(Duration::MAX, || unreachable!());
scheduler.every(Duration::MAX, || unreachable!());
scheduler.run_due();
}
}