pub(crate) mod bucket;
pub(crate) mod delay_queue;
pub(crate) mod entry;
pub(crate) mod state;
use std::sync::Arc;
use crate::timing_wheel::bucket::Bucket;
use crate::timing_wheel::delay_queue::{BucketOffer, DelayQueue};
use crate::timing_wheel::entry::Entry;
#[derive(Debug)]
pub(crate) struct TimingWheel {
tick_ms: u64,
bucket_count: usize,
interval_ms: u64,
current_time_ms: u64,
buckets: Vec<Bucket>,
delay_queue: DelayQueue,
overflow: Option<Box<TimingWheel>>,
}
impl TimingWheel {
pub(crate) fn new(tick_ms: u64, bucket_count: usize, start_ms: u64) -> Self {
let current_time_ms = start_ms - (start_ms % tick_ms);
let interval_ms = tick_ms.saturating_mul(bucket_count as u64);
let buckets = (0..bucket_count).map(|_| Bucket::default()).collect();
Self {
tick_ms,
bucket_count,
interval_ms,
current_time_ms,
buckets,
delay_queue: DelayQueue::default(),
overflow: None,
}
}
pub(crate) fn add(&mut self, entry: Arc<Entry>) -> bool {
self.add_with_deadline(entry, None)
}
fn add_with_deadline(&mut self, entry: Arc<Entry>, deadline_override: Option<u64>) -> bool {
let deadline_ms = deadline_override.unwrap_or_else(|| entry.deadline_ms());
if deadline_ms < self.current_time_ms.saturating_add(self.tick_ms) {
return false;
}
let level_end_ms = self.current_time_ms.saturating_add(self.interval_ms);
if deadline_ms < level_end_ms || level_end_ms == u64::MAX {
let virtual_id = deadline_ms / self.tick_ms;
let bucket_index = (virtual_id as usize) % self.bucket_count;
let expiration_ms = virtual_id * self.tick_ms;
let bucket = &mut self.buckets[bucket_index];
bucket.add(entry);
if bucket.set_expiration_ms(expiration_ms) {
self.delay_queue.offer(BucketOffer {
expiration_ms,
bucket_index,
});
}
return true;
}
let overflow = self.overflow.get_or_insert_with(|| {
Box::new(Self::new(
self.interval_ms,
self.bucket_count,
self.current_time_ms,
))
});
overflow.add_with_deadline(entry, Some(deadline_ms))
}
pub(crate) fn advance_clock(&mut self, time_ms: u64) {
if time_ms >= self.current_time_ms.saturating_add(self.tick_ms) {
self.current_time_ms = time_ms - (time_ms % self.tick_ms);
if let Some(overflow) = self.overflow.as_mut() {
overflow.advance_clock(self.current_time_ms);
}
}
}
pub(crate) fn next_expiration_ms(&mut self) -> Option<u64> {
while let Some(offer) = self.delay_queue.peek() {
if self.buckets[offer.bucket_index].expiration_ms() == Some(offer.expiration_ms) {
return Some(offer.expiration_ms);
}
self.delay_queue.poll();
}
None
}
pub(crate) fn poll_expired(&mut self, now_ms: u64) -> Vec<Arc<Entry>> {
let mut expired = Vec::new();
self.advance_clock(now_ms);
if let Some(overflow) = self.overflow.as_mut() {
for entry in overflow.poll_expired(now_ms) {
if self.add(Arc::clone(&entry)) {
continue;
}
expired.push(entry);
}
}
while let Some(offer) = self.delay_queue.peek() {
if offer.expiration_ms > now_ms {
break;
}
self.delay_queue.poll();
if self.buckets[offer.bucket_index].expiration_ms() != Some(offer.expiration_ms) {
continue;
}
self.advance_clock(offer.expiration_ms);
let entries = self.buckets[offer.bucket_index].drain();
for entry in entries {
if self.add(Arc::clone(&entry)) {
continue;
}
expired.push(entry);
}
}
expired
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::timing_wheel::entry::Entry;
use crate::timing_wheel::state::TimeoutState;
use super::TimingWheel;
#[test]
fn immediate_deadline_is_due_without_bucket() {
let mut wheel = TimingWheel::new(10, 8, 0);
let entry = Arc::new(Entry::new(1, 5, Arc::new(TimeoutState::scheduled())));
assert!(!wheel.add(entry));
}
#[test]
fn in_range_deadline_expires_from_bucket() {
let mut wheel = TimingWheel::new(10, 8, 0);
let entry = Arc::new(Entry::new(1, 20, Arc::new(TimeoutState::scheduled())));
assert!(wheel.add(Arc::clone(&entry)));
assert_eq!(wheel.next_expiration_ms(), Some(20));
assert!(wheel.poll_expired(19).is_empty());
assert_eq!(wheel.poll_expired(20)[0].id(), entry.id());
}
#[test]
fn overflow_deadline_cascades_into_lower_level() {
let mut wheel = TimingWheel::new(10, 8, 0);
let entry = Arc::new(Entry::new(1, 120, Arc::new(TimeoutState::scheduled())));
assert!(wheel.add(Arc::clone(&entry)));
assert!(wheel.poll_expired(79).is_empty());
assert!(wheel.poll_expired(80).is_empty());
assert_eq!(wheel.poll_expired(120)[0].id(), entry.id());
}
#[test]
fn stale_delay_queue_offer_is_skipped() {
let mut wheel = TimingWheel::new(10, 8, 0);
let old_entry = Arc::new(Entry::new(1, 20, Arc::new(TimeoutState::scheduled())));
let new_entry = Arc::new(Entry::new(2, 100, Arc::new(TimeoutState::scheduled())));
assert!(wheel.add(Arc::clone(&old_entry)));
wheel.advance_clock(80);
assert!(wheel.add(Arc::clone(&new_entry)));
assert_eq!(wheel.next_expiration_ms(), Some(100));
}
#[test]
fn very_far_deadline_cascades_across_multiple_levels() {
let mut wheel = TimingWheel::new(10, 8, 0);
let entry = Arc::new(Entry::new(1, 800, Arc::new(TimeoutState::scheduled())));
assert!(wheel.add(Arc::clone(&entry)));
assert!(wheel.poll_expired(79).is_empty());
assert!(wheel.poll_expired(640).is_empty());
assert_eq!(wheel.poll_expired(800)[0].id(), entry.id());
}
}