use crate::util::linked_list::LinkedList;
use super::{Entry, EntryHandle};
use std::ptr::NonNull;
use std::{array, fmt};
pub(crate) struct Level {
level: usize,
occupied: u64,
slot: [LinkedList<Entry>; LEVEL_MULT],
}
#[derive(Debug)]
pub(crate) struct Expiration {
pub(crate) level: usize,
pub(crate) slot: usize,
pub(crate) deadline: u64,
}
const LEVEL_MULT: usize = 1 << super::BITS_PER_LEVEL;
impl Level {
pub(crate) fn new(level: usize) -> Level {
Level {
level,
occupied: 0,
slot: array::from_fn(|_| LinkedList::default()),
}
}
pub(crate) fn next_expiration(&self, now: u64) -> Option<Expiration> {
let slot = self.next_occupied_slot(now)?;
let level_range = level_range(self.level);
let slot_range = slot_range(self.level);
let level_start = now & !(level_range - 1);
let mut deadline = level_start + slot as u64 * slot_range;
if deadline <= now {
debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
deadline += level_range;
}
debug_assert!(
deadline >= now,
"deadline={:016X}; now={:016X}; level={}; lr={:016X}, sr={:016X}, slot={}; occupied={:b}",
deadline,
now,
self.level,
level_range,
slot_range,
slot,
self.occupied
);
Some(Expiration {
level: self.level,
slot,
deadline,
})
}
fn next_occupied_slot(&self, now: u64) -> Option<usize> {
if self.occupied == 0 {
return None;
}
let now_slot = ((now / slot_range(self.level)) % LEVEL_MULT as u64) as usize + 1;
let occupied = self.occupied.rotate_right(now_slot as u32);
let zeros = occupied.trailing_zeros() as usize;
let slot = (zeros + now_slot) % LEVEL_MULT;
Some(slot)
}
pub(crate) unsafe fn add_entry(&mut self, hdl: EntryHandle) {
let deadline = hdl.deadline();
let slot = slot_for(deadline, self.level);
self.slot[slot].push_front(hdl);
self.occupied |= occupied_bit(slot);
}
pub(crate) unsafe fn remove_entry(&mut self, hdl: EntryHandle) {
let slot = slot_for(hdl.deadline(), self.level);
unsafe {
assert!(
self.slot[slot].remove(NonNull::from(&hdl)).is_some(),
"Attempt to remove item not present in the timing wheel"
)
};
if self.slot[slot].is_empty() {
debug_assert!(self.occupied & occupied_bit(slot) != 0);
self.occupied ^= occupied_bit(slot);
}
}
pub(crate) fn take_slot(&mut self, slot: usize) -> LinkedList<Entry> {
self.occupied &= !occupied_bit(slot);
std::mem::take(&mut self.slot[slot])
}
}
impl fmt::Debug for Level {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("Level")
.field("occupied", &self.occupied)
.finish()
}
}
fn occupied_bit(slot: usize) -> u64 {
1 << slot
}
fn slot_range(level: usize) -> u64 {
1 << (super::BITS_PER_LEVEL * level)
}
fn level_range(level: usize) -> u64 {
1 << (super::BITS_PER_LEVEL * (level + 1))
}
fn slot_for(duration: u64, level: usize) -> usize {
((duration >> (level * super::BITS_PER_LEVEL)) % LEVEL_MULT as u64) as usize
}
#[cfg(all(test, not(loom)))]
mod test {
use super::*;
#[test]
fn test_slot_for() {
for pos in 0..64 {
assert_eq!(pos as usize, slot_for(pos, 0));
}
for level in 1..5 {
for pos in level..64 {
let a = pos * 64_usize.pow(level as u32);
assert_eq!(pos, slot_for(a as u64, level));
}
}
}
fn level_with(level: usize, occupied: u64) -> Level {
let mut level = Level::new(level);
level.occupied = occupied;
level
}
#[test]
fn next_occupied_slot_on_an_empty_level() {
assert_eq!(Level::new(0).next_occupied_slot(0), None);
assert_eq!(Level::new(5).next_occupied_slot(1 << 36), None);
}
#[test]
fn next_occupied_slot_of_a_single_slot() {
let level = level_with(0, 1 << 10);
assert_eq!(level.next_occupied_slot(0), Some(10));
assert_eq!(level.next_occupied_slot(9), Some(10));
assert_eq!(level.next_occupied_slot(10), Some(10));
assert_eq!(level.next_occupied_slot(11), Some(10));
assert_eq!(level.next_occupied_slot(64 + 3), Some(10));
}
#[test]
fn next_occupied_slot_picks_the_nearest_slot_forward() {
let level = level_with(0, (1 << 10) | (1 << 40));
assert_eq!(level.next_occupied_slot(0), Some(10));
assert_eq!(level.next_occupied_slot(20), Some(40));
assert_eq!(level.next_occupied_slot(41), Some(10));
}
#[test]
fn next_occupied_slot_skips_the_slot_holding_now() {
let level = level_with(5, 0b11);
assert_eq!(level.next_occupied_slot(0), Some(1));
assert_eq!(level.next_occupied_slot((1 << 30) - 1), Some(1));
let level = level_with(5, 1 | (1 << 40));
assert_eq!(level.next_occupied_slot(0), Some(40));
}
#[test]
fn next_occupied_slot_of_the_slot_holding_now_when_it_is_the_only_one() {
let level = level_with(5, 1);
assert_eq!(level.next_occupied_slot(0), Some(0));
assert_eq!(level.next_occupied_slot((1 << 30) - 1), Some(0));
}
#[test]
fn next_occupied_slot_of_the_last_slot() {
let level = level_with(5, 1 << 63);
assert_eq!(level.next_occupied_slot(62 << 30), Some(63));
assert_eq!(level.next_occupied_slot(63 << 30), Some(63));
}
#[test]
fn next_expiration_reports_the_start_of_the_slot() {
let expiration = level_with(1, 1 << 3).next_expiration(100).unwrap();
assert_eq!(expiration.level, 1);
assert_eq!(expiration.slot, 3);
assert_eq!(expiration.deadline, 192);
}
#[test]
fn next_expiration_below_the_top_level() {
let level = level_with(4, 1 << 1);
assert_eq!(level.next_expiration(0).unwrap().deadline, 1 << 24);
assert_eq!(level.next_expiration(1000).unwrap().deadline, 1 << 24);
}
#[test]
fn next_expiration_at_the_top_level() {
let level = level_with(5, 1 << 1);
assert_eq!(level.next_expiration(0).unwrap().deadline, 1 << 30);
assert_eq!(level.next_expiration(1000).unwrap().deadline, 1 << 30);
}
#[test]
fn next_expiration_wraps_a_slot_at_or_behind_now() {
let level = level_with(5, 1 << 0);
assert_eq!(level.next_expiration(0).unwrap().deadline, 1 << 36);
assert_eq!(
level.next_expiration((1 << 30) + 10).unwrap().deadline,
1 << 36
);
}
}