|
| 1 | +// License and Copyright Notice: |
| 2 | +// |
| 3 | +// Some of the code and doc comments in this module were ported or copied from |
| 4 | +// a Java class `com.github.benmanes.caffeine.cache.TimerWheel` of Caffeine. |
| 5 | +// https://github.com/ben-manes/caffeine/blob/master/caffeine/src/main/java/com/github/benmanes/caffeine/cache/TimerWheel.java |
| 6 | +// |
| 7 | +// The original code/comments from Caffeine are licensed under the Apache License, |
| 8 | +// Version 2.0 <https://github.com/ben-manes/caffeine/blob/master/LICENSE> |
| 9 | +// |
| 10 | +// Copyrights of the original code/comments are retained by their contributors. |
| 11 | +// For full authorship information, see the version control history of |
| 12 | +// https://github.com/ben-manes/caffeine/ |
| 13 | + |
| 14 | +#![allow(unused)] // TODO: Remove this. |
| 15 | + |
| 16 | +use std::{convert::TryInto, ptr::NonNull, time::Duration}; |
| 17 | + |
| 18 | +use super::{ |
| 19 | + deque::{DeqNode, Deque}, |
| 20 | + time::{CheckedTimeOps, Instant}, |
| 21 | +}; |
| 22 | + |
| 23 | +const BUCKET_COUNTS: &[u64] = &[ |
| 24 | + 64, // roughly seconds |
| 25 | + 64, // roughly minutes |
| 26 | + 32, // roughly hours |
| 27 | + 4, // roughly days |
| 28 | + 1, // overflow (> ~6.5 days) |
| 29 | +]; |
| 30 | + |
| 31 | +const OVERFLOW_QUEUE_INDEX: usize = BUCKET_COUNTS.len() - 1; |
| 32 | +const NUM_LEVELS: usize = OVERFLOW_QUEUE_INDEX - 1; |
| 33 | + |
| 34 | +const DAY: Duration = Duration::from_secs(60 * 60 * 24); |
| 35 | + |
| 36 | +const SPANS: &[u64] = &[ |
| 37 | + aligned_duration(Duration::from_secs(1)), // 1.07s |
| 38 | + aligned_duration(Duration::from_secs(60)), // 1.14m |
| 39 | + aligned_duration(Duration::from_secs(60 * 60)), // 1.22h |
| 40 | + aligned_duration(DAY), // 1.63d |
| 41 | + BUCKET_COUNTS[3] * aligned_duration(DAY), // 6.5d |
| 42 | + BUCKET_COUNTS[3] * aligned_duration(DAY), // 6.5d |
| 43 | +]; |
| 44 | + |
| 45 | +const SHIFT: &[u64] = &[ |
| 46 | + SPANS[0].trailing_zeros() as u64, |
| 47 | + SPANS[1].trailing_zeros() as u64, |
| 48 | + SPANS[2].trailing_zeros() as u64, |
| 49 | + SPANS[3].trailing_zeros() as u64, |
| 50 | + SPANS[4].trailing_zeros() as u64, |
| 51 | +]; |
| 52 | + |
| 53 | +/// Returns the next power of two of the duration in nanoseconds. |
| 54 | +const fn aligned_duration(duration: Duration) -> u64 { |
| 55 | + // NOTE: as_nanos() returns u128, so convert it to u64 by using `as`. |
| 56 | + // We cannot call TryInto::try_into() here because it is not a const fn. |
| 57 | + (duration.as_nanos() as u64).next_power_of_two() |
| 58 | +} |
| 59 | + |
| 60 | +/// A hierarchical timer wheel to add, remove, and fire expiration events in |
| 61 | +/// amortized O(1) time. |
| 62 | +/// |
| 63 | +/// The expiration events are deferred until the timer is advanced, which is |
| 64 | +/// performed as part of the cache's housekeeping cycle. |
| 65 | +pub(crate) struct TimerWheel<T> { |
| 66 | + wheels: Box<[Box<[Deque<T>]>]>, |
| 67 | + /// The time when this timer wheel was created. |
| 68 | + origin: Instant, |
| 69 | + /// The time when this timer wheel was last advanced. |
| 70 | + current: Instant, |
| 71 | +} |
| 72 | + |
| 73 | +impl<T> TimerWheel<T> { |
| 74 | + fn new(now: Instant) -> Self { |
| 75 | + let wheels = BUCKET_COUNTS |
| 76 | + .iter() |
| 77 | + .map(|b| { |
| 78 | + (0..*b) |
| 79 | + .map(|_| Deque::new(super::CacheRegion::Other)) |
| 80 | + .collect::<Vec<_>>() |
| 81 | + .into_boxed_slice() |
| 82 | + }) |
| 83 | + .collect::<Vec<_>>() |
| 84 | + .into_boxed_slice(); |
| 85 | + Self { |
| 86 | + wheels, |
| 87 | + origin: now, |
| 88 | + current: now, |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + /// Schedules a timer event for the node. |
| 93 | + // pub(crate) fn schedule(&mut self, node: Box<DeqNode<T>>) { |
| 94 | + // if let Some(t) = node.element.expiration_time() { |
| 95 | + // let (level, index) = self.bucket_indices(t); |
| 96 | + // self.wheels[level][index].push_back(node); |
| 97 | + // } |
| 98 | + // } |
| 99 | + |
| 100 | + // /// Reschedules an active timer event for the node. |
| 101 | + // pub(crate) fn reschedule(&mut self, node: NonNull<DeqNode<T>>) {} |
| 102 | + |
| 103 | + /// Removes a timer event for this node if present. |
| 104 | + // pub(crate) fn deschedule(&mut self, node: NonNull<DeqNode<T>>) { |
| 105 | + // if let Some(t) = node.element.expiration_time() { |
| 106 | + // let (level, index) = self.bucket_indices(t); |
| 107 | + // unsafe { self.wheels[level][index].unlink_and_drop(node) }; |
| 108 | + // } |
| 109 | + // } |
| 110 | + |
| 111 | + /// Returns the bucket indices to locate the bucket that the timer event |
| 112 | + /// should be added to. |
| 113 | + fn bucket_indices(&self, time: Instant) -> (usize, usize) { |
| 114 | + let duration = time |
| 115 | + .checked_duration_since(self.current) |
| 116 | + // FIXME: unwrap will panic if the time is earlier than self.current. |
| 117 | + .unwrap() |
| 118 | + .as_nanos() as u64; |
| 119 | + // ENHANCEME: Check overflow? (u128 -> u64) |
| 120 | + // FIXME: unwrap will panic if the time is earlier than self.origin. |
| 121 | + let time_nano = time.checked_duration_since(self.origin).unwrap().as_nanos() as u64; |
| 122 | + for level in 0..=NUM_LEVELS { |
| 123 | + if duration < SPANS[level + 1] { |
| 124 | + let ticks = time_nano >> SHIFT[level]; |
| 125 | + let index = ticks & (BUCKET_COUNTS[level] - 1); |
| 126 | + return (level, index as usize); |
| 127 | + } |
| 128 | + } |
| 129 | + (OVERFLOW_QUEUE_INDEX, 0) |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +#[cfg(test)] |
| 134 | +mod tests { |
| 135 | + use std::time::Duration; |
| 136 | + |
| 137 | + use super::{TimerWheel, SPANS}; |
| 138 | + use crate::common::time::{CheckedTimeOps, Clock, Instant}; |
| 139 | + |
| 140 | + #[test] |
| 141 | + fn test_bucket_indices() { |
| 142 | + fn dur(nanos: u64) -> Duration { |
| 143 | + Duration::from_nanos(nanos) |
| 144 | + } |
| 145 | + |
| 146 | + fn bi(timer: &TimerWheel<()>, now: Instant, dur: Duration) -> (usize, usize) { |
| 147 | + let t = now.checked_add(dur).unwrap(); |
| 148 | + timer.bucket_indices(t) |
| 149 | + } |
| 150 | + |
| 151 | + let (clock, mock) = Clock::mock(); |
| 152 | + let now = Instant::new(clock.now()); |
| 153 | + |
| 154 | + let mut timer = TimerWheel::<()>::new(now); |
| 155 | + assert_eq!(timer.bucket_indices(now), (0, 0)); |
| 156 | + |
| 157 | + // Level 0: 1.07s |
| 158 | + assert_eq!(bi(&timer, now, dur(SPANS[0] - 1)), (0, 0)); |
| 159 | + assert_eq!(bi(&timer, now, dur(SPANS[0])), (0, 1)); |
| 160 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 63)), (0, 63)); |
| 161 | + |
| 162 | + // Level 1: 1.14m |
| 163 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 64)), (1, 1)); |
| 164 | + assert_eq!(bi(&timer, now, dur(SPANS[1])), (1, 1)); |
| 165 | + assert_eq!(bi(&timer, now, dur(SPANS[1] * 63 + SPANS[0] * 63)), (1, 63)); |
| 166 | + |
| 167 | + // Level 2: 1.22h |
| 168 | + assert_eq!(bi(&timer, now, dur(SPANS[1] * 64)), (2, 1)); |
| 169 | + assert_eq!(bi(&timer, now, dur(SPANS[2])), (2, 1)); |
| 170 | + assert_eq!( |
| 171 | + bi( |
| 172 | + &timer, |
| 173 | + now, |
| 174 | + dur(SPANS[2] * 31 + SPANS[1] * 63 + SPANS[0] * 63) |
| 175 | + ), |
| 176 | + (2, 31) |
| 177 | + ); |
| 178 | + |
| 179 | + // Level 3: 1.63dh |
| 180 | + assert_eq!(bi(&timer, now, dur(SPANS[2] * 32)), (3, 1)); |
| 181 | + assert_eq!(bi(&timer, now, dur(SPANS[3])), (3, 1)); |
| 182 | + assert_eq!(bi(&timer, now, dur(SPANS[3] * 3)), (3, 3)); |
| 183 | + |
| 184 | + // Overflow |
| 185 | + assert_eq!(bi(&timer, now, dur(SPANS[3] * 4)), (4, 0)); |
| 186 | + assert_eq!(bi(&timer, now, dur(SPANS[4])), (4, 0)); |
| 187 | + assert_eq!(bi(&timer, now, dur(SPANS[4] * 100)), (4, 0)); |
| 188 | + |
| 189 | + // Increment the clock by 5 ticks. (1 tick ~= 1.07s) |
| 190 | + mock.increment(dur(SPANS[0] * 5)); |
| 191 | + let now = Instant::new(clock.now()); |
| 192 | + timer.current = now; |
| 193 | + |
| 194 | + // Level 0: 1.07s |
| 195 | + assert_eq!(bi(&timer, now, dur(SPANS[0] - 1)), (0, 5)); |
| 196 | + assert_eq!(bi(&timer, now, dur(SPANS[0])), (0, 6)); |
| 197 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 63)), (0, 4)); |
| 198 | + |
| 199 | + // Level 1: 1.14m |
| 200 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 64)), (1, 1)); |
| 201 | + assert_eq!(bi(&timer, now, dur(SPANS[1])), (1, 1)); |
| 202 | + assert_eq!( |
| 203 | + bi(&timer, now, dur(SPANS[1] * 63 + SPANS[0] * (63 - 5))), |
| 204 | + (1, 63) |
| 205 | + ); |
| 206 | + |
| 207 | + // Increment the clock by 61 ticks. (total 66 ticks) |
| 208 | + mock.increment(dur(SPANS[0] * 61)); |
| 209 | + let now = Instant::new(clock.now()); |
| 210 | + timer.current = now; |
| 211 | + |
| 212 | + // Level 0: 1.07s |
| 213 | + assert_eq!(bi(&timer, now, dur(SPANS[0] - 1)), (0, 2)); |
| 214 | + assert_eq!(bi(&timer, now, dur(SPANS[0])), (0, 3)); |
| 215 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 63)), (0, 1)); |
| 216 | + |
| 217 | + // Level 1: 1.14m |
| 218 | + assert_eq!(bi(&timer, now, dur(SPANS[0] * 64)), (1, 2)); |
| 219 | + assert_eq!(bi(&timer, now, dur(SPANS[1])), (1, 2)); |
| 220 | + assert_eq!( |
| 221 | + bi(&timer, now, dur(SPANS[1] * 63 + SPANS[0] * (63 - 2))), |
| 222 | + (1, 0) |
| 223 | + ); |
| 224 | + } |
| 225 | +} |
0 commit comments