1use std::time::{Duration, Instant};
2
3use crate::{
4 PollError, Timer, TimerHandle,
5 gear::{Gear, InsertError, NUM_SLOTS, SLOT_MASK},
6};
7
8pub const DEFAULT_GEARS: usize = 5;
9pub const DEFAULT_RESOLUTION_MS: u64 = 5;
10pub const DEFAULT_SLOT_CAP: usize = 32;
11pub const DEFAULT_MAX_PROBES: usize = 3;
12
13pub struct BitWheel<
14 T,
15 const NUM_GEARS: usize = DEFAULT_GEARS,
16 const RESOLUTION_MS: u64 = DEFAULT_RESOLUTION_MS,
17 const SLOT_CAP: usize = DEFAULT_SLOT_CAP,
18 const MAX_PROBES: usize = DEFAULT_MAX_PROBES,
19> {
20 gears: [Gear<T, SLOT_CAP>; NUM_GEARS],
21 epoch: Instant,
22 current_tick: u64,
23 next_fire_tick: Option<u64>,
24
25 gear_next_fire: [Option<u64>; NUM_GEARS],
27 gear_dirty: u64,
28}
29
30impl<
31 T,
32 const NUM_GEARS: usize,
33 const RESOLUTION_MS: u64,
34 const SLOT_CAP: usize,
35 const MAX_PROBES: usize,
36> Default for BitWheel<T, NUM_GEARS, RESOLUTION_MS, SLOT_CAP, MAX_PROBES>
37{
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl<
44 T,
45 const NUM_GEARS: usize,
46 const RESOLUTION_MS: u64,
47 const SLOT_CAP: usize,
48 const MAX_PROBES: usize,
49> BitWheel<T, NUM_GEARS, RESOLUTION_MS, SLOT_CAP, MAX_PROBES>
50{
51 pub fn with_epoch(epoch: Instant) -> Self {
52 const {
53 assert!(NUM_GEARS >= 1, "must have at least one gear");
54 assert!(NUM_GEARS <= 64, "cannot have more than 64 gears");
55 assert!(RESOLUTION_MS >= 1, "resolution must be at least 1ms");
56 assert!(
57 6 * NUM_GEARS + (64 - RESOLUTION_MS.leading_zeros() as usize) <= 64,
58 "configuration would overflow u64 - reduce NUM_GEARS or RESOLUTION_MS"
59 );
60 assert!(MAX_PROBES < 64, "max probes must be less than 64");
61 }
62
63 Self {
64 gears: std::array::from_fn(|_| Gear::new()),
65 epoch,
66 current_tick: 0,
67 next_fire_tick: None,
68 gear_next_fire: [None; NUM_GEARS],
69 gear_dirty: 0,
70 }
71 }
72
73 pub fn new() -> Self {
74 Self::with_epoch(Instant::now())
75 }
76
77 pub fn boxed() -> Box<Self> {
78 Box::new(Self::new())
79 }
80
81 pub fn boxed_with_epoch(epoch: Instant) -> Box<Self> {
82 Box::new(Self::with_epoch(epoch))
83 }
84
85 pub const fn gear_granularities() -> [u64; NUM_GEARS] {
89 let mut granularities = [0; NUM_GEARS];
90 granularities[0] = RESOLUTION_MS;
91
92 let mut idx = 1;
93 while idx < NUM_GEARS {
94 granularities[idx] = granularities[idx - 1] * (NUM_SLOTS as u64);
95 idx += 1;
96 }
97
98 granularities
99 }
100
101 pub const fn memory_footprint() -> usize {
105 let struct_size = std::mem::size_of::<Self>();
107
108 const ENTRY_OVERHEAD: usize = 24;
112 let entry_size = std::mem::size_of::<T>() + ENTRY_OVERHEAD;
113 let heap_per_slot = SLOT_CAP * entry_size;
114 let total_heap = NUM_GEARS * NUM_SLOTS * heap_per_slot;
115
116 struct_size + total_heap
117 }
118
119 #[inline(always)]
120 pub fn duration_until_next(&self) -> Option<Duration> {
121 self.next_fire_tick.map(|next| {
122 let ticks_remaining = next.saturating_sub(self.current_tick);
123 Duration::from_millis(ticks_remaining * RESOLUTION_MS)
124 })
125 }
126
127 #[inline(always)]
128 pub fn is_empty(&self) -> bool {
129 self.next_fire_tick.is_none()
130 }
131
132 pub fn insert(&mut self, when: Instant, timer: T) -> Result<TimerHandle, InsertError<T>> {
133 let when_tick = self.instant_to_tick(when);
134 let delay = when_tick.saturating_sub(self.current_tick).max(1);
135
136 let gear_idx = self.gear_for_delay(delay);
137 let target_slot = self.slot_for_tick(gear_idx, when_tick);
138 let Ok(guard) = self.gears[gear_idx].acquire_next_available(target_slot, MAX_PROBES) else {
139 return Err(InsertError(timer));
140 };
141
142 let actual_slot = guard.slot();
143 let key = guard.insert(timer);
144
145 let fire_tick = self.compute_fire_tick(gear_idx, actual_slot);
147 self.gear_next_fire[gear_idx] =
148 Some(self.gear_next_fire[gear_idx].map_or(fire_tick, |t| t.min(fire_tick)));
149 self.next_fire_tick = Some(self.next_fire_tick.map_or(fire_tick, |t| t.min(fire_tick)));
150
151 Ok(TimerHandle {
152 when_offset: when_tick,
153 key: key as u32,
154 gear: gear_idx as u8,
155 slot: actual_slot as u8,
156 overflow: false,
157 })
158 }
159
160 pub fn cancel(&mut self, handle: TimerHandle) -> Option<T> {
161 debug_assert!(!handle.overflow, "received unexpected handle for overflow");
162 if handle.when_offset <= self.current_tick {
163 return None;
164 }
165
166 let gear_idx = handle.gear as usize;
167 let slot = handle.slot as usize;
168 let key = handle.key as usize;
169
170 if gear_idx >= NUM_GEARS {
171 return None;
172 }
173
174 let guard = self.gears[gear_idx].acquire(slot);
187 let timer = guard.remove(key);
188
189 self.gear_dirty |= 1 << gear_idx;
191
192 Some(timer)
193 }
194
195 pub fn poll(&mut self, now: Instant, ctx: &mut T::Context) -> Result<usize, PollError>
196 where
197 T: Timer,
198 {
199 let mut lost = 0usize;
200 let fired = self.poll_with_failover(now, ctx, |_, _| {
201 lost += 1;
202 });
203
204 if lost > 0 {
205 Err(PollError(lost))
206 } else {
207 Ok(fired)
208 }
209 }
210
211 #[inline(always)]
212 pub(crate) fn poll_with_failover(
213 &mut self,
214 now: Instant,
215 ctx: &mut T::Context,
216 mut failover: impl FnMut(u64, T),
217 ) -> usize
218 where
219 T: Timer,
220 {
221 let target_tick = self.instant_to_tick(now);
222
223 if target_tick <= self.current_tick {
224 return 0;
225 }
226
227 if self.is_empty() {
229 self.current_tick = target_tick;
230 return 0;
231 }
232
233 let mut fired = 0usize;
234
235 match self.next_fire_tick {
237 None => {
238 self.current_tick = target_tick;
239 return 0;
240 }
241 Some(nft) if nft > target_tick => {
242 self.current_tick = target_tick;
243 return 0;
244 }
245 Some(nft) => {
246 if nft > self.current_tick + 1 {
247 self.current_tick = nft - 1;
248 }
249 }
250 }
251
252 for tick in (self.current_tick + 1)..=target_tick {
253 self.poll_tick(tick, now, ctx, &mut fired, |when, timer| {
254 failover(when, timer)
255 });
256 }
257
258 self.current_tick = target_tick;
259 self.recompute_next_fire();
260 fired
261 }
262
263 #[inline(always)]
264 fn poll_tick(
265 &mut self,
266 tick: u64,
267 now: Instant,
268 ctx: &mut T::Context,
269 fired: &mut usize,
270 mut failover: impl FnMut(u64, T),
271 ) where
272 T: Timer,
273 {
274 for gear_idx in 0..NUM_GEARS {
275 if gear_idx > 0 {
276 let mask = (1u64 << (6 * gear_idx)) - 1;
277 if (tick & mask) != 0 {
278 continue;
279 }
280 }
281
282 let slot = self.slot_for_tick(gear_idx, tick);
283 self.drain_and_fire(gear_idx, slot, now, ctx, fired, |when, timer| {
284 failover(when, timer)
285 });
286 }
287 }
288
289 #[inline(always)]
290 fn drain_and_fire(
291 &mut self,
292 gear_idx: usize,
293 slot: usize,
294 now: Instant,
295 ctx: &mut T::Context,
296 fired: &mut usize,
297 mut failover: impl FnMut(u64, T),
298 ) where
299 T: Timer,
300 {
301 loop {
302 let mut timer = {
303 let guard = self.gears[gear_idx].acquire(slot);
304 match guard.pop() {
305 Some(t) => t,
306 None => break,
307 }
308 };
309
310 self.gear_dirty |= 1 << gear_idx;
312
313 *fired += 1;
314
315 if let Some(next_when) = timer.fire(now, ctx) {
316 let when_tick = self.instant_to_tick(next_when);
317 if let Err(InsertError(timer)) =
318 self.insert_excluding(when_tick, timer, gear_idx, slot)
319 {
320 failover(when_tick, timer);
321 }
322 }
323 }
324 }
325
326 #[inline(always)]
327 fn insert_excluding(
328 &mut self,
329 when_tick: u64,
330 timer: T,
331 excluded_gear: usize,
332 excluded_slot: usize,
333 ) -> Result<TimerHandle, InsertError<T>> {
334 let delay = when_tick.saturating_sub(self.current_tick).max(1);
335
336 let gear_idx = self.gear_for_delay(delay);
337 let target_slot = self.slot_for_tick(gear_idx, when_tick);
338
339 let result = if gear_idx == excluded_gear {
340 self.gears[gear_idx].acquire_next_available_excluding(
341 excluded_slot,
342 target_slot,
343 MAX_PROBES,
344 )
345 } else {
346 self.gears[gear_idx].acquire_next_available(target_slot, MAX_PROBES)
347 };
348
349 let Ok(guard) = result else {
350 return Err(InsertError(timer));
351 };
352
353 let actual_slot = guard.slot();
354 let key = guard.insert(timer);
355
356 let fire_tick = self.compute_fire_tick(gear_idx, actual_slot);
358 self.gear_next_fire[gear_idx] =
359 Some(self.gear_next_fire[gear_idx].map_or(fire_tick, |t| t.min(fire_tick)));
360 self.next_fire_tick = Some(self.next_fire_tick.map_or(fire_tick, |t| t.min(fire_tick)));
361
362 Ok(TimerHandle {
363 when_offset: when_tick,
364 key: key as u32,
365 gear: gear_idx as u8,
366 slot: actual_slot as u8,
367 overflow: false,
368 })
369 }
370
371 #[inline(always)]
372 fn recompute_next_fire(&mut self) {
373 while self.gear_dirty != 0 {
375 let gear_idx = self.gear_dirty.trailing_zeros() as usize;
376 self.gear_dirty &= self.gear_dirty - 1;
377
378 self.gear_next_fire[gear_idx] = self.compute_gear_min_fire(gear_idx);
379 }
380
381 self.next_fire_tick = None;
383 for &cached in &self.gear_next_fire {
384 if let Some(tick) = cached {
385 self.next_fire_tick = Some(self.next_fire_tick.map_or(tick, |t| t.min(tick)));
386 }
387 }
388 }
389
390 #[inline(always)]
391 fn compute_gear_min_fire(&self, gear_idx: usize) -> Option<u64> {
392 let occupied = self.gears[gear_idx].occupied_bitmap();
393 if occupied == 0 {
394 return None;
395 }
396
397 let current_slot = self.slot_for_tick(gear_idx, self.current_tick);
398
399 let rotation = (current_slot as u32 + 1) & (SLOT_MASK as u32);
401 let rotated = occupied.rotate_right(rotation);
402
403 let distance = rotated.trailing_zeros() as usize;
404 let next_slot = (current_slot + 1 + distance) & SLOT_MASK;
405
406 Some(self.compute_fire_tick(gear_idx, next_slot))
407 }
408
409 #[inline(always)]
410 fn compute_fire_tick(&self, gear_idx: usize, slot: usize) -> u64 {
411 let shift = gear_idx * 6;
412 let gear_period = 1u64 << (shift + 6);
413 let slot_fire_offset = (slot as u64) << shift;
414
415 let current_in_period = self.current_tick & (gear_period - 1);
416
417 if slot_fire_offset > current_in_period {
418 (self.current_tick & !(gear_period - 1)) + slot_fire_offset
419 } else {
420 (self.current_tick & !(gear_period - 1)) + gear_period + slot_fire_offset
421 }
422 }
423
424 #[inline(always)]
425 pub(crate) fn instant_to_tick(&self, when: Instant) -> u64 {
426 when.saturating_duration_since(self.epoch).as_millis() as u64 / RESOLUTION_MS
427 }
428
429 #[inline(always)]
430 pub(crate) fn current_tick(&self) -> u64 {
431 self.current_tick
432 }
433
434 #[inline(always)]
435 fn gear_for_delay(&self, delay: u64) -> usize {
436 if delay == 0 {
437 return 0;
438 }
439 let gear = (63 - delay.leading_zeros()) as usize / 6;
440 gear.min(NUM_GEARS - 1)
441 }
442
443 #[inline(always)]
444 fn slot_for_tick(&self, gear: usize, tick: u64) -> usize {
445 let shift = gear * 6;
446 ((tick >> shift) & 63) as usize
447 }
448}
449
450#[macro_export]
451macro_rules! define_bitwheel {
452 ($name:ident, $timer:ty, $num_gears:expr, $resolution_ms:expr, $slot_cap:expr, $max_probes:expr) => {
453 pub type $name =
454 $crate::BitWheel<$timer, $num_gears, $resolution_ms, $slot_cap, $max_probes>;
455 };
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461 use std::cell::Cell;
462 use std::rc::Rc;
463
464 struct OneShotTimer {
468 id: usize,
469 fired: Rc<Cell<bool>>,
470 }
471
472 impl OneShotTimer {
473 fn new(id: usize) -> (Self, Rc<Cell<bool>>) {
474 let fired = Rc::new(Cell::new(false));
475 (
476 Self {
477 id,
478 fired: Rc::clone(&fired),
479 },
480 fired,
481 )
482 }
483 }
484
485 impl Timer for OneShotTimer {
486 type Context = Vec<usize>;
487
488 fn fire(&mut self, _now: Instant, ctx: &mut Self::Context) -> Option<Instant> {
489 self.fired.set(true);
490 ctx.push(self.id);
491 None }
493 }
494
495 struct PeriodicTimer {
497 id: usize,
498 period: Duration,
499 max_fires: usize,
500 fire_count: usize,
501 }
502
503 impl PeriodicTimer {
504 fn new(id: usize, period: Duration, max_fires: usize) -> Self {
505 Self {
506 id,
507 period,
508 max_fires,
509 fire_count: 0,
510 }
511 }
512 }
513
514 impl Timer for PeriodicTimer {
515 type Context = Vec<(usize, usize)>; fn fire(&mut self, now: Instant, ctx: &mut Self::Context) -> Option<Instant> {
518 self.fire_count += 1;
519 ctx.push((self.id, self.fire_count));
520
521 if self.fire_count < self.max_fires {
522 Some(now + self.period)
523 } else {
524 None
525 }
526 }
527 }
528
529 struct CounterTimer;
531
532 impl Timer for CounterTimer {
533 type Context = usize;
534
535 fn fire(&mut self, _now: Instant, ctx: &mut Self::Context) -> Option<Instant> {
536 *ctx += 1;
537 None
538 }
539 }
540
541 #[test]
544 fn test_new() {
545 let wheel: Box<BitWheel<OneShotTimer>> = BitWheel::boxed();
546 assert!(wheel.is_empty());
547 assert!(wheel.duration_until_next().is_none());
548 }
549
550 #[test]
551 fn test_with_epoch() {
552 let epoch = Instant::now();
553 let wheel: Box<BitWheel<OneShotTimer>> = BitWheel::boxed_with_epoch(epoch);
554 assert!(wheel.is_empty());
555 assert_eq!(wheel.current_tick, 0);
556 }
557
558 #[test]
559 fn test_default() {
560 let wheel: Box<BitWheel<OneShotTimer>> = BitWheel::boxed();
561 assert!(wheel.is_empty());
562 }
563
564 #[test]
565 fn test_custom_config() {
566 let wheel: Box<BitWheel<OneShotTimer, 4, 10, 16, 5>> = BitWheel::boxed();
568 assert!(wheel.is_empty());
569 }
570
571 #[test]
574 fn test_insert_single() {
575 let epoch = Instant::now();
576 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
577
578 let (timer, _fired) = OneShotTimer::new(1);
579 let when = epoch + Duration::from_millis(100);
580
581 let handle = wheel.insert(when, timer).unwrap();
582 assert!(!wheel.is_empty());
583 assert!(wheel.duration_until_next().is_some());
584 assert_eq!(handle.gear, 1); }
586
587 #[test]
588 fn test_insert_updates_next_fire() {
589 let epoch = Instant::now();
590 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
591
592 assert!(wheel.duration_until_next().is_none());
593
594 let (timer, _) = OneShotTimer::new(1);
595 wheel
596 .insert(epoch + Duration::from_millis(50), timer)
597 .unwrap();
598
599 let duration = wheel.duration_until_next().unwrap();
600 assert!(duration.as_millis() <= 50);
601 }
602
603 #[test]
604 fn test_insert_multiple_updates_next_fire_to_earliest() {
605 let epoch = Instant::now();
606 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
607
608 let (timer1, _) = OneShotTimer::new(1);
609 let (timer2, _) = OneShotTimer::new(2);
610
611 wheel
613 .insert(epoch + Duration::from_millis(100), timer1)
614 .unwrap();
615 let d1 = wheel.duration_until_next().unwrap();
616
617 wheel
619 .insert(epoch + Duration::from_millis(30), timer2)
620 .unwrap();
621 let d2 = wheel.duration_until_next().unwrap();
622
623 assert!(d2 < d1);
624 assert!(d2.as_millis() <= 30);
625 }
626
627 #[test]
628 fn test_insert_gear_selection_gear0() {
629 let epoch = Instant::now();
630 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
631
632 let (timer, _) = OneShotTimer::new(1);
634 let handle = wheel
635 .insert(epoch + Duration::from_millis(30), timer)
636 .unwrap();
637 assert_eq!(handle.gear, 0);
638 }
639
640 #[test]
641 fn test_insert_gear_selection_gear1() {
642 let epoch = Instant::now();
643 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
644
645 let (timer, _) = OneShotTimer::new(1);
647 let handle = wheel
648 .insert(epoch + Duration::from_millis(100), timer)
649 .unwrap();
650 assert_eq!(handle.gear, 1);
651
652 let (timer2, _) = OneShotTimer::new(2);
653 let handle2 = wheel
654 .insert(epoch + Duration::from_millis(4000), timer2)
655 .unwrap();
656 assert_eq!(handle2.gear, 1);
657 }
658
659 #[test]
660 fn test_insert_gear_selection_gear2() {
661 let epoch = Instant::now();
662 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
663
664 let (timer, _) = OneShotTimer::new(1);
666 let handle = wheel
667 .insert(epoch + Duration::from_millis(5000), timer)
668 .unwrap();
669 assert_eq!(handle.gear, 2);
670 }
671
672 #[test]
673 fn test_insert_with_probing() {
674 let epoch = Instant::now();
675 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 2, 10>> = BitWheel::boxed_with_epoch(epoch);
677
678 let when = epoch + Duration::from_millis(10);
679
680 let (t1, _) = OneShotTimer::new(1);
682 let (t2, _) = OneShotTimer::new(2);
683 let h1 = wheel.insert(when, t1).unwrap();
684 let h2 = wheel.insert(when, t2).unwrap();
685
686 assert_eq!(h1.slot, h2.slot);
688
689 let (t3, _) = OneShotTimer::new(3);
691 let h3 = wheel.insert(when, t3).unwrap();
692 assert_ne!(h2.slot, h3.slot);
693 }
694
695 #[test]
696 fn test_insert_slot_full_error() {
697 let epoch = Instant::now();
698 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 1, 1>> = BitWheel::boxed_with_epoch(epoch);
700
701 let when = epoch + Duration::from_millis(10);
702
703 let (t1, _) = OneShotTimer::new(1);
704 wheel.insert(when, t1).unwrap();
705
706 let (t2, _) = OneShotTimer::new(2);
708 let result = wheel.insert(when, t2);
709 assert!(result.is_err());
710 }
711
712 #[test]
713 fn test_insert_past_timer() {
714 let epoch = Instant::now();
715 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
716
717 let mut ctx = Vec::new();
719 let _ = wheel.poll(epoch + Duration::from_millis(100), &mut ctx);
720
721 let (timer, _) = OneShotTimer::new(1);
723 let handle = wheel
724 .insert(epoch + Duration::from_millis(50), timer)
725 .unwrap();
726
727 assert_eq!(handle.gear, 0);
729 }
730
731 #[test]
734 fn test_cancel_returns_timer() {
735 let epoch = Instant::now();
736 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
737
738 let (timer, _) = OneShotTimer::new(42);
739 let handle = wheel
740 .insert(epoch + Duration::from_millis(100), timer)
741 .unwrap();
742
743 let cancelled = wheel.cancel(handle);
744 assert!(cancelled.is_some());
745 assert_eq!(cancelled.unwrap().id, 42);
746 }
747
748 #[test]
749 fn test_cancel_after_poll_returns_none() {
750 let epoch = Instant::now();
751 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
752
753 let (timer, _) = OneShotTimer::new(1);
754 let handle = wheel
755 .insert(epoch + Duration::from_millis(10), timer)
756 .unwrap();
757
758 let mut ctx = Vec::new();
760 wheel
761 .poll(epoch + Duration::from_millis(100), &mut ctx)
762 .unwrap();
763
764 let cancelled = wheel.cancel(handle);
766 assert!(cancelled.is_none());
767 }
768
769 #[test]
772 fn test_poll_no_timers() {
773 let epoch = Instant::now();
774 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
775
776 let mut ctx = Vec::new();
777 let result = wheel.poll(epoch + Duration::from_millis(100), &mut ctx);
778
779 assert_eq!(result.unwrap(), 0);
780 assert!(ctx.is_empty());
781 }
782
783 #[test]
784 fn test_poll_before_deadline() {
785 let epoch = Instant::now();
786 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
787
788 let (timer, fired) = OneShotTimer::new(1);
789 wheel
790 .insert(epoch + Duration::from_millis(100), timer)
791 .unwrap();
792
793 let mut ctx = Vec::new();
794 let result = wheel.poll(epoch + Duration::from_millis(50), &mut ctx);
795
796 assert_eq!(result.unwrap(), 0);
797 assert!(!fired.get());
798 assert!(ctx.is_empty());
799 }
800
801 #[test]
802 fn test_poll_at_deadline() {
803 let epoch = Instant::now();
804 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
805
806 let (timer, fired) = OneShotTimer::new(1);
807 wheel
808 .insert(epoch + Duration::from_millis(10), timer)
809 .unwrap();
810
811 let mut ctx = Vec::new();
812 let result = wheel.poll(epoch + Duration::from_millis(10), &mut ctx);
813
814 assert_eq!(result.unwrap(), 1);
815 assert!(fired.get());
816 assert_eq!(ctx, vec![1]);
817 }
818
819 #[test]
820 fn test_poll_after_deadline() {
821 let epoch = Instant::now();
822 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
823
824 let (timer, fired) = OneShotTimer::new(1);
825 wheel
826 .insert(epoch + Duration::from_millis(10), timer)
827 .unwrap();
828
829 let mut ctx = Vec::new();
830 let result = wheel.poll(epoch + Duration::from_millis(100), &mut ctx);
831
832 assert_eq!(result.unwrap(), 1);
833 assert!(fired.get());
834 assert_eq!(ctx, vec![1]);
835 }
836
837 #[test]
838 fn test_poll_multiple_timers_same_slot() {
839 let epoch = Instant::now();
840 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
841
842 let when = epoch + Duration::from_millis(10);
843 let (t1, f1) = OneShotTimer::new(1);
844 let (t2, f2) = OneShotTimer::new(2);
845 let (t3, f3) = OneShotTimer::new(3);
846
847 wheel.insert(when, t1).unwrap();
848 wheel.insert(when, t2).unwrap();
849 wheel.insert(when, t3).unwrap();
850
851 let mut ctx = Vec::new();
852 let result = wheel.poll(epoch + Duration::from_millis(20), &mut ctx);
853
854 assert_eq!(result.unwrap(), 3);
855 assert!(f1.get());
856 assert!(f2.get());
857 assert!(f3.get());
858 assert_eq!(ctx.len(), 3);
859 }
860
861 #[test]
862 fn test_poll_multiple_timers_different_times() {
863 let epoch = Instant::now();
864 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
865
866 let (t1, f1) = OneShotTimer::new(1);
867 let (t2, f2) = OneShotTimer::new(2);
868 let (t3, f3) = OneShotTimer::new(3);
869
870 wheel.insert(epoch + Duration::from_millis(10), t1).unwrap();
871 wheel.insert(epoch + Duration::from_millis(20), t2).unwrap();
872 wheel.insert(epoch + Duration::from_millis(30), t3).unwrap();
873
874 let mut ctx = Vec::new();
876 wheel
877 .poll(epoch + Duration::from_millis(15), &mut ctx)
878 .unwrap();
879 assert!(f1.get());
880 assert!(!f2.get());
881 assert!(!f3.get());
882 assert_eq!(ctx, vec![1]);
883
884 ctx.clear();
886 wheel
887 .poll(epoch + Duration::from_millis(25), &mut ctx)
888 .unwrap();
889 assert!(f2.get());
890 assert!(!f3.get());
891 assert_eq!(ctx, vec![2]);
892
893 ctx.clear();
895 wheel
896 .poll(epoch + Duration::from_millis(35), &mut ctx)
897 .unwrap();
898 assert!(f3.get());
899 assert_eq!(ctx, vec![3]);
900 }
901
902 #[test]
903 fn test_poll_clears_is_empty() {
904 let epoch = Instant::now();
905 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
906
907 let (timer, _) = OneShotTimer::new(1);
908 wheel
909 .insert(epoch + Duration::from_millis(10), timer)
910 .unwrap();
911 assert!(!wheel.is_empty());
912
913 let mut ctx = Vec::new();
914 wheel
915 .poll(epoch + Duration::from_millis(100), &mut ctx)
916 .unwrap();
917 assert!(wheel.is_empty());
918 }
919
920 #[test]
921 fn test_poll_updates_duration_until_next() {
922 let epoch = Instant::now();
923 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
924
925 let (t1, _) = OneShotTimer::new(1);
926 let (t2, _) = OneShotTimer::new(2);
927
928 wheel.insert(epoch + Duration::from_millis(10), t1).unwrap();
929 wheel.insert(epoch + Duration::from_millis(50), t2).unwrap();
930
931 let d1 = wheel.duration_until_next().unwrap();
933 assert!(d1.as_millis() <= 10);
934
935 let mut ctx = Vec::new();
937 wheel
938 .poll(epoch + Duration::from_millis(20), &mut ctx)
939 .unwrap();
940
941 let d2 = wheel.duration_until_next().unwrap();
943 assert!(d2.as_millis() <= 30); }
945
946 #[test]
949 fn test_periodic_timer_reschedules() {
950 let epoch = Instant::now();
951 let mut wheel: Box<BitWheel<PeriodicTimer, 4, 1, 32, 3>> =
952 BitWheel::boxed_with_epoch(epoch);
953
954 let timer = PeriodicTimer::new(1, Duration::from_millis(10), 3);
955 wheel
956 .insert(epoch + Duration::from_millis(10), timer)
957 .unwrap();
958
959 let mut ctx = Vec::new();
960
961 wheel
963 .poll(epoch + Duration::from_millis(15), &mut ctx)
964 .unwrap();
965 assert_eq!(ctx, vec![(1, 1)]);
966
967 wheel
969 .poll(epoch + Duration::from_millis(30), &mut ctx)
970 .unwrap();
971 assert_eq!(ctx, vec![(1, 1), (1, 2)]);
972
973 wheel
975 .poll(epoch + Duration::from_millis(45), &mut ctx)
976 .unwrap();
977 assert_eq!(ctx, vec![(1, 1), (1, 2), (1, 3)]);
978
979 wheel
981 .poll(epoch + Duration::from_millis(100), &mut ctx)
982 .unwrap();
983 assert_eq!(ctx.len(), 3);
984 assert!(wheel.is_empty());
985 }
986
987 #[test]
988 fn test_periodic_timer_exclusion() {
989 let epoch = Instant::now();
990 let mut wheel: Box<BitWheel<PeriodicTimer, 4, 1, 2, 10>> =
992 BitWheel::boxed_with_epoch(epoch);
993
994 let timer = PeriodicTimer::new(1, Duration::from_millis(1), 5);
996 wheel
997 .insert(epoch + Duration::from_millis(1), timer)
998 .unwrap();
999
1000 let mut ctx = Vec::new();
1001
1002 let result = wheel.poll(epoch + Duration::from_millis(10), &mut ctx);
1004 assert!(result.is_ok());
1005 }
1006
1007 #[test]
1010 fn test_gear0_every_tick() {
1011 let epoch = Instant::now();
1012 let mut wheel: Box<BitWheel<CounterTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1013
1014 for i in 1..=5 {
1016 wheel
1017 .insert(epoch + Duration::from_millis(i), CounterTimer)
1018 .unwrap();
1019 }
1020
1021 let mut count = 0usize;
1022
1023 for i in 1..=5 {
1025 wheel
1026 .poll(epoch + Duration::from_millis(i), &mut count)
1027 .unwrap();
1028 }
1029
1030 assert_eq!(count, 5);
1031 }
1032
1033 #[test]
1034 fn test_gear1_rotation() {
1035 let epoch = Instant::now();
1036 let mut wheel: Box<BitWheel<CounterTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1037
1038 wheel
1040 .insert(epoch + Duration::from_millis(100), CounterTimer)
1041 .unwrap();
1042
1043 let mut count = 0usize;
1044
1045 wheel
1047 .poll(epoch + Duration::from_millis(100), &mut count)
1048 .unwrap();
1049 assert_eq!(count, 1);
1050 }
1051
1052 #[test]
1053 fn test_higher_gear_precision() {
1054 let epoch = Instant::now();
1055 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1056
1057 let (timer, fired) = OneShotTimer::new(1);
1059 let handle = wheel
1060 .insert(epoch + Duration::from_millis(5000), timer)
1061 .unwrap();
1062 assert_eq!(handle.gear, 2);
1063
1064 let mut ctx = Vec::new();
1065
1066 wheel
1068 .poll(epoch + Duration::from_millis(4000), &mut ctx)
1069 .unwrap();
1070 assert!(!fired.get());
1071
1072 wheel
1074 .poll(epoch + Duration::from_millis(5100), &mut ctx)
1075 .unwrap();
1076 assert!(fired.get());
1077 }
1078
1079 #[test]
1082 fn test_poll_same_instant_twice() {
1083 let epoch = Instant::now();
1084 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1085
1086 let (timer, _) = OneShotTimer::new(1);
1087 wheel
1088 .insert(epoch + Duration::from_millis(10), timer)
1089 .unwrap();
1090
1091 let mut ctx = Vec::new();
1092 let now = epoch + Duration::from_millis(20);
1093
1094 let r1 = wheel.poll(now, &mut ctx).unwrap();
1096 assert_eq!(r1, 1);
1097
1098 let r2 = wheel.poll(now, &mut ctx).unwrap();
1100 assert_eq!(r2, 0);
1101 }
1102
1103 #[test]
1104 fn test_poll_backwards_in_time() {
1105 let epoch = Instant::now();
1106 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1107
1108 let (timer, fired) = OneShotTimer::new(1);
1109 wheel
1110 .insert(epoch + Duration::from_millis(50), timer)
1111 .unwrap();
1112
1113 let mut ctx = Vec::new();
1114
1115 wheel
1117 .poll(epoch + Duration::from_millis(100), &mut ctx)
1118 .unwrap();
1119 assert!(fired.get());
1120
1121 let r = wheel
1123 .poll(epoch + Duration::from_millis(30), &mut ctx)
1124 .unwrap();
1125 assert_eq!(r, 0);
1126 }
1127
1128 #[test]
1129 fn test_many_timers_stress() {
1130 let epoch = Instant::now();
1131 let mut wheel: Box<BitWheel<CounterTimer, 4, 1, 64, 10>> =
1132 BitWheel::boxed_with_epoch(epoch);
1133
1134 for i in 0..1000 {
1136 let delay = (i % 500) + 1;
1137 wheel
1138 .insert(epoch + Duration::from_millis(delay as u64), CounterTimer)
1139 .unwrap();
1140 }
1141
1142 let mut count = 0usize;
1143 wheel
1144 .poll(epoch + Duration::from_millis(1000), &mut count)
1145 .unwrap();
1146
1147 assert_eq!(count, 1000);
1148 assert!(wheel.is_empty());
1149 }
1150
1151 #[test]
1154 fn test_duration_until_next_empty() {
1155 let wheel: Box<BitWheel<OneShotTimer>> = BitWheel::boxed();
1156 assert!(wheel.duration_until_next().is_none());
1157 }
1158
1159 #[test]
1160 fn test_duration_until_next_after_cancel() {
1161 let epoch = Instant::now();
1162 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1163
1164 let (t1, _) = OneShotTimer::new(1);
1165 let (t2, _) = OneShotTimer::new(2);
1166
1167 wheel.insert(epoch + Duration::from_millis(50), t1).unwrap();
1168 let h2 = wheel.insert(epoch + Duration::from_millis(10), t2).unwrap();
1169
1170 let d1 = wheel.duration_until_next().unwrap();
1172 assert!(d1.as_millis() <= 10);
1173
1174 wheel.cancel(h2);
1176
1177 }
1180
1181 #[test]
1184 fn test_gear_for_delay_boundaries() {
1185 let epoch = Instant::now();
1186 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1187
1188 assert_eq!(wheel.gear_for_delay(1), 0);
1190 assert_eq!(wheel.gear_for_delay(63), 0);
1191
1192 assert_eq!(wheel.gear_for_delay(64), 1);
1194 assert_eq!(wheel.gear_for_delay(4095), 1);
1195
1196 assert_eq!(wheel.gear_for_delay(4096), 2);
1198 assert_eq!(wheel.gear_for_delay(262143), 2);
1199
1200 assert_eq!(wheel.gear_for_delay(262144), 3);
1202 }
1203
1204 #[test]
1205 fn test_slot_for_tick() {
1206 let epoch = Instant::now();
1207 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1208
1209 assert_eq!(wheel.slot_for_tick(0, 0), 0);
1211 assert_eq!(wheel.slot_for_tick(0, 10), 10);
1212 assert_eq!(wheel.slot_for_tick(0, 63), 63);
1213 assert_eq!(wheel.slot_for_tick(0, 64), 0); assert_eq!(wheel.slot_for_tick(1, 64), 1);
1217 assert_eq!(wheel.slot_for_tick(1, 128), 2);
1218 assert_eq!(wheel.slot_for_tick(1, 4032), 63);
1219
1220 assert_eq!(wheel.slot_for_tick(2, 4096), 1);
1222 assert_eq!(wheel.slot_for_tick(2, 8192), 2);
1223 }
1224
1225 #[test]
1228 fn test_drop_pending_timers() {
1229 use std::sync::Arc;
1230 use std::sync::atomic::{AtomicUsize, Ordering};
1231
1232 struct DropCounter(Arc<AtomicUsize>);
1233
1234 impl Drop for DropCounter {
1235 fn drop(&mut self) {
1236 self.0.fetch_add(1, Ordering::SeqCst);
1237 }
1238 }
1239
1240 impl Timer for DropCounter {
1241 type Context = ();
1242 fn fire(&mut self, _now: Instant, _ctx: &mut ()) -> Option<Instant> {
1243 None
1244 }
1245 }
1246
1247 let drop_count = Arc::new(AtomicUsize::new(0));
1248 let epoch = Instant::now();
1249
1250 {
1251 let mut wheel: Box<BitWheel<DropCounter, 4, 1, 32, 3>> =
1252 BitWheel::boxed_with_epoch(epoch);
1253
1254 for i in 0..10 {
1255 wheel
1256 .insert(
1257 epoch + Duration::from_millis((i + 1) * 100),
1258 DropCounter(Arc::clone(&drop_count)),
1259 )
1260 .unwrap();
1261 }
1262
1263 assert_eq!(drop_count.load(Ordering::SeqCst), 0);
1264 }
1266
1267 assert_eq!(drop_count.load(Ordering::SeqCst), 10);
1268 }
1269
1270 #[test]
1273 fn test_next_fire_bitmap_wrap_around() {
1274 let epoch = Instant::now();
1275 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1276
1277 let (t1, _) = OneShotTimer::new(1);
1279 wheel.insert(epoch + Duration::from_millis(60), t1).unwrap();
1280
1281 let mut ctx = Vec::new();
1283 wheel
1284 .poll(epoch + Duration::from_millis(61), &mut ctx)
1285 .unwrap();
1286
1287 let (t2, _) = OneShotTimer::new(2);
1289 wheel.insert(epoch + Duration::from_millis(70), t2).unwrap(); assert!(wheel.duration_until_next().is_some());
1293 }
1294
1295 #[test]
1296 fn test_multiple_gears_same_poll() {
1297 let epoch = Instant::now();
1298 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1299
1300 let (t1, f1) = OneShotTimer::new(1);
1301 let (t2, f2) = OneShotTimer::new(2);
1302 let (t3, f3) = OneShotTimer::new(3);
1303
1304 wheel.insert(epoch + Duration::from_millis(10), t1).unwrap();
1306 wheel
1308 .insert(epoch + Duration::from_millis(100), t2)
1309 .unwrap();
1310 wheel
1312 .insert(epoch + Duration::from_millis(5000), t3)
1313 .unwrap();
1314
1315 let mut ctx = Vec::new();
1316
1317 wheel
1319 .poll(epoch + Duration::from_millis(6000), &mut ctx)
1320 .unwrap();
1321
1322 assert!(f1.get());
1323 assert!(f2.get());
1324 assert!(f3.get());
1325 assert_eq!(ctx.len(), 3);
1326 }
1327
1328 #[test]
1329 fn test_poll_error_on_reschedule_failure() {
1330 let epoch = Instant::now();
1331 let mut wheel: Box<BitWheel<PeriodicTimer, 4, 1, 1, 1>> = BitWheel::boxed_with_epoch(epoch);
1333
1334 let timer = PeriodicTimer::new(1, Duration::from_millis(1), 2);
1336 wheel
1337 .insert(epoch + Duration::from_millis(10), timer)
1338 .unwrap();
1339
1340 let blocker = PeriodicTimer::new(99, Duration::from_millis(1000), 1);
1343 wheel
1344 .insert(epoch + Duration::from_millis(16), blocker)
1345 .unwrap();
1346
1347 let mut ctx = Vec::new();
1348 let result = wheel.poll(epoch + Duration::from_millis(15), &mut ctx);
1349
1350 assert!(result.is_err());
1351 let err = result.unwrap_err();
1352 assert!(err.0 > 0);
1353 }
1354
1355 #[test]
1356 fn test_gear_boundary_exact_64() {
1357 let epoch = Instant::now();
1358 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1359
1360 assert_eq!(wheel.gear_for_delay(63), 0);
1362 assert_eq!(wheel.gear_for_delay(64), 1);
1364 }
1365
1366 #[test]
1367 fn test_gear_boundary_exact_4096() {
1368 let epoch = Instant::now();
1369 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1370
1371 assert_eq!(wheel.gear_for_delay(4095), 1);
1373 assert_eq!(wheel.gear_for_delay(4096), 2);
1375 }
1376
1377 #[test]
1378 fn test_insert_after_cancel_reuses_slot() {
1379 let epoch = Instant::now();
1380 let mut wheel: Box<BitWheel<OneShotTimer, 4, 1, 2, 3>> = BitWheel::boxed_with_epoch(epoch);
1381
1382 let when = epoch + Duration::from_millis(10);
1383
1384 let (t1, _) = OneShotTimer::new(1);
1386 let (t2, _) = OneShotTimer::new(2);
1387 let h1 = wheel.insert(when, t1).unwrap();
1388 let h2 = wheel.insert(when, t2).unwrap();
1389
1390 wheel.cancel(h1);
1392
1393 let (t3, _) = OneShotTimer::new(3);
1395 let h3 = wheel.insert(when, t3).unwrap();
1396
1397 assert_eq!(h2.slot, h3.slot);
1399 }
1400
1401 #[test]
1402 fn test_periodic_crosses_gear_boundary() {
1403 let epoch = Instant::now();
1404 let mut wheel: Box<BitWheel<PeriodicTimer, 4, 1, 32, 3>> =
1405 BitWheel::boxed_with_epoch(epoch);
1406
1407 let timer = PeriodicTimer::new(1, Duration::from_millis(100), 3);
1409 wheel
1410 .insert(epoch + Duration::from_millis(10), timer)
1411 .unwrap();
1412
1413 let mut ctx = Vec::new();
1414
1415 wheel
1417 .poll(epoch + Duration::from_millis(15), &mut ctx)
1418 .unwrap();
1419 assert_eq!(ctx.len(), 1);
1420
1421 wheel
1423 .poll(epoch + Duration::from_millis(115), &mut ctx)
1424 .unwrap();
1425 assert_eq!(ctx.len(), 2);
1426
1427 wheel
1429 .poll(epoch + Duration::from_millis(215), &mut ctx)
1430 .unwrap();
1431 assert_eq!(ctx.len(), 3);
1432
1433 assert!(wheel.is_empty());
1434 }
1435
1436 #[test]
1437 fn test_zero_delay_goes_to_gear0() {
1438 let epoch = Instant::now();
1439 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1440
1441 assert_eq!(wheel.gear_for_delay(0), 0);
1442 }
1443
1444 #[test]
1445 fn test_delay_beyond_max_gears_clamps() {
1446 let epoch = Instant::now();
1447 let wheel: Box<BitWheel<OneShotTimer, 4, 1, 32, 3>> = BitWheel::boxed_with_epoch(epoch);
1448
1449 assert_eq!(wheel.gear_for_delay(u64::MAX), 3);
1451 assert_eq!(wheel.gear_for_delay(1_000_000_000), 3);
1452 }
1453}
1454
1455#[cfg(test)]
1456mod latency_tests {
1457 use super::*;
1458 use hdrhistogram::Histogram;
1459 use std::time::{Duration, Instant};
1460
1461 struct LatencyTimer;
1462
1463 impl Timer for LatencyTimer {
1464 type Context = ();
1465
1466 fn fire(&mut self, _now: Instant, _ctx: &mut ()) -> Option<Instant> {
1467 None
1468 }
1469 }
1470
1471 struct PeriodicLatencyTimer {
1472 period: Duration,
1473 remaining: usize,
1474 }
1475
1476 impl Timer for PeriodicLatencyTimer {
1477 type Context = ();
1478
1479 fn fire(&mut self, now: Instant, _ctx: &mut ()) -> Option<Instant> {
1480 self.remaining = self.remaining.saturating_sub(1);
1481 if self.remaining > 0 {
1482 Some(now + self.period)
1483 } else {
1484 None
1485 }
1486 }
1487 }
1488
1489 enum MixedLatencyTimer {
1490 OneShot,
1491 Periodic { period: Duration, remaining: usize },
1492 }
1493
1494 impl Timer for MixedLatencyTimer {
1495 type Context = ();
1496
1497 fn fire(&mut self, now: Instant, _ctx: &mut ()) -> Option<Instant> {
1498 match self {
1499 MixedLatencyTimer::OneShot => None,
1500 MixedLatencyTimer::Periodic { period, remaining } => {
1501 *remaining = remaining.saturating_sub(1);
1502 if *remaining > 0 {
1503 Some(now + *period)
1504 } else {
1505 None
1506 }
1507 }
1508 }
1509 }
1510 }
1511
1512 fn print_histogram(name: &str, hist: &Histogram<u64>) {
1513 println!("\n=== {} ===", name);
1514 println!(" count: {}", hist.len());
1515 println!(" min: {} ns", hist.min());
1516 println!(" max: {} ns", hist.max());
1517 println!(" mean: {:.1} ns", hist.mean());
1518 println!(" stddev: {:.1} ns", hist.stdev());
1519 println!(" p50: {} ns", hist.value_at_quantile(0.50));
1520 println!(" p90: {} ns", hist.value_at_quantile(0.90));
1521 println!(" p99: {} ns", hist.value_at_quantile(0.99));
1522 println!(" p99.9: {} ns", hist.value_at_quantile(0.999));
1523 println!(" p99.99: {} ns", hist.value_at_quantile(0.9999));
1524 }
1525
1526 #[test]
1527 #[ignore]
1528 fn hdr_insert_latency() {
1529 let epoch = Instant::now();
1530 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1531
1532 let mut hist = Histogram::<u64>::new(3).unwrap();
1533 let iterations = 100_000;
1534
1535 for i in 0..1000 {
1537 let when = epoch + Duration::from_millis((i % 500) + 10);
1538 let handle = wheel.insert(when, LatencyTimer).unwrap();
1539 wheel.cancel(handle);
1540 }
1541
1542 for i in 0..iterations {
1544 let when = epoch + Duration::from_millis((i % 500) + 10);
1545
1546 let start = Instant::now();
1547 let handle = wheel.insert(when, LatencyTimer).unwrap();
1548 let elapsed = start.elapsed().as_nanos() as u64;
1549
1550 hist.record(elapsed).unwrap();
1551 wheel.cancel(handle);
1552 }
1553
1554 print_histogram("Insert Latency", &hist);
1555 }
1556
1557 #[test]
1558 #[ignore]
1559 fn hdr_cancel_latency() {
1560 let epoch = Instant::now();
1561 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1562
1563 let mut hist = Histogram::<u64>::new(3).unwrap();
1564 let iterations = 100_000;
1565
1566 for i in 0..1000 {
1568 let when = epoch + Duration::from_millis((i % 500) + 10);
1569 let handle = wheel.insert(when, LatencyTimer).unwrap();
1570 wheel.cancel(handle);
1571 }
1572
1573 for i in 0..iterations {
1575 let when = epoch + Duration::from_millis((i % 500) + 10);
1576 let handle = wheel.insert(when, LatencyTimer).unwrap();
1577
1578 let start = Instant::now();
1579 let _ = wheel.cancel(handle);
1580 let elapsed = start.elapsed().as_nanos() as u64;
1581
1582 hist.record(elapsed).unwrap();
1583 }
1584
1585 print_histogram("Cancel Latency", &hist);
1586 }
1587
1588 #[test]
1589 #[ignore]
1590 fn hdr_poll_empty() {
1591 let epoch = Instant::now();
1592 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1593
1594 let mut hist = Histogram::<u64>::new(3).unwrap();
1595 let iterations = 100_000;
1596 let mut ctx = ();
1597
1598 for i in 0..1000u64 {
1600 let now = epoch + Duration::from_millis(i);
1601 let _ = wheel.poll(now, &mut ctx);
1602 }
1603
1604 for i in 1000..(1000 + iterations) {
1606 let now = epoch + Duration::from_millis(i);
1607
1608 let start = Instant::now();
1609 let _ = wheel.poll(now, &mut ctx);
1610 let elapsed = start.elapsed().as_nanos() as u64;
1611
1612 hist.record(elapsed).unwrap();
1613 }
1614
1615 print_histogram("Poll Empty", &hist);
1616 }
1617
1618 #[test]
1619 #[ignore]
1620 fn hdr_poll_pending_no_fires() {
1621 let epoch = Instant::now();
1622 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1623
1624 for i in 0..1000 {
1626 let when = epoch + Duration::from_millis(1_000_000 + i);
1627 let _ = wheel.insert(when, LatencyTimer);
1628 }
1629
1630 let mut hist = Histogram::<u64>::new(3).unwrap();
1631 let iterations = 100_000;
1632 let mut ctx = ();
1633
1634 for i in 0..1000u64 {
1636 let now = epoch + Duration::from_millis(i);
1637 let _ = wheel.poll(now, &mut ctx);
1638 }
1639
1640 for i in 1000..(1000 + iterations) {
1642 let now = epoch + Duration::from_millis(i);
1643
1644 let start = Instant::now();
1645 let _ = wheel.poll(now, &mut ctx);
1646 let elapsed = start.elapsed().as_nanos() as u64;
1647
1648 hist.record(elapsed).unwrap();
1649 }
1650
1651 print_histogram("Poll Pending (No Fires)", &hist);
1652 }
1653
1654 #[test]
1655 #[ignore]
1656 fn hdr_poll_single_fire() {
1657 let epoch = Instant::now();
1658 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1659
1660 let mut hist = Histogram::<u64>::new(3).unwrap();
1661 let iterations = 100_000u64;
1662 let mut ctx = ();
1663
1664 for i in 0..1000u64 {
1666 let when = epoch + Duration::from_millis(i + 1);
1667 let _ = wheel.insert(when, LatencyTimer);
1668 let now = epoch + Duration::from_millis(i + 1);
1669 let _ = wheel.poll(now, &mut ctx);
1670 }
1671
1672 for i in 0..iterations {
1674 let tick = 1000 + i;
1675 let when = epoch + Duration::from_millis(tick + 1);
1676 let _ = wheel.insert(when, LatencyTimer);
1677
1678 let now = epoch + Duration::from_millis(tick + 1);
1679
1680 let start = Instant::now();
1681 let _ = wheel.poll(now, &mut ctx);
1682 let elapsed = start.elapsed().as_nanos() as u64;
1683
1684 hist.record(elapsed).unwrap();
1685 }
1686
1687 print_histogram("Poll Single Fire", &hist);
1688 }
1689
1690 #[test]
1691 #[ignore]
1692 fn hdr_periodic_steady_state() {
1693 let epoch = Instant::now();
1694 let mut wheel: Box<BitWheel<PeriodicLatencyTimer, 4, 1, 64, 8>> =
1695 BitWheel::boxed_with_epoch(epoch);
1696
1697 let mut hist = Histogram::<u64>::new(3).unwrap();
1698 let mut ctx = ();
1699
1700 for i in 0..10 {
1702 let when = epoch + Duration::from_millis(i + 1);
1703 let timer = PeriodicLatencyTimer {
1704 period: Duration::from_millis(1),
1705 remaining: usize::MAX,
1706 };
1707 let _ = wheel.insert(when, timer);
1708 }
1709
1710 for i in 0..1000u64 {
1712 let now = epoch + Duration::from_millis(i + 1);
1713 let _ = wheel.poll(now, &mut ctx);
1714 }
1715
1716 for i in 1000..101_000u64 {
1718 let now = epoch + Duration::from_millis(i + 1);
1719
1720 let start = Instant::now();
1721 let _ = wheel.poll(now, &mut ctx);
1722 let elapsed = start.elapsed().as_nanos() as u64;
1723
1724 hist.record(elapsed).unwrap();
1725 }
1726
1727 print_histogram("Periodic Steady State (10 timers @ 1ms)", &hist);
1728 }
1729
1730 #[test]
1731 #[ignore]
1732 fn hdr_mixed_periodic_oneshot() {
1733 let epoch = Instant::now();
1734 let mut wheel: Box<BitWheel<MixedLatencyTimer, 4, 1, 64, 8>> =
1735 BitWheel::boxed_with_epoch(epoch);
1736
1737 let mut hist = Histogram::<u64>::new(3).unwrap();
1738 let mut ctx = ();
1739
1740 for i in 0..10 {
1742 let when = epoch + Duration::from_millis(i + 10);
1743 let timer = MixedLatencyTimer::Periodic {
1744 period: Duration::from_millis(10),
1745 remaining: usize::MAX,
1746 };
1747 let _ = wheel.insert(when, timer);
1748 }
1749
1750 for i in 0..1000u64 {
1752 let now = epoch + Duration::from_millis(i + 1);
1753
1754 for j in 0..2 {
1756 let when = now + Duration::from_millis(50 + (i + j) % 50);
1757 let _ = wheel.insert(when, MixedLatencyTimer::OneShot);
1758 }
1759
1760 let _ = wheel.poll(now, &mut ctx);
1761 }
1762
1763 for i in 1000..101_000u64 {
1765 let now = epoch + Duration::from_millis(i + 1);
1766
1767 for j in 0..2 {
1769 let when = now + Duration::from_millis(50 + (i + j) % 50);
1770 let _ = wheel.insert(when, MixedLatencyTimer::OneShot);
1771 }
1772
1773 let start = Instant::now();
1774 let _ = wheel.poll(now, &mut ctx);
1775 let elapsed = start.elapsed().as_nanos() as u64;
1776
1777 hist.record(elapsed).unwrap();
1778 }
1779
1780 print_histogram("Mixed (10 periodic + 2 oneshot/tick)", &hist);
1781 }
1782
1783 #[test]
1784 #[ignore]
1785 fn hdr_bursty_workload() {
1786 let epoch = Instant::now();
1787 let mut wheel: Box<BitWheel<MixedLatencyTimer, 4, 1, 128, 16>> =
1788 BitWheel::boxed_with_epoch(epoch);
1789
1790 let mut hist = Histogram::<u64>::new(3).unwrap();
1791 let mut ctx = ();
1792
1793 for i in 0..10 {
1795 let when = epoch + Duration::from_millis(i + 10);
1796 let timer = MixedLatencyTimer::Periodic {
1797 period: Duration::from_millis(10),
1798 remaining: usize::MAX,
1799 };
1800 let _ = wheel.insert(when, timer);
1801 }
1802
1803 for i in 0..1000u64 {
1805 let now = epoch + Duration::from_millis(i + 1);
1806
1807 if i % 100 == 0 {
1809 for j in 0..50 {
1810 let when = now + Duration::from_millis(20 + j % 80);
1811 let _ = wheel.insert(when, MixedLatencyTimer::OneShot);
1812 }
1813 }
1814
1815 let _ = wheel.poll(now, &mut ctx);
1816 }
1817
1818 for i in 1000..101_000u64 {
1820 let now = epoch + Duration::from_millis(i + 1);
1821
1822 if i % 100 == 0 {
1824 for j in 0..50 {
1825 let when = now + Duration::from_millis(20 + j % 80);
1826 let _ = wheel.insert(when, MixedLatencyTimer::OneShot);
1827 }
1828 }
1829
1830 let start = Instant::now();
1831 let _ = wheel.poll(now, &mut ctx);
1832 let elapsed = start.elapsed().as_nanos() as u64;
1833
1834 hist.record(elapsed).unwrap();
1835 }
1836
1837 print_histogram("Bursty (10 periodic + 50 burst every 100ms)", &hist);
1838 }
1839
1840 #[test]
1841 #[ignore]
1842 fn hdr_trading_simulation() {
1843 let epoch = Instant::now();
1844 let mut wheel: Box<BitWheel<LatencyTimer, 4, 1, 64, 8>> = BitWheel::boxed_with_epoch(epoch);
1845
1846 let mut insert_hist = Histogram::<u64>::new(3).unwrap();
1847 let mut poll_hist = Histogram::<u64>::new(3).unwrap();
1848 let mut cancel_hist = Histogram::<u64>::new(3).unwrap();
1849
1850 let mut handles = Vec::with_capacity(100);
1851 let mut ctx = ();
1852 let mut now = epoch;
1853
1854 let iterations = 100_000u64;
1855
1856 for i in 0..1000 {
1858 now += Duration::from_millis(1);
1859 let _ = wheel.poll(now, &mut ctx);
1860
1861 if i % 5 != 0 {
1862 let when = now + Duration::from_millis(50 + (i % 200));
1863 if let Ok(handle) = wheel.insert(when, LatencyTimer) {
1864 if handles.len() < 100 {
1865 handles.push(handle);
1866 }
1867 }
1868 }
1869
1870 if i % 20 == 0 {
1871 if let Some(handle) = handles.pop() {
1872 let _ = wheel.cancel(handle);
1873 }
1874 }
1875 }
1876
1877 for i in 0..iterations {
1879 now += Duration::from_millis(1);
1880
1881 let start = Instant::now();
1883 let _ = wheel.poll(now, &mut ctx);
1884 poll_hist.record(start.elapsed().as_nanos() as u64).unwrap();
1885
1886 if i % 5 != 0 {
1888 let when = now + Duration::from_millis(50 + (i % 200));
1889
1890 let start = Instant::now();
1891 if let Ok(handle) = wheel.insert(when, LatencyTimer) {
1892 insert_hist
1893 .record(start.elapsed().as_nanos() as u64)
1894 .unwrap();
1895
1896 if handles.len() < 100 {
1897 handles.push(handle);
1898 }
1899 }
1900 }
1901
1902 if i % 20 == 0 {
1904 if let Some(handle) = handles.pop() {
1905 let start = Instant::now();
1906 let _ = wheel.cancel(handle);
1907 cancel_hist
1908 .record(start.elapsed().as_nanos() as u64)
1909 .unwrap();
1910 }
1911 }
1912 }
1913
1914 print_histogram("Trading Sim - Insert", &insert_hist);
1915 print_histogram("Trading Sim - Poll", &poll_hist);
1916 print_histogram("Trading Sim - Cancel", &cancel_hist);
1917 }
1918
1919 #[test]
1920 #[ignore]
1921 fn hdr_realistic_trading() {
1922 let epoch = Instant::now();
1923 let mut wheel: Box<BitWheel<MixedLatencyTimer, 4, 1, 64, 8>> =
1924 BitWheel::boxed_with_epoch(epoch);
1925
1926 let mut insert_hist = Histogram::<u64>::new(3).unwrap();
1927 let mut poll_hist = Histogram::<u64>::new(3).unwrap();
1928 let mut cancel_hist = Histogram::<u64>::new(3).unwrap();
1929
1930 let mut handles = Vec::with_capacity(100);
1931 let mut ctx = ();
1932 let mut now = epoch;
1933
1934 for i in 0..5 {
1936 let when = epoch + Duration::from_secs(30) + Duration::from_millis(i * 100);
1937 let timer = MixedLatencyTimer::Periodic {
1938 period: Duration::from_secs(30),
1939 remaining: usize::MAX,
1940 };
1941 let _ = wheel.insert(when, timer);
1942 }
1943
1944 let iterations = 100_000u64;
1945
1946 for i in 0..1000 {
1948 now += Duration::from_millis(1);
1949 let _ = wheel.poll(now, &mut ctx);
1950
1951 if i % 5 != 0 {
1953 let when = now + Duration::from_millis(50 + (i % 200));
1954 if let Ok(handle) = wheel.insert(when, MixedLatencyTimer::OneShot) {
1955 if handles.len() < 100 {
1956 handles.push(handle);
1957 }
1958 }
1959 }
1960
1961 if i % 20 == 0 {
1963 if let Some(handle) = handles.pop() {
1964 let _ = wheel.cancel(handle);
1965 }
1966 }
1967 }
1968
1969 for i in 0..iterations {
1971 now += Duration::from_millis(1);
1972
1973 let start = Instant::now();
1975 let _ = wheel.poll(now, &mut ctx);
1976 poll_hist.record(start.elapsed().as_nanos() as u64).unwrap();
1977
1978 if i % 5 != 0 {
1980 let when = now + Duration::from_millis(50 + (i % 200));
1981
1982 let start = Instant::now();
1983 if let Ok(handle) = wheel.insert(when, MixedLatencyTimer::OneShot) {
1984 insert_hist
1985 .record(start.elapsed().as_nanos() as u64)
1986 .unwrap();
1987
1988 if handles.len() < 100 {
1989 handles.push(handle);
1990 }
1991 }
1992 }
1993
1994 if i % 20 == 0 {
1996 if let Some(handle) = handles.pop() {
1997 let start = Instant::now();
1998 let _ = wheel.cancel(handle);
1999 cancel_hist
2000 .record(start.elapsed().as_nanos() as u64)
2001 .unwrap();
2002 }
2003 }
2004 }
2005
2006 print_histogram("Realistic Trading - Insert (order timeout)", &insert_hist);
2007 print_histogram("Realistic Trading - Poll", &poll_hist);
2008 print_histogram("Realistic Trading - Cancel (order fill)", &cancel_hist);
2009 }
2010}