1 use crate::runtime::time::{EntryList, TimerHandle, TimerShared};
2 
3 use std::{array, fmt, ptr::NonNull};
4 
5 /// Wheel for a single level in the timer. This wheel contains 64 slots.
6 pub(crate) struct Level {
7     level: usize,
8 
9     /// 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.
16     occupied: u64,
17 
18     /// Slots. We access these via the EntryInner `current_list` as well, so this needs to be an `UnsafeCell`.
19     slot: [EntryList; LEVEL_MULT],
20 }
21 
22 /// Indicates when a slot must be processed next.
23 #[derive(Debug)]
24 pub(crate) struct Expiration {
25     /// The level containing the slot.
26     pub(crate) level: usize,
27 
28     /// The slot index.
29     pub(crate) slot: usize,
30 
31     /// The instant at which the slot needs to be processed.
32     pub(crate) deadline: u64,
33 }
34 
35 /// Level multiplier.
36 ///
37 /// Being a power of 2 is very important.
38 const LEVEL_MULT: usize = 64;
39 
40 impl Level {
new(level: usize) -> Level41     pub(crate) fn new(level: usize) -> Level {
42         Level {
43             level,
44             occupied: 0,
45             slot: array::from_fn(|_| EntryList::default()),
46         }
47     }
48 
49     /// Finds the slot that needs to be processed next and returns the slot and
50     /// `Instant` at which this slot must be processed.
next_expiration(&self, now: u64) -> Option<Expiration>51     pub(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.
54         let slot = self.next_occupied_slot(now)?;
55 
56         // 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`.
58 
59         let level_range = level_range(self.level);
60         let slot_range = slot_range(self.level);
61 
62         // Compute the start date of the current level by masking the low bits
63         // of `now` (`level_range` is a power of 2).
64         let level_start = now & !(level_range - 1);
65         let mut deadline = level_start + slot as u64 * slot_range;
66 
67         if 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.
84             debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
85 
86             deadline += level_range;
87         }
88 
89         debug_assert!(
90             deadline >= now,
91             "deadline={:016X}; now={:016X}; level={}; lr={:016X}, sr={:016X}, slot={}; occupied={:b}",
92             deadline,
93             now,
94             self.level,
95             level_range,
96             slot_range,
97             slot,
98             self.occupied
99         );
100 
101         Some(Expiration {
102             level: self.level,
103             slot,
104             deadline,
105         })
106     }
107 
next_occupied_slot(&self, now: u64) -> Option<usize>108     fn next_occupied_slot(&self, now: u64) -> Option<usize> {
109         if self.occupied == 0 {
110             return None;
111         }
112 
113         // Get the slot for now using Maths
114         let now_slot = (now / slot_range(self.level)) as usize;
115         let occupied = self.occupied.rotate_right(now_slot as u32);
116         let zeros = occupied.trailing_zeros() as usize;
117         let slot = (zeros + now_slot) % LEVEL_MULT;
118 
119         Some(slot)
120     }
121 
add_entry(&mut self, item: TimerHandle)122     pub(crate) unsafe fn add_entry(&mut self, item: TimerHandle) {
123         let slot = slot_for(item.cached_when(), self.level);
124 
125         self.slot[slot].push_front(item);
126 
127         self.occupied |= occupied_bit(slot);
128     }
129 
remove_entry(&mut self, item: NonNull<TimerShared>)130     pub(crate) unsafe fn remove_entry(&mut self, item: NonNull<TimerShared>) {
131         let slot = slot_for(unsafe { item.as_ref().cached_when() }, self.level);
132 
133         unsafe { self.slot[slot].remove(item) };
134         if self.slot[slot].is_empty() {
135             // The bit is currently set
136             debug_assert!(self.occupied & occupied_bit(slot) != 0);
137 
138             // Unset the bit
139             self.occupied ^= occupied_bit(slot);
140         }
141     }
142 
take_slot(&mut self, slot: usize) -> EntryList143     pub(crate) fn take_slot(&mut self, slot: usize) -> EntryList {
144         self.occupied &= !occupied_bit(slot);
145 
146         std::mem::take(&mut self.slot[slot])
147     }
148 }
149 
150 impl fmt::Debug for Level {
fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result151     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
152         fmt.debug_struct("Level")
153             .field("occupied", &self.occupied)
154             .finish()
155     }
156 }
157 
occupied_bit(slot: usize) -> u64158 fn occupied_bit(slot: usize) -> u64 {
159     1 << slot
160 }
161 
slot_range(level: usize) -> u64162 fn slot_range(level: usize) -> u64 {
163     LEVEL_MULT.pow(level as u32) as u64
164 }
165 
level_range(level: usize) -> u64166 fn level_range(level: usize) -> u64 {
167     LEVEL_MULT as u64 * slot_range(level)
168 }
169 
170 /// Converts a duration (milliseconds) and a level to a slot position.
slot_for(duration: u64, level: usize) -> usize171 fn slot_for(duration: u64, level: usize) -> usize {
172     ((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
173 }
174 
175 #[cfg(all(test, not(loom)))]
176 mod test {
177     use super::*;
178 
179     #[test]
test_slot_for()180     fn test_slot_for() {
181         for pos in 0..64 {
182             assert_eq!(pos as usize, slot_for(pos, 0));
183         }
184 
185         for level in 1..5 {
186             for pos in level..64 {
187                 let a = pos * 64_usize.pow(level as u32);
188                 assert_eq!(pos, slot_for(a as u64, level));
189             }
190         }
191     }
192 }
193