use std::{
pin::Pin,
sync::{
atomic::{AtomicIsize, Ordering},
Arc,
},
task::{Context, Poll},
time::{Duration, Instant},
};
use crossbeam::channel;
use driver::{NodeDesc, WakerNode};
#[cfg(target_os = "linux")]
const DEFAULT_SCHEDULE_RESOLUTION: Duration = Duration::from_millis(4);
#[cfg(target_os = "macos")]
const DEFAULT_SCHEDULE_RESOLUTION: Duration = Duration::from_millis(10);
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
const DEFAULT_SCHEDULE_RESOLUTION: Duration = Duration::from_millis(33);
#[derive(Debug, derive_setters::Setters)]
#[setters(prefix = "with_")]
#[non_exhaustive]
pub struct Builder {
pub schedule_resolution: Duration,
pub gc_threshold: usize,
#[setters(into)]
pub channel_capacity: Option<usize>,
pub yields_per_spin: usize,
#[setters(skip)]
gc_counter: Arc<AtomicIsize>,
}
impl Default for Builder {
fn default() -> Self {
Self {
schedule_resolution: DEFAULT_SCHEDULE_RESOLUTION,
gc_threshold: 1000,
channel_capacity: None,
gc_counter: Default::default(),
yields_per_spin: 1,
}
}
}
impl Builder {
#[must_use = "Never drop the driver instance!"]
pub fn build(self) -> (Handle, impl FnOnce()) {
self.build_d_ary::<4>()
}
#[must_use = "Never drop the driver instance!"]
pub fn build_d_ary<const D: usize>(self) -> (Handle, impl FnOnce()) {
let _ = instant::origin();
let (tx, rx) = if let Some(cap) = self.channel_capacity {
channel::bounded(cap)
} else {
channel::unbounded()
};
let handle = Handle { tx: tx.clone(), gc_counter: self.gc_counter.clone() };
let driver = move || driver::execute::<D>(self, rx);
(handle, driver)
}
}
pub fn create() -> (Handle, impl FnOnce()) {
Builder::default().build()
}
pub fn create_d_ary<const D: usize>() -> (Handle, impl FnOnce()) {
Builder::default().build_d_ary::<D>()
}
mod driver {
use std::{
sync::{atomic::Ordering, Weak},
task::Waker,
time::{Duration, Instant},
};
use crossbeam::channel::{self, TryRecvError};
use dary_heap::DaryHeap;
use educe::Educe;
use crate::Builder;
#[derive(Debug)]
pub(crate) enum Event {
SleepUntil(NodeDesc),
}
pub(crate) fn execute<const D: usize>(this: Builder, rx: channel::Receiver<Event>) {
let mut nodes = DaryHeap::<Node, D>::new();
let pivot = Instant::now();
let to_usec = |x: Instant| x.duration_since(pivot).as_micros() as u64;
let resolution_usec = this.schedule_resolution.as_micros() as u64;
let gc_counter = this.gc_counter;
let yields_per_spin = this.yields_per_spin.max(1);
'worker: loop {
let now_ts = Instant::now();
let now = to_usec(now_ts);
let mut event = if let Some(node) = nodes.peek() {
let remain = node.timeout_usec.saturating_sub(now);
if remain > resolution_usec {
let system_sleep_for = remain - resolution_usec;
let timeout = Duration::from_micros(system_sleep_for);
let deadline = now_ts + timeout;
let Ok(x) = rx.recv_deadline(deadline).map_err(|e| match e {
channel::RecvTimeoutError::Timeout => (),
channel::RecvTimeoutError::Disconnected => {
std::thread::sleep(deadline.saturating_duration_since(Instant::now()))
}
}) else {
continue;
};
x
} else {
let mut yields_counter = 0usize;
'busy_wait: loop {
let now = to_usec(Instant::now());
if now >= node.timeout_usec {
let node = nodes.pop().unwrap();
if let Some(waker) = node.weak_waker.upgrade() {
waker.value.lock().take().expect("logic error").wake();
}
let n_garbage = gc_counter.fetch_sub(1, Ordering::Release);
if n_garbage > this.gc_threshold as isize {
let n_collect = gc(&mut nodes) as _;
gc_counter.fetch_sub(n_collect, Ordering::Release);
}
continue 'worker;
} else {
if yields_counter % yields_per_spin == 0 {
match rx.try_recv() {
Ok(x) => break 'busy_wait x,
Err(TryRecvError::Disconnected) if nodes.is_empty() => {
break 'worker
}
Err(TryRecvError::Disconnected) | Err(TryRecvError::Empty) => {
}
}
}
yields_counter += 1;
std::thread::yield_now();
continue 'busy_wait;
}
}
}
} else {
let Ok(x) = rx.recv() else { break };
x
};
if gc_counter.load(Ordering::Acquire) as usize > this.gc_threshold {
let n_collect = gc(&mut nodes) as _;
gc_counter.fetch_sub(n_collect, Ordering::Release);
}
'flush: loop {
match event {
Event::SleepUntil(desc) => nodes
.push(Node { timeout_usec: to_usec(desc.timeout), weak_waker: desc.waker }),
};
event = match rx.try_recv() {
Ok(x) => x,
Err(TryRecvError::Disconnected) if nodes.is_empty() => break 'worker,
Err(TryRecvError::Disconnected) | Err(TryRecvError::Empty) => break 'flush,
};
}
}
assert!(nodes.is_empty());
assert_eq!(gc_counter.load(Ordering::Relaxed), 0);
}
fn gc<const D: usize>(nodes: &mut DaryHeap<Node, D>) -> usize {
let fn_retain = |x: &Node| x.weak_waker.upgrade().is_some();
let prev_len = nodes.len();
*nodes = {
let mut vec = std::mem::take(nodes).into_vec();
vec.retain(fn_retain);
DaryHeap::from(vec)
};
prev_len - nodes.len()
}
#[derive(Debug, Clone)]
pub(crate) struct NodeDesc {
pub timeout: Instant,
pub waker: Weak<WakerNode>,
}
#[derive(Debug, Clone, Educe)]
#[educe(Eq, PartialEq, PartialOrd, Ord)]
pub(crate) struct Node {
#[educe(PartialOrd(method = "cmp_rev_partial"), Ord(method = "cmp_rev"))]
pub timeout_usec: u64,
#[educe(Eq(ignore), PartialEq(ignore), PartialOrd(ignore), Ord(ignore))]
pub weak_waker: Weak<WakerNode>,
}
fn cmp_rev(a: &u64, b: &u64) -> std::cmp::Ordering {
b.cmp(a)
}
fn cmp_rev_partial(a: &u64, b: &u64) -> Option<std::cmp::Ordering> {
b.partial_cmp(a)
}
#[derive(Debug)]
pub(crate) struct WakerNode {
value: parking_lot::Mutex<Option<Waker>>,
}
impl WakerNode {
pub fn new(waker: Waker) -> Self {
Self { value: parking_lot::Mutex::new(Some(waker)) }
}
pub fn is_expired(&self) -> bool {
self.value.lock().is_none()
}
}
}
#[derive(Debug, Clone)]
pub struct Handle {
tx: channel::Sender<driver::Event>,
gc_counter: Arc<AtomicIsize>,
}
impl Handle {
pub fn sleep_for(&self, duration: Duration) -> SleepFuture {
self.sleep_until(Instant::now() + duration)
}
pub fn sleep_until(&self, timeout: Instant) -> SleepFuture {
SleepFuture {
state: SleepState::Pending(self.tx.clone()),
timeout,
gc_counter: self.gc_counter.clone(),
}
}
pub fn interval(&self, interval: Duration) -> util::Interval {
util::Interval { handle: self.clone(), wakeup_time: Instant::now() + interval, interval }
}
}
pub mod util {
use crate::{instant, Report};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct Interval {
pub(crate) handle: super::Handle,
pub(crate) wakeup_time: Instant,
pub(crate) interval: Duration,
}
impl Interval {
pub async fn tick_with_min_interval(&mut self, minimum_interval: Duration) -> Report {
assert!(minimum_interval <= self.interval);
let Self { handle, wakeup_time: wakeup, interval } = self;
let result = handle.sleep_until(*wakeup).await;
let now = Instant::now();
*wakeup += *interval;
let minimum_next = now + minimum_interval;
if minimum_next > *wakeup {
let interval_ns = interval.as_nanos();
let num_ticks = ((minimum_next - *wakeup).as_nanos() - 1) / interval_ns + 1;
*wakeup += Duration::from_nanos((interval_ns * num_ticks) as _);
}
result
}
pub async fn tick(&mut self) -> Report {
self.tick_with_min_interval(self.interval / 2).await
}
pub fn set_interval(&mut self, interval: Duration) {
assert!(interval > Duration::default());
self.wakeup_time -= self.interval;
self.wakeup_time += interval;
self.interval = interval;
}
pub fn interval(&self) -> Duration {
self.interval
}
pub fn wakeup_time(&self) -> Instant {
self.wakeup_time
}
pub fn align_with_clock(
&mut self,
now_since_epoch: impl FnOnce() -> Duration,
interval: Option<Duration>, initial_interval_tolerance: Option<Duration>, align_offset_ns: i64,
) {
let prev_trig = self.wakeup_time - self.interval;
let dst_now_ns = now_since_epoch().as_nanos() as i64;
let inst_now = Instant::now();
let interval = interval.unwrap_or(self.interval);
let interval_ns = interval.as_nanos() as i64;
let interval_tolerance =
initial_interval_tolerance.unwrap_or(Duration::from_nanos((interval_ns / 10) as _));
assert!(interval > Duration::default(), "interval must be larger than zero");
assert!(interval_tolerance < interval);
let ticks_to_align = {
let mut val = interval_ns - (dst_now_ns % interval_ns) + align_offset_ns;
if val < 0 {
val += (val / interval_ns + 1) * interval_ns;
}
Duration::from_nanos((val % interval_ns) as _)
};
let mut desired_wake_up = inst_now + ticks_to_align;
if desired_wake_up < prev_trig + interval - interval_tolerance {
desired_wake_up += interval;
debug_assert!(desired_wake_up >= prev_trig + interval - interval_tolerance);
}
self.wakeup_time = desired_wake_up;
self.interval = interval;
}
pub fn align_now(
&mut self,
interval: Option<Duration>,
initial_interval_tolerance: Option<Duration>,
align_offset_ns: i64,
) {
self.align_with_clock(
instant::time_from_epoch,
interval,
initial_interval_tolerance,
align_offset_ns,
);
}
#[cfg(feature = "system-clock")]
pub fn align_with_system_clock(
&mut self,
interval: Option<Duration>,
initial_interval_tolerance: Option<Duration>,
align_offset_ns: i64,
) {
self.align_with_clock(
|| {
let now = std::time::SystemTime::now();
now.duration_since(std::time::UNIX_EPOCH).unwrap()
},
interval,
initial_interval_tolerance,
align_offset_ns,
);
}
}
}
mod instant {
use std::time::Instant;
pub(crate) fn origin() -> Instant {
lazy_static::lazy_static!(
static ref PIVOT: Instant = Instant::now();
);
*PIVOT
}
pub(crate) fn time_from_epoch() -> std::time::Duration {
origin().elapsed()
}
}
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SleepFuture {
gc_counter: Arc<AtomicIsize>,
timeout: Instant,
state: SleepState,
}
#[cfg(test)]
static_assertions::assert_impl_all!(SleepFuture: Send, Sync, Unpin);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Report {
Completed(Duration),
ExpiredTimer(Duration),
CompletedEarly(Duration),
}
impl Report {
pub fn overslept(&self) -> Duration {
match self {
Self::Completed(dur) => *dur,
Self::ExpiredTimer(dur) => *dur,
Self::CompletedEarly(_) => Duration::ZERO,
}
}
pub fn is_woke_up_early(&self) -> bool {
matches!(self, Self::CompletedEarly(_))
}
#[doc(hidden)]
pub fn unwrap(self) -> Self {
self
}
#[doc(hidden)]
pub fn ok(self) -> Option<Self> {
Some(self)
}
}
#[derive(Debug)]
enum SleepState {
Pending(channel::Sender<driver::Event>),
Sleeping(Arc<WakerNode>),
Woken,
}
impl std::future::Future for SleepFuture {
type Output = Report;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let now = Instant::now();
if let Some(over) = now.checked_duration_since(self.timeout) {
let result = if matches!(self.state, SleepState::Sleeping(_)) {
self.state = SleepState::Woken;
Report::Completed(over)
} else {
Report::ExpiredTimer(over)
};
return Poll::Ready(result);
}
if let SleepState::Pending(tx) = &self.state {
let waker = Arc::new(WakerNode::new(cx.waker().clone()));
let event = driver::Event::SleepUntil(NodeDesc {
timeout: self.timeout,
waker: Arc::downgrade(&waker),
});
tx.send(event).expect("timer driver instance dropped!");
self.state = SleepState::Sleeping(waker);
} else if let SleepState::Sleeping(node) = &self.state {
if node.is_expired() {
self.state = SleepState::Woken;
return Poll::Ready(Report::CompletedEarly(self.timeout - now));
} else {
}
}
Poll::Pending
}
}
impl Drop for SleepFuture {
fn drop(&mut self) {
if !matches!(&self.state, SleepState::Pending { .. }) {
self.gc_counter.fetch_add(1, Ordering::Release);
}
}
}