1use std::cmp::Reverse;
23use std::collections::{BinaryHeap, HashMap};
24use std::hash::Hash;
25use std::ops::Add;
26use std::time::Duration;
27
28use tokio::time::Instant;
29
30#[derive(Debug, PartialEq, Eq)]
31struct Entry<K, I> {
32 deadline: I,
33 generation: u64,
34 key: K,
35}
36
37impl<K: Eq, I: Ord> Ord for Entry<K, I> {
40 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
41 self.deadline.cmp(&other.deadline)
42 }
43}
44
45impl<K: Eq, I: Ord> PartialOrd for Entry<K, I> {
46 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
47 Some(self.cmp(other))
48 }
49}
50
51#[derive(Debug)]
58pub struct TimerQueue<K, I = Instant> {
59 heap: BinaryHeap<Reverse<Entry<K, I>>>,
60 generations: HashMap<K, u64>,
61 next_generation: u64,
64}
65
66impl<K: Eq, I: Ord> Default for TimerQueue<K, I> {
70 fn default() -> Self {
71 Self {
72 heap: BinaryHeap::new(),
73 generations: HashMap::new(),
74 next_generation: 0,
75 }
76 }
77}
78
79impl<K: Clone + Eq + Hash, I: Ord + Copy + Add<Duration, Output = I>> TimerQueue<K, I> {
80 #[must_use]
82 pub fn new() -> Self {
83 Self::default()
84 }
85
86 #[must_use]
88 pub fn len(&self) -> usize {
89 self.heap.len()
90 }
91
92 #[must_use]
94 pub fn is_empty(&self) -> bool {
95 self.heap.is_empty()
96 }
97
98 pub fn set(&mut self, key: K, now: I, after: Duration) {
104 let generation = self.bump(&key);
105 self.heap.push(Reverse(Entry {
106 deadline: now + after,
107 generation,
108 key,
109 }));
110 }
111
112 pub fn clear(&mut self, key: &K) {
114 self.bump(key);
115 }
116
117 pub fn forget(&mut self, key: &K) {
123 self.generations.remove(key);
124 }
125
126 pub fn clear_matching(&mut self, matches: impl Fn(&K) -> bool) {
132 let keys: Vec<K> = self
133 .generations
134 .keys()
135 .filter(|key| matches(key))
136 .cloned()
137 .collect();
138 for key in keys {
139 self.bump(&key);
140 }
141 }
142
143 fn bump(&mut self, key: &K) -> u64 {
144 if self.next_generation == u64::MAX {
145 self.compact_generations();
146 }
147 self.next_generation += 1;
148 self.generations.insert(key.clone(), self.next_generation);
149 self.next_generation
150 }
151
152 fn compact_generations(&mut self) {
157 let previous = std::mem::take(&mut self.generations);
158 let entries = std::mem::take(&mut self.heap);
159 self.next_generation = 0;
160 for Reverse(mut entry) in entries {
161 if previous.get(&entry.key) != Some(&entry.generation) {
162 continue;
163 }
164 self.next_generation += 1;
165 entry.generation = self.next_generation;
166 self.generations
167 .insert(entry.key.clone(), self.next_generation);
168 self.heap.push(Reverse(entry));
169 }
170 }
171
172 pub fn next_deadline(&mut self) -> Option<I> {
177 loop {
178 let Reverse(entry) = self.heap.peek()?;
179 if self.is_live(entry) {
180 return Some(entry.deadline);
181 }
182 self.heap.pop();
183 }
184 }
185
186 pub fn take_due(&mut self, now: I) -> Vec<K> {
188 let mut fired = Vec::new();
189 while let Some(Reverse(entry)) = self.heap.peek() {
190 if entry.deadline > now {
191 break;
192 }
193 let Some(Reverse(entry)) = self.heap.pop() else {
194 break;
195 };
196 if !self.is_live(&entry) {
197 continue;
198 }
199 self.bump(&entry.key);
202 fired.push(entry.key);
203 }
204 fired
205 }
206
207 fn is_live(&self, entry: &Entry<K, I>) -> bool {
208 self.generations
209 .get(&entry.key)
210 .is_some_and(|&generation| generation == entry.generation)
211 }
212
213 pub fn forget_matching(&mut self, matches: impl Fn(&K) -> bool) {
215 self.generations.retain(|key, _| !matches(key));
216 }
217}
218
219#[cfg(test)]
220#[allow(
221 clippy::unwrap_used,
222 clippy::expect_used,
223 clippy::panic,
224 clippy::indexing_slicing
225)]
226mod tests {
227 use super::*;
228 use sipx_sip::transaction::{Timer, TransactionKey};
229 use std::time::Duration;
230
231 fn key(branch: &str) -> TransactionKey {
232 TransactionKey::Rfc3261 {
233 branch: branch.as_bytes().to_vec(),
234 sent_by: b"h.example.com".to_vec(),
235 method: b"INVITE".to_vec(),
236 }
237 }
238
239 type Transactions = TimerQueue<(TransactionKey, Timer)>;
241
242 #[tokio::test(start_paused = true)]
243 async fn timers_fire_in_deadline_order() {
244 let mut q = Transactions::new();
245 let now = Instant::now();
246 q.set((key("a"), Timer::A), now, Duration::from_millis(500));
247 q.set((key("b"), Timer::B), now, Duration::from_millis(100));
248 q.set((key("c"), Timer::E), now, Duration::from_millis(300));
249
250 let fired = q.take_due(now + Duration::from_millis(600));
251 let order: Vec<Timer> = fired.iter().map(|(_, timer)| *timer).collect();
252 assert_eq!(order, vec![Timer::B, Timer::E, Timer::A]);
253 }
254
255 #[tokio::test]
259 async fn scheduling_and_firing_need_no_real_time_to_pass() {
260 let mut q = Transactions::new();
261 let epoch = Instant::now();
262 q.set((key("a"), Timer::A), epoch, Duration::from_secs(3600));
263
264 assert!(q.take_due(epoch).is_empty(), "not due yet");
265 assert_eq!(
266 q.take_due(epoch + Duration::from_secs(3600)).len(),
267 1,
268 "an hour later, without an hour passing"
269 );
270 }
271
272 #[tokio::test(start_paused = true)]
273 async fn a_cleared_timer_does_not_fire() {
274 let mut q = Transactions::new();
275 let now = Instant::now();
276 q.set((key("a"), Timer::A), now, Duration::from_millis(100));
277 q.clear(&(key("a"), Timer::A));
278
279 assert!(q.take_due(now + Duration::from_millis(200)).is_empty());
280 }
281
282 #[tokio::test(start_paused = true)]
283 async fn forgetting_one_timer_discards_its_generation_without_scanning_others() {
284 let mut q = Transactions::new();
285 let now = Instant::now();
286 let forgotten = (key("a"), Timer::A);
287 let live = (key("z"), Timer::B);
288 q.set(forgotten.clone(), now, Duration::from_millis(100));
289 q.set(live.clone(), now, Duration::from_millis(200));
290
291 q.forget(&forgotten);
292
293 assert!(!q.generations.contains_key(&forgotten));
294 assert!(q.generations.contains_key(&live));
295 assert_eq!(q.take_due(now + Duration::from_millis(300)), vec![live]);
296 }
297
298 #[tokio::test(start_paused = true)]
301 async fn reusing_a_forgotten_key_does_not_revive_its_stale_timer() {
302 let mut q = Transactions::new();
303 let now = Instant::now();
304 let reused = (key("a"), Timer::A);
305 q.set(reused.clone(), now, Duration::from_millis(100));
306 q.forget(&reused);
307
308 q.set(reused.clone(), now, Duration::from_millis(200));
309
310 assert!(q.take_due(now + Duration::from_millis(100)).is_empty());
311 assert_eq!(q.take_due(now + Duration::from_millis(200)), vec![reused]);
312 }
313
314 #[tokio::test(start_paused = true)]
317 async fn resetting_a_timer_replaces_it() {
318 let mut q = Transactions::new();
319 let now = Instant::now();
320 q.set((key("a"), Timer::A), now, Duration::from_millis(100));
321 q.set((key("a"), Timer::A), now, Duration::from_millis(500));
322
323 assert!(
324 q.take_due(now + Duration::from_millis(200)).is_empty(),
325 "the first schedule must not survive"
326 );
327 assert_eq!(q.take_due(now + Duration::from_millis(600)).len(), 1);
328 }
329
330 #[tokio::test(start_paused = true)]
331 async fn clearing_a_transaction_clears_all_of_its_timers() {
332 let mut q = Transactions::new();
333 let now = Instant::now();
334 q.set((key("a"), Timer::A), now, Duration::from_millis(100));
335 q.set((key("a"), Timer::B), now, Duration::from_millis(200));
336 q.set((key("z"), Timer::A), now, Duration::from_millis(100));
337 q.clear_matching(|(k, _)| k == &key("a"));
338
339 let fired = q.take_due(now + Duration::from_millis(300));
340 assert_eq!(fired.len(), 1);
341 assert_eq!(fired[0].0, key("z"));
342 }
343
344 #[tokio::test(start_paused = true)]
345 async fn cancelled_entries_do_not_keep_waking_the_loop() {
346 let mut q = Transactions::new();
347 let now = Instant::now();
348 for i in 0..100 {
349 q.set(
350 (key(&format!("k{i}")), Timer::A),
351 now,
352 Duration::from_millis(10),
353 );
354 q.clear(&(key(&format!("k{i}")), Timer::A));
355 }
356 q.set((key("live"), Timer::A), now, Duration::from_secs(60));
357
358 let deadline = q.next_deadline().expect("a deadline");
360 assert!(deadline >= now + Duration::from_secs(59));
361 assert_eq!(q.len(), 1, "stale entries are discarded while looking");
362 }
363
364 #[test]
374 fn a_virtual_clock_drives_the_queue_with_no_runtime() {
375 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
377 struct Virtual(u64);
378
379 impl std::ops::Add<Duration> for Virtual {
380 type Output = Self;
381 fn add(self, after: Duration) -> Self {
382 Self(
385 self.0
386 .saturating_add(u64::try_from(after.as_millis()).unwrap_or(u64::MAX)),
387 )
388 }
389 }
390
391 let mut q: TimerQueue<&'static str, Virtual> = TimerQueue::new();
392 let epoch = Virtual(0);
393
394 q.set("retransmit", epoch, Duration::from_millis(500));
395 q.set("give-up", epoch, Duration::from_secs(32));
396
397 assert!(q.take_due(epoch).is_empty(), "nothing is due at the epoch");
398 assert_eq!(
399 q.next_deadline(),
400 Some(Virtual(500)),
401 "the queue answers in the caller's own units"
402 );
403 assert_eq!(q.take_due(Virtual(500)), vec!["retransmit"]);
404 assert_eq!(q.take_due(Virtual(31_999)), Vec::<&str>::new());
405 assert_eq!(q.take_due(Virtual(32_000)), vec!["give-up"]);
406 }
407
408 #[tokio::test(start_paused = true)]
411 async fn naming_the_queue_without_an_instant_still_means_the_tokio_one() {
412 let mut q: TimerQueue<(TransactionKey, Timer)> = TimerQueue::new();
413 let now: Instant = Instant::now();
414 q.set((key("a"), Timer::A), now, Duration::from_millis(100));
415 assert_eq!(q.next_deadline(), Some(now + Duration::from_millis(100)));
416 }
417
418 #[tokio::test(start_paused = true)]
420 async fn the_queue_schedules_any_key_at_all() {
421 let mut q: TimerQueue<&'static str> = TimerQueue::new();
422 let now = Instant::now();
423 q.set("refresh", now, Duration::from_millis(50));
424 q.set("keepalive", now, Duration::from_millis(10));
425 assert_eq!(
426 q.take_due(now + Duration::from_millis(100)),
427 vec!["keepalive", "refresh"]
428 );
429 }
430}