use alloc::rc::Rc;
use core::cell::{Cell, RefCell};
use core::fmt;
use core::future::{Future, poll_fn};
use core::pin::Pin;
use core::task::Waker;
use core::task::{Context, Poll};
use core::time::Duration;
use std::time::Instant;
use crate::platform::current::runtime as imp;
pub struct Sleep {
delay: Option<Duration>,
state: Option<Rc<SleepState>>,
handle: Option<crate::TimeoutHandle>,
completed: bool,
}
pub struct Interval {
period: Duration,
next_tick: Instant,
first_tick: bool,
sleep: Option<Sleep>,
missed_tick_behavior: MissedTickBehavior,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum MissedTickBehavior {
Burst,
Delay,
Skip,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Elapsed;
pub fn sleep(duration: Duration) -> Sleep {
Sleep {
delay: Some(duration),
state: None,
handle: None,
completed: false,
}
}
pub fn interval(period: Duration) -> Interval {
Interval {
period,
next_tick: Instant::now(),
first_tick: true,
sleep: None,
missed_tick_behavior: MissedTickBehavior::Burst,
}
}
pub fn set_timeout<F>(delay: Duration, callback: F) -> crate::TimeoutHandle
where
F: FnOnce() + 'static,
{
imp::timeout(delay, callback)
}
pub fn set_interval<F>(delay: Duration, callback: F) -> crate::IntervalHandle
where
F: FnMut() + 'static,
{
imp::interval(delay, callback)
}
pub async fn timeout<F>(duration: Duration, future: F) -> Result<F::Output, Elapsed>
where
F: Future,
{
let mut future = std::pin::pin!(future);
let mut sleeper = std::pin::pin!(sleep(duration));
poll_fn(|cx| {
if let Poll::Ready(output) = future.as_mut().poll(cx) {
return Poll::Ready(Ok(output));
}
if let Poll::Ready(()) = sleeper.as_mut().poll(cx) {
return Poll::Ready(Err(Elapsed));
}
Poll::Pending
})
.await
}
impl Interval {
pub fn tick(&mut self) -> impl Future<Output = Instant> + '_ {
Tick { interval: self }
}
pub fn period(&self) -> Duration {
self.period
}
pub fn set_missed_tick_behavior(&mut self, behavior: MissedTickBehavior) {
self.missed_tick_behavior = behavior;
}
pub fn missed_tick_behavior(&self) -> MissedTickBehavior {
self.missed_tick_behavior
}
}
struct Tick<'a> {
interval: &'a mut Interval,
}
impl Future for Tick<'_> {
type Output = Instant;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let interval = &mut *self.interval;
if interval.first_tick {
interval.first_tick = false;
let tick = interval.next_tick;
interval.next_tick = add_instant(tick, interval.period);
return Poll::Ready(tick);
}
if interval.period.is_zero() {
let sleep = interval.sleep.get_or_insert_with(|| sleep(Duration::ZERO));
match Pin::new(sleep).poll(cx) {
Poll::Ready(()) => {
interval.sleep = None;
return Poll::Ready(Instant::now());
}
Poll::Pending => return Poll::Pending,
}
}
let scheduled = interval.next_tick;
let now = Instant::now();
if now < scheduled {
let delay = scheduled.duration_since(now);
let sleep = interval.sleep.get_or_insert_with(|| sleep(delay));
match Pin::new(sleep).poll(cx) {
Poll::Ready(()) => interval.sleep = None,
Poll::Pending => return Poll::Pending,
}
} else {
interval.sleep = None;
}
let observed = Instant::now();
interval.next_tick = next_deadline(
scheduled,
observed,
interval.period,
interval.missed_tick_behavior,
);
Poll::Ready(scheduled)
}
}
fn next_deadline(
scheduled: Instant,
observed: Instant,
period: Duration,
behavior: MissedTickBehavior,
) -> Instant {
let next = add_instant(scheduled, period);
let missed = observed >= next;
match behavior {
MissedTickBehavior::Burst => next,
MissedTickBehavior::Delay if missed => add_instant(observed, period),
MissedTickBehavior::Delay => next,
MissedTickBehavior::Skip if missed => first_deadline_after(scheduled, observed, period),
MissedTickBehavior::Skip => next,
}
}
fn first_deadline_after(scheduled: Instant, observed: Instant, period: Duration) -> Instant {
debug_assert!(!period.is_zero());
let elapsed = observed.saturating_duration_since(scheduled);
let periods = (elapsed.as_nanos() / period.as_nanos()) + 1;
let mut remaining = periods;
let mut next = scheduled;
while remaining > 0 {
let chunk = remaining.min(u128::from(u32::MAX)) as u32;
let Some(delta) = period.checked_mul(chunk) else {
return add_instant(observed, period);
};
let Some(deadline) = next.checked_add(delta) else {
return add_instant(observed, period);
};
next = deadline;
remaining -= u128::from(chunk);
}
next
}
fn add_instant(instant: Instant, duration: Duration) -> Instant {
instant.checked_add(duration).unwrap_or(instant)
}
impl Future for Sleep {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.completed {
return Poll::Ready(());
}
if self.state.is_none() {
let delay = self.delay.take().unwrap_or(Duration::ZERO);
let state = Rc::new(SleepState::default());
let state_for_callback = Rc::clone(&state);
let timeout_handle = set_timeout(delay, move || state_for_callback.complete());
self.state = Some(state);
self.handle = Some(timeout_handle);
}
let state = self
.state
.as_ref()
.expect("sleep state should be initialized");
if state.ready.get() {
self.completed = true;
self.state = None;
self.handle = None;
Poll::Ready(())
} else {
*state.waker.borrow_mut() = Some(cx.waker().clone());
if state.ready.get() {
self.completed = true;
self.state = None;
self.handle = None;
Poll::Ready(())
} else {
Poll::Pending
}
}
}
}
impl Drop for Sleep {
fn drop(&mut self) {
if self.completed {
return;
}
if let Some(handle) = self.handle.take() {
handle.cancel();
}
}
}
#[derive(Default)]
struct SleepState {
ready: Cell<bool>,
waker: RefCell<Option<Waker>>,
}
impl SleepState {
fn complete(&self) {
self.ready.set(true);
if let Some(waker) = self.waker.borrow_mut().take() {
waker.wake();
}
}
}
impl fmt::Display for Elapsed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("timeout elapsed")
}
}
impl std::error::Error for Elapsed {}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::{queue_macrotask, run, spawn};
use super::{MissedTickBehavior, interval, next_deadline, sleep, timeout};
#[test]
fn sleep_and_timeout_work() {
let log = std::thread::spawn(|| {
let log = Arc::new(Mutex::new(Vec::new()));
let log_for_task = Arc::clone(&log);
queue_macrotask(move || {
let log_for_task = Arc::clone(&log_for_task);
spawn(async move {
log_for_task.lock().unwrap().push("started");
sleep(Duration::from_millis(5)).await;
log_for_task.lock().unwrap().push("slept");
let result = timeout(Duration::from_millis(5), async {
sleep(Duration::from_millis(20)).await;
42usize
})
.await;
assert!(result.is_err(), "timeout should fire first");
log_for_task.lock().unwrap().push("timed out");
});
});
run();
let log = log.lock().unwrap();
log.clone()
})
.join()
.expect("time test thread should join successfully");
assert_eq!(log.as_slice(), ["started", "slept", "timed out"]);
}
#[test]
fn sleep_zero_yields_to_macrotask_queue() {
let order = std::thread::spawn(|| {
let order = Rc::new(RefCell::new(Vec::<&'static str>::new()));
{
let order = Rc::clone(&order);
queue_macrotask(move || order.borrow_mut().push("macrotask"));
}
{
let order = Rc::clone(&order);
spawn(async move {
sleep(Duration::ZERO).await;
order.borrow_mut().push("after_sleep");
});
}
run();
Rc::try_unwrap(order).unwrap().into_inner()
})
.join()
.expect("test thread should join");
assert_eq!(order.as_slice(), ["macrotask", "after_sleep"]);
}
#[test]
fn interval_first_tick_is_immediate() {
let elapsed = std::thread::spawn(|| {
let elapsed = Arc::new(Mutex::new(None::<Duration>));
let elapsed_for_task = Arc::clone(&elapsed);
spawn(async move {
let start = Instant::now();
let mut interval = interval(Duration::from_millis(50));
assert_eq!(interval.period(), Duration::from_millis(50));
assert_eq!(interval.missed_tick_behavior(), MissedTickBehavior::Burst);
let tick = interval.tick().await;
assert!(tick <= Instant::now());
*elapsed_for_task.lock().unwrap() = Some(start.elapsed());
});
run();
elapsed.lock().unwrap().expect("task should record elapsed")
})
.join()
.expect("interval test thread should join successfully");
assert!(
elapsed < Duration::from_millis(20),
"first tick should complete immediately, elapsed {elapsed:?}"
);
}
#[test]
fn zero_period_interval_yields_between_ticks() {
let order = std::thread::spawn(|| {
let order = Rc::new(RefCell::new(Vec::<&'static str>::new()));
let order_for_task = Rc::clone(&order);
spawn(async move {
let mut interval = interval(Duration::ZERO);
interval.tick().await;
order_for_task.borrow_mut().push("first");
let order_for_macrotask = Rc::clone(&order_for_task);
queue_macrotask(move || order_for_macrotask.borrow_mut().push("macrotask"));
interval.tick().await;
order_for_task.borrow_mut().push("second");
});
run();
Rc::try_unwrap(order).unwrap().into_inner()
})
.join()
.expect("interval test thread should join successfully");
assert_eq!(order.as_slice(), ["first", "macrotask", "second"]);
}
#[test]
fn interval_ticks_steadily_at_period() {
let (ticks, elapsed) = std::thread::spawn(|| {
let output = Arc::new(Mutex::new(None::<(Vec<Instant>, Duration)>));
let output_for_task = Arc::clone(&output);
spawn(async move {
let start = Instant::now();
let mut interval = interval(Duration::from_millis(25));
let first = interval.tick().await;
let second = interval.tick().await;
let third = interval.tick().await;
*output_for_task.lock().unwrap() =
Some((vec![first, second, third], start.elapsed()));
});
run();
output
.lock()
.unwrap()
.take()
.expect("task should record ticks")
})
.join()
.expect("interval test thread should join successfully");
assert_eq!(ticks[1].duration_since(ticks[0]), Duration::from_millis(25));
assert_eq!(ticks[2].duration_since(ticks[1]), Duration::from_millis(25));
assert!(
elapsed >= Duration::from_millis(45),
"two waited ticks should take about two periods, elapsed {elapsed:?}"
);
assert!(
elapsed < Duration::from_millis(200),
"steady interval test should not stall, elapsed {elapsed:?}"
);
}
#[test]
fn interval_missed_tick_behavior_bursts_to_catch_up() {
let (ticks, after_delay) = delayed_interval_ticks(MissedTickBehavior::Burst, 4);
assert_eq!(ticks[1].duration_since(ticks[0]), Duration::from_millis(20));
assert_eq!(ticks[2].duration_since(ticks[1]), Duration::from_millis(20));
assert_eq!(ticks[3].duration_since(ticks[2]), Duration::from_millis(20));
assert!(
ticks[3] <= after_delay,
"burst catch-up ticks should be overdue (fired immediately)"
);
}
#[test]
fn interval_missed_tick_behavior_delays_after_late_tick() {
let (ticks, after_delay) = delayed_interval_ticks(MissedTickBehavior::Delay, 3);
assert_eq!(ticks[1].duration_since(ticks[0]), Duration::from_millis(20));
assert!(
ticks[2].duration_since(ticks[1]) >= Duration::from_millis(65),
"delay should drift past the missed span"
);
assert!(
ticks[2] > after_delay,
"delayed tick must wait into the future, not fire immediately"
);
}
#[test]
fn interval_missed_tick_behavior_skips_missed_grid_ticks() {
let (ticks, after_delay) = delayed_interval_ticks(MissedTickBehavior::Skip, 3);
assert_eq!(ticks[1].duration_since(ticks[0]), Duration::from_millis(20));
let skip_gap = ticks[2].duration_since(ticks[0]);
assert_eq!(
skip_gap.as_nanos() % Duration::from_millis(20).as_nanos(),
0,
"skip deadline must remain on the original grid, gap {skip_gap:?}"
);
assert!(
ticks[2].duration_since(ticks[1]) >= Duration::from_millis(20),
"skip should advance at least one period"
);
assert!(
ticks[2] > after_delay,
"skip should wait for the next grid tick, not fire immediately"
);
}
#[test]
fn next_deadline_matches_missed_tick_contract() {
let period = Duration::from_millis(20);
let base = Instant::now();
let scheduled = base + Duration::from_millis(20);
let on_time = base + Duration::from_millis(25);
let next_grid = base + Duration::from_millis(40);
for behavior in [
MissedTickBehavior::Burst,
MissedTickBehavior::Delay,
MissedTickBehavior::Skip,
] {
assert_eq!(
next_deadline(scheduled, on_time, period, behavior),
next_grid,
"on-time {behavior:?} should advance one grid step"
);
}
let late = base + Duration::from_millis(65);
assert_eq!(
next_deadline(scheduled, late, period, MissedTickBehavior::Burst),
base + Duration::from_millis(40),
"burst replays the next grid deadline regardless of lateness"
);
assert_eq!(
next_deadline(scheduled, late, period, MissedTickBehavior::Delay),
base + Duration::from_millis(85),
"delay drifts to one period past the observation"
);
assert_eq!(
next_deadline(scheduled, late, period, MissedTickBehavior::Skip),
base + Duration::from_millis(80),
"skip jumps to the first grid instant after the observation"
);
}
fn delayed_interval_ticks(
behavior: MissedTickBehavior,
tick_count: usize,
) -> (Vec<Instant>, Instant) {
std::thread::spawn(move || {
let output = Arc::new(Mutex::new(None::<(Vec<Instant>, Instant)>));
let output_for_task = Arc::clone(&output);
spawn(async move {
let mut interval = interval(Duration::from_millis(20));
interval.set_missed_tick_behavior(behavior);
let mut ticks = Vec::with_capacity(tick_count);
ticks.push(interval.tick().await);
std::thread::sleep(Duration::from_millis(65));
let after_delay = Instant::now();
while ticks.len() < tick_count {
ticks.push(interval.tick().await);
}
*output_for_task.lock().unwrap() = Some((ticks, after_delay));
});
run();
output
.lock()
.unwrap()
.take()
.expect("task should record ticks")
})
.join()
.expect("interval test thread should join successfully")
}
}