use std::{
collections::BTreeMap,
mem,
task::{Context, Poll, Waker},
time::{Duration, Instant},
};
use compio_log::{debug, instrument};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct TimerKey {
deadline: Instant,
generation: u64,
}
#[derive(Debug)]
pub(crate) struct TimerRuntime {
generation: u64,
wheel: BTreeMap<TimerKey, Option<Waker>>,
}
impl TimerRuntime {
pub fn new() -> Self {
Self {
generation: 0,
wheel: BTreeMap::default(),
}
}
pub fn is_completed(&self, key: &TimerKey) -> bool {
!self.wheel.contains_key(key)
}
pub fn insert(&mut self, deadline: Instant) -> Option<TimerKey> {
if deadline <= Instant::now() {
return None;
}
let key = TimerKey {
deadline,
generation: self.generation,
};
self.wheel.insert(key, None);
self.generation = self
.generation
.checked_add(1)
.expect("too many timers created");
Some(key)
}
pub fn update_waker(&mut self, key: &TimerKey, waker: &Waker) {
let Some(w) = self.wheel.get_mut(key) else {
return;
};
if let Some(w) = w
&& waker.will_wake(w)
{
return;
}
*w = Some(waker.clone())
}
pub fn cancel(&mut self, key: &TimerKey) {
self.wheel.remove(key);
}
pub fn min_timeout(&self) -> Option<Duration> {
self.wheel.first_key_value().map(|(key, _)| {
let now = Instant::now();
key.deadline.saturating_duration_since(now)
})
}
pub fn wake(&mut self) {
if self.wheel.is_empty() {
return;
}
let now = Instant::now();
let pending = self.wheel.split_off(&TimerKey {
deadline: now,
generation: u64::MAX,
});
let expired = mem::replace(&mut self.wheel, pending);
for (_, w) in expired {
if let Some(w) = w {
w.wake();
}
}
}
pub fn poll_timer(&mut self, cx: &mut Context<'_>, key: &TimerKey) -> Poll<()> {
instrument!(compio_log::Level::DEBUG, "poll_timer", ?cx, ?key);
if self.is_completed(key) {
debug!("ready");
Poll::Ready(())
} else {
debug!("pending");
self.update_waker(key, cx.waker());
Poll::Pending
}
}
}