tokio_util/time/wheel/
level.rs

1use crate::time::wheel::Stack;
2
3use std::fmt;
4
5/// Wheel for a single level in the timer. This wheel contains 64 slots.
6pub(crate) struct Level<T> {
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
19    slot: [T; LEVEL_MULT],
20}
21
22/// Indicates when a slot must be processed next.
23#[derive(Debug)]
24pub(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.
38const LEVEL_MULT: usize = 64;
39
40impl<T: Stack> Level<T> {
41    pub(crate) fn new(level: usize) -> Level<T> {
42        Level {
43            level,
44            occupied: 0,
45            slot: std::array::from_fn(|_| T::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 = match self.next_occupied_slot(now) {
55            Some(slot) => slot,
56            None => return None,
57        };
58
59        // From the slot index, calculate the `Instant` at which it needs to be
60        // processed. This value *must* be in the future with respect to `now`.
61
62        let level_range = level_range(self.level);
63        let slot_range = slot_range(self.level);
64
65        // TODO: This can probably be simplified w/ power of 2 math
66        let level_start = now - (now % level_range);
67        let mut deadline = level_start + slot as u64 * slot_range;
68        if deadline < now {
69            // A timer is in a slot "prior" to the current time. This can occur
70            // because we do not have an infinite hierarchy of timer levels, and
71            // eventually a timer scheduled for a very distant time might end up
72            // being placed in a slot that is beyond the end of all of the
73            // arrays.
74            //
75            // To deal with this, we first limit timers to being scheduled no
76            // more than MAX_DURATION ticks in the future; that is, they're at
77            // most one rotation of the top level away. Then, we force timers
78            // that logically would go into the top+1 level, to instead go into
79            // the top level's slots.
80            //
81            // What this means is that the top level's slots act as a
82            // pseudo-ring buffer, and we rotate around them indefinitely. If we
83            // compute a deadline before now, and it's the top level, it
84            // therefore means we're actually looking at a slot in the future.
85            debug_assert_eq!(self.level, super::NUM_LEVELS - 1);
86
87            deadline += level_range;
88        }
89        debug_assert!(
90            deadline >= now,
91            "deadline={:016X}; now={:016X}; level={}; slot={}; occupied={:b}",
92            deadline,
93            now,
94            self.level,
95            slot,
96            self.occupied
97        );
98
99        Some(Expiration {
100            level: self.level,
101            slot,
102            deadline,
103        })
104    }
105
106    fn next_occupied_slot(&self, now: u64) -> Option<usize> {
107        if self.occupied == 0 {
108            return None;
109        }
110
111        // Get the slot for now using Maths
112        let now_slot = (now / slot_range(self.level)) as usize;
113        let occupied = self.occupied.rotate_right(now_slot as u32);
114        let zeros = occupied.trailing_zeros() as usize;
115        let slot = (zeros + now_slot) % 64;
116
117        Some(slot)
118    }
119
120    pub(crate) fn add_entry(&mut self, when: u64, item: T::Owned, store: &mut T::Store) {
121        let slot = slot_for(when, self.level);
122
123        self.slot[slot].push(item, store);
124        self.occupied |= occupied_bit(slot);
125    }
126
127    pub(crate) fn remove_entry(&mut self, when: u64, item: &T::Borrowed, store: &mut T::Store) {
128        let slot = slot_for(when, self.level);
129
130        self.slot[slot].remove(item, store);
131
132        if self.slot[slot].is_empty() {
133            // The bit is currently set
134            debug_assert!(self.occupied & occupied_bit(slot) != 0);
135
136            // Unset the bit
137            self.occupied ^= occupied_bit(slot);
138        }
139    }
140
141    pub(crate) fn pop_entry_slot(&mut self, slot: usize, store: &mut T::Store) -> Option<T::Owned> {
142        let ret = self.slot[slot].pop(store);
143
144        if ret.is_some() && self.slot[slot].is_empty() {
145            // The bit is currently set
146            debug_assert!(self.occupied & occupied_bit(slot) != 0);
147
148            self.occupied ^= occupied_bit(slot);
149        }
150
151        ret
152    }
153
154    pub(crate) fn peek_entry_slot(&self, slot: usize) -> Option<T::Owned> {
155        self.slot[slot].peek()
156    }
157}
158
159impl<T> fmt::Debug for Level<T> {
160    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
161        fmt.debug_struct("Level")
162            .field("occupied", &self.occupied)
163            .finish()
164    }
165}
166
167fn occupied_bit(slot: usize) -> u64 {
168    1 << slot
169}
170
171fn slot_range(level: usize) -> u64 {
172    LEVEL_MULT.pow(level as u32) as u64
173}
174
175fn level_range(level: usize) -> u64 {
176    LEVEL_MULT as u64 * slot_range(level)
177}
178
179/// Convert a duration (milliseconds) and a level to a slot position
180fn slot_for(duration: u64, level: usize) -> usize {
181    ((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
182}
183
184#[cfg(all(test, not(loom)))]
185mod test {
186    use super::*;
187
188    #[test]
189    fn test_slot_for() {
190        for pos in 0..64 {
191            assert_eq!(pos as usize, slot_for(pos, 0));
192        }
193
194        for level in 1..5 {
195            for pos in level..64 {
196                let a = pos * 64_usize.pow(level as u32);
197                assert_eq!(pos, slot_for(a as u64, level));
198            }
199        }
200    }
201}