Skip to main content

tokio/runtime/time_alt/wheel/
level.rs

1use super::{EntryHandle, EntryList};
2use std::ptr::NonNull;
3use std::{array, fmt};
4
5/// Wheel for a single level in the timer. This wheel contains 64 slots.
6pub(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)]
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 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, hdl: EntryHandle) {
123        // Safety: the associated entry must be valid.
124        let deadline = hdl.deadline();
125        let slot = slot_for(deadline, self.level);
126
127        self.slot[slot].push_front(hdl);
128
129        self.occupied |= occupied_bit(slot);
130    }
131
132    pub(crate) unsafe fn remove_entry(&mut self, hdl: EntryHandle) {
133        let slot = slot_for(hdl.deadline(), self.level);
134
135        unsafe { self.slot[slot].remove(NonNull::from(&hdl)) };
136        if self.slot[slot].is_empty() {
137            // The bit is currently set
138            debug_assert!(self.occupied & occupied_bit(slot) != 0);
139
140            // Unset the bit
141            self.occupied ^= occupied_bit(slot);
142        }
143    }
144
145    pub(crate) fn take_slot(&mut self, slot: usize) -> EntryList {
146        self.occupied &= !occupied_bit(slot);
147
148        std::mem::take(&mut self.slot[slot])
149    }
150}
151
152impl fmt::Debug for Level {
153    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
154        fmt.debug_struct("Level")
155            .field("occupied", &self.occupied)
156            .finish()
157    }
158}
159
160fn occupied_bit(slot: usize) -> u64 {
161    1 << slot
162}
163
164fn slot_range(level: usize) -> u64 {
165    LEVEL_MULT.pow(level as u32) as u64
166}
167
168fn level_range(level: usize) -> u64 {
169    LEVEL_MULT as u64 * slot_range(level)
170}
171
172/// Converts a duration (milliseconds) and a level to a slot position.
173fn slot_for(duration: u64, level: usize) -> usize {
174    ((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
175}
176
177#[cfg(all(test, not(loom)))]
178mod test {
179    use super::*;
180
181    #[test]
182    fn test_slot_for() {
183        for pos in 0..64 {
184            assert_eq!(pos as usize, slot_for(pos, 0));
185        }
186
187        for level in 1..5 {
188            for pos in level..64 {
189                let a = pos * 64_usize.pow(level as u32);
190                assert_eq!(pos, slot_for(a as u64, level));
191            }
192        }
193    }
194}