| 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 { |
| 41 | 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. |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 143 | 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 { |
| 151 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 152 | fmt&mut DebugStruct<'_, '_>.debug_struct("Level" ) |
| 153 | .field(name:"occupied" , &self.occupied) |
| 154 | .finish() |
| 155 | } |
| 156 | } |
| 157 | |
| 158 | fn occupied_bit(slot: usize) -> u64 { |
| 159 | 1 << slot |
| 160 | } |
| 161 | |
| 162 | fn slot_range(level: usize) -> u64 { |
| 163 | LEVEL_MULT.pow(exp:level as u32) as u64 |
| 164 | } |
| 165 | |
| 166 | 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. |
| 171 | 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 ] |
| 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 | |