1use crate::runtime::time::{EntryList, TimerHandle, TimerShared};
23use std::{array, fmt, ptr::NonNull};
45/// Wheel for a single level in the timer. This wheel contains 64 slots.
6pub(crate) struct Level {
7 level: usize,
89/// Bit field tracking which slots currently contain entries.
10 ///
11 /// Using a bit field to track slots that contain entries allows avoiding a
12 /// scan to find entries. This field is updated when entries are added or
13 /// removed from a slot.
14 ///
15 /// The least-significant bit represents slot zero.
16occupied: u64,
1718/// Slots. We access these via the EntryInner `current_list` as well, so this needs to be an `UnsafeCell`.
19slot: [EntryList; LEVEL_MULT],
20}
2122/// Indicates when a slot must be processed next.
23#[derive(Debug)]
24pub(crate) struct Expiration {
25/// The level containing the slot.
26pub(crate) level: usize,
2728/// The slot index.
29pub(crate) slot: usize,
3031/// The instant at which the slot needs to be processed.
32pub(crate) deadline: u64,
33}
3435/// Level multiplier.
36///
37/// Being a power of 2 is very important.
38const LEVEL_MULT: usize = 64;
3940impl Level {
41pub(crate) fn new(level: usize) -> Level {
42 Level {
43 level,
44 occupied: 0,
45 slot: array::from_fn(|_| EntryList::default()),
46 }
47 }
4849/// Finds the slot that needs to be processed next and returns the slot and
50 /// `Instant` at which this slot must be processed.
51pub(crate) fn next_expiration(&self, now: u64) -> Option<Expiration> {
52// Use the `occupied` bit field to get the index of the next slot that
53 // needs to be processed.
54let slot = self.next_occupied_slot(now)?;
5556// From the slot index, calculate the `Instant` at which it needs to be
57 // processed. This value *must* be in the future with respect to `now`.
5859let level_range = level_range(self.level);
60let slot_range = slot_range(self.level);
6162// Compute the start date of the current level by masking the low bits
63 // of `now` (`level_range` is a power of 2).
64let level_start = now & !(level_range - 1);
65let mut deadline = level_start + slot as u64 * slot_range;
6667if deadline <= now {
68// A timer is in a slot "prior" to the current time. This can occur
69 // because we do not have an infinite hierarchy of timer levels, and
70 // eventually a timer scheduled for a very distant time might end up
71 // being placed in a slot that is beyond the end of all of the
72 // arrays.
73 //
74 // To deal with this, we first limit timers to being scheduled no
75 // more than MAX_DURATION ticks in the future; that is, they're at
76 // most one rotation of the top level away. Then, we force timers
77 // that logically would go into the top+1 level, to instead go into
78 // the top level's slots.
79 //
80 // What this means is that the top level's slots act as a
81 // pseudo-ring buffer, and we rotate around them indefinitely. If we
82 // compute a deadline before now, and it's the top level, it
83 // therefore means we're actually looking at a slot in the future.
84debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
8586 deadline += level_range;
87 }
8889debug_assert!(
90 deadline >= now,
91"deadline={:016X}; now={:016X}; level={}; lr={:016X}, sr={:016X}, slot={}; occupied={:b}",
92 deadline,
93 now,
94self.level,
95 level_range,
96 slot_range,
97 slot,
98self.occupied
99 );
100101Some(Expiration {
102 level: self.level,
103 slot,
104 deadline,
105 })
106 }
107108fn next_occupied_slot(&self, now: u64) -> Option<usize> {
109if self.occupied == 0 {
110return None;
111 }
112113// Get the slot for now using Maths
114let now_slot = (now / slot_range(self.level)) as usize;
115let occupied = self.occupied.rotate_right(now_slot as u32);
116let zeros = occupied.trailing_zeros() as usize;
117let slot = (zeros + now_slot) % LEVEL_MULT;
118119Some(slot)
120 }
121122pub(crate) unsafe fn add_entry(&mut self, item: TimerHandle) {
123let slot = slot_for(item.cached_when(), self.level);
124125self.slot[slot].push_front(item);
126127self.occupied |= occupied_bit(slot);
128 }
129130pub(crate) unsafe fn remove_entry(&mut self, item: NonNull<TimerShared>) {
131let slot = slot_for(unsafe { item.as_ref().cached_when() }, self.level);
132133unsafe { self.slot[slot].remove(item) };
134if self.slot[slot].is_empty() {
135// The bit is currently set
136debug_assert!(self.occupied & occupied_bit(slot) != 0);
137138// Unset the bit
139self.occupied ^= occupied_bit(slot);
140 }
141 }
142143pub(crate) fn take_slot(&mut self, slot: usize) -> EntryList {
144self.occupied &= !occupied_bit(slot);
145146 std::mem::take(&mut self.slot[slot])
147 }
148}
149150impl fmt::Debug for Level {
151fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
152 fmt.debug_struct("Level")
153 .field("occupied", &self.occupied)
154 .finish()
155 }
156}
157158fn occupied_bit(slot: usize) -> u64 {
1591 << slot
160}
161162fn slot_range(level: usize) -> u64 {
163 LEVEL_MULT.pow(level as u32) as u64
164}
165166fn level_range(level: usize) -> u64 {
167 LEVEL_MULT as u64 * slot_range(level)
168}
169170/// Converts a duration (milliseconds) and a level to a slot position.
171fn slot_for(duration: u64, level: usize) -> usize {
172 ((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
173}
174175#[cfg(all(test, not(loom)))]
176mod test {
177use super::*;
178179#[test]
180fn test_slot_for() {
181for pos in 0..64 {
182assert_eq!(pos as usize, slot_for(pos, 0));
183 }
184185for level in 1..5 {
186for pos in level..64 {
187let a = pos * 64_usize.pow(level as u32);
188assert_eq!(pos, slot_for(a as u64, level));
189 }
190 }
191 }
192}