Skip to main content

mio_timer/
timer.rs

1//! Timer optimized for I/O related operations
2use super::convert;
3use lazycell::LazyCell;
4use log::trace;
5use mio::Token;
6use slab::Slab;
7use std::{
8    cmp, fmt, io, iter,
9    sync::{
10        Arc,
11        atomic::{AtomicUsize, Ordering},
12    },
13    thread,
14    time::{Duration, Instant},
15};
16use mio_misc::{queue::Notifier, NotificationId};
17
18type Tick = u64;
19
20const TICK_MAX: Tick = u64::MAX;
21
22// Manages communication with wakeup thread
23type WakeupState = Arc<AtomicUsize>;
24
25const TERMINATE_THREAD: usize = 0;
26const EMPTY: Token = Token(usize::MAX);
27
28/// A timer.
29///
30/// Typical usage goes like this:
31///
32/// * register the timer with a `mio::Poll`.
33/// * set a timeout, by calling `Timer::set_timeout`.  Here you provide some
34///   state to be associated with this timeout.
35/// * poll the `Poll`, to learn when a timeout has occurred.
36/// * retrieve state associated with the timeout by calling `Timer::poll`.
37///
38/// You can omit use of the `Poll` altogether, if you like, and just poll the
39/// `Timer` directly.
40pub struct Timer<T> {
41    // Size of each tick in milliseconds
42    tick_ms: u64,
43    // Slab of timeout entries
44    entries: Slab<Entry<T>>,
45    // Timeout wheel. Each tick, the timer will look at the next slot for
46    // timeouts that match the current tick.
47    wheel: Vec<WheelEntry>,
48    // Tick 0's time instant
49    start: Instant,
50    // The current tick
51    tick: Tick,
52    // The next entry to possibly timeout
53    next: Token,
54    // Masks the target tick to get the slot
55    mask: u64,
56    // Set on registration with Poll
57    inner: LazyCell<Inner>,
58}
59
60/// Used to create a `Timer`.
61pub struct Builder {
62    // Approximate duration of each tick
63    tick: Duration,
64    // Number of slots in the timer wheel
65    num_slots: usize,
66    // Max number of timeouts that can be in flight at a given time.
67    capacity: usize,
68}
69
70impl Builder {
71    /// Set the tick duration.  Default is 100ms.
72    pub fn tick_duration(mut self, duration: Duration) -> Builder {
73        self.tick = duration;
74        self
75    }
76
77    /// Set the number of slots.  Default is 256.
78    pub fn num_slots(mut self, num_slots: usize) -> Builder {
79        self.num_slots = num_slots;
80        self
81    }
82
83    /// Set the capacity.  Default is 65536.
84    pub fn capacity(mut self, capacity: usize) -> Builder {
85        self.capacity = capacity;
86        self
87    }
88
89    /// Build a `Timer` with the parameters set on this `Builder`.
90    pub fn build<T>(self) -> Timer<T> {
91        Timer::new(
92            convert::millis(self.tick),
93            self.num_slots,
94            self.capacity,
95            Instant::now(),
96        )
97    }
98}
99
100impl Default for Builder {
101    fn default() -> Builder {
102        Builder {
103            tick: Duration::from_millis(100),
104            num_slots: 1 << 8,
105            capacity: 1 << 16,
106        }
107    }
108}
109
110/// A timeout, as returned by `Timer::set_timeout`.
111///
112/// Use this as the argument to `Timer::cancel_timeout`, to cancel this timeout.
113#[derive(Clone, Debug)]
114pub struct Timeout {
115    // Reference into the timer entry slab
116    token: Token,
117    // Tick that it should match up with
118    tick: u64,
119}
120
121#[allow(unused)]
122struct Inner {
123    waker: Option<Arc<dyn Notifier>>,
124    waker_id: Option<NotificationId>,
125    wakeup_state: WakeupState,
126    wakeup_thread: thread::JoinHandle<()>,
127}
128
129impl Drop for Inner {
130    fn drop(&mut self) {
131        // 1. Set wakeup state to TERMINATE_THREAD
132        self.wakeup_state.store(TERMINATE_THREAD, Ordering::Release);
133        // 2. Wake him up
134        self.wakeup_thread.thread().unpark();
135    }
136}
137
138impl fmt::Debug for Inner {
139    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
140        fmt.debug_struct("Inner")
141            .field("wakeup_state", &self.wakeup_state.load(Ordering::Relaxed))
142            .finish()
143    }
144}
145
146#[derive(Copy, Clone, Debug)]
147struct WheelEntry {
148    next_tick: Tick,
149    head: Token,
150}
151
152// Doubly linked list of timer entries. Allows for efficient insertion /
153// removal of timeouts.
154struct Entry<T> {
155    state: T,
156    links: EntryLinks,
157}
158
159impl<T> Entry<T> {
160    fn new(state: T, tick: u64, next: Token) -> Entry<T> {
161        Entry {
162            state,
163            links: EntryLinks {
164                tick,
165                prev: EMPTY,
166                next,
167            },
168        }
169    }
170}
171
172#[derive(Copy, Clone)]
173struct EntryLinks {
174    tick: Tick,
175    prev: Token,
176    next: Token,
177}
178
179impl<T> Timer<T> {
180    fn new(tick_ms: u64, num_slots: usize, capacity: usize, start: Instant) -> Timer<T> {
181        let num_slots = num_slots.next_power_of_two();
182        let capacity = capacity.next_power_of_two();
183        let mask = (num_slots as u64) - 1;
184        let wheel = iter::repeat(WheelEntry {
185            next_tick: TICK_MAX,
186            head: EMPTY,
187        })
188        .take(num_slots)
189        .collect();
190
191        Timer {
192            tick_ms,
193            entries: Slab::with_capacity(capacity),
194            wheel,
195            start,
196            tick: 0,
197            next: EMPTY,
198            mask,
199            inner: LazyCell::new(),
200        }
201    }
202
203    /// Set a timeout.
204    ///
205    /// When the timeout occurs, the given state becomes available via `poll`.
206    pub fn set_timeout(&mut self, delay_from_now: Duration, state: T) -> Timeout {
207        let delay_from_start = self.start.elapsed() + delay_from_now;
208        self.set_timeout_at(delay_from_start, state)
209    }
210
211    fn set_timeout_at(&mut self, delay_from_start: Duration, state: T) -> Timeout {
212        let mut tick = duration_to_tick(delay_from_start, self.tick_ms);
213        trace!(
214            "setting timeout; delay={:?}; tick={:?}; current-tick={:?}",
215            delay_from_start, tick, self.tick
216        );
217
218        // Always target at least 1 tick in the future
219        if tick <= self.tick {
220            tick = self.tick + 1;
221        }
222
223        self.insert(tick, state)
224    }
225
226    fn insert(&mut self, tick: Tick, state: T) -> Timeout {
227        // Get the slot for the requested tick
228        let slot = self.slot_for(tick);
229        let curr = self.wheel[slot];
230
231        // Insert the new entry
232        let entry = Entry::new(state, tick, curr.head);
233        let token = Token(self.entries.insert(entry));
234
235        if curr.head != EMPTY {
236            // If there was a previous entry, set its prev pointer to the new
237            // entry
238            self.entries[curr.head.into()].links.prev = token;
239        }
240
241        // Update the head slot
242        self.wheel[slot] = WheelEntry {
243            next_tick: cmp::min(tick, curr.next_tick),
244            head: token,
245        };
246
247        self.schedule_readiness(tick);
248
249        trace!("inserted timout; slot={}; token={:?}", slot, token);
250
251        // Return the new timeout
252        Timeout { token, tick }
253    }
254
255    /// Cancel a timeout.
256    ///
257    /// If the timeout has not yet occurred, the return value holds the
258    /// associated state.
259    pub fn cancel_timeout(&mut self, timeout: &Timeout) -> Option<T> {
260        let links = match self.entries.get(timeout.token.into()) {
261            Some(e) => e.links,
262            None => return None,
263        };
264
265        // Sanity check
266        if links.tick != timeout.tick {
267            return None;
268        }
269
270        self.unlink(&links, timeout.token);
271        Some(self.entries.remove(timeout.token.into()).state)
272    }
273
274    /// Poll for an expired timer.
275    ///
276    /// The return value holds the state associated with the first expired
277    /// timer, if any.
278    pub fn poll(&mut self) -> Option<T> {
279        let target_tick = current_tick(self.start, self.tick_ms);
280        self.poll_to(target_tick)
281    }
282
283    fn poll_to(&mut self, mut target_tick: Tick) -> Option<T> {
284        trace!(
285            "tick_to; target_tick={}; current_tick={}",
286            target_tick, self.tick
287        );
288
289        if target_tick < self.tick {
290            target_tick = self.tick;
291        }
292
293        while self.tick <= target_tick {
294            let curr = self.next;
295
296            trace!("ticking; curr={:?}", curr);
297
298            if curr == EMPTY {
299                self.tick += 1;
300
301                let slot = self.slot_for(self.tick);
302                self.next = self.wheel[slot].head;
303
304                // Handle the case when a slot has a single timeout which gets
305                // canceled before the timeout expires. In this case, the
306                // slot's head is EMPTY but there is a value for next_tick. Not
307                // resetting next_tick here causes the timer to get stuck in a
308                // loop.
309                if self.next == EMPTY {
310                    self.wheel[slot].next_tick = TICK_MAX;
311                }
312            } else {
313                let slot = self.slot_for(self.tick);
314
315                if curr == self.wheel[slot].head {
316                    self.wheel[slot].next_tick = TICK_MAX;
317                }
318
319                let links = self.entries[curr.into()].links;
320
321                if links.tick <= self.tick {
322                    trace!("triggering; token={:?}", curr);
323
324                    // Unlink will also advance self.next
325                    self.unlink(&links, curr);
326
327                    // Remove and return the token
328                    return Some(self.entries.remove(curr.into()).state);
329                } else {
330                    let next_tick = self.wheel[slot].next_tick;
331                    self.wheel[slot].next_tick = cmp::min(next_tick, links.tick);
332                    self.next = links.next;
333                }
334            }
335        }
336
337        // No more timeouts to poll
338        if let Some(_inner) = self.inner.borrow() {
339            trace!("unsetting readiness");
340            // let _ = inner.set_readiness.set_readiness(Ready::empty());
341
342            if let Some(tick) = self.next_tick() {
343                self.schedule_readiness(tick);
344            }
345        }
346
347        None
348    }
349
350    fn unlink(&mut self, links: &EntryLinks, token: Token) {
351        trace!(
352            "unlinking timeout; slot={}; token={:?}",
353            self.slot_for(links.tick),
354            token
355        );
356
357        if links.prev == EMPTY {
358            let slot = self.slot_for(links.tick);
359            self.wheel[slot].head = links.next;
360        } else {
361            self.entries[links.prev.into()].links.next = links.next;
362        }
363
364        if links.next != EMPTY {
365            self.entries[links.next.into()].links.prev = links.prev;
366
367            if token == self.next {
368                self.next = links.next;
369            }
370        } else if token == self.next {
371            self.next = EMPTY;
372        }
373    }
374
375    fn schedule_readiness(&self, tick: Tick) {
376        if let Some(inner) = self.inner.borrow() {
377            // Coordinate setting readiness w/ the wakeup thread
378            let mut curr = inner.wakeup_state.load(Ordering::Acquire);
379
380            loop {
381                if curr as Tick <= tick {
382                    // Nothing to do, wakeup is already scheduled
383                    return;
384                }
385
386                // Attempt to move the wakeup time forward
387                trace!("advancing the wakeup time; target={}; curr={}", tick, curr);
388                let actual = inner
389                    .wakeup_state
390                    .compare_exchange(curr, tick as usize, Ordering::Release, Ordering::Relaxed)
391                    .unwrap_or_else(|x| x);
392
393                if actual == curr {
394                    // Signal to the wakeup thread that the wakeup time has
395                    // been changed.
396                    trace!("unparking wakeup thread");
397                    inner.wakeup_thread.thread().unpark();
398                    return;
399                }
400
401                curr = actual;
402            }
403        }
404    }
405
406    // Next tick containing a timeout
407    fn next_tick(&self) -> Option<Tick> {
408        if self.next != EMPTY {
409            let slot = self.slot_for(self.entries[self.next.into()].links.tick);
410
411            if self.wheel[slot].next_tick == self.tick {
412                // There is data ready right now
413                return Some(self.tick);
414            }
415        }
416
417        self.wheel.iter().map(|e| e.next_tick).min()
418    }
419
420    fn slot_for(&self, tick: Tick) -> usize {
421        (self.mask & tick) as usize
422    }
423
424    pub fn register(&mut self, notifier: Arc<dyn Notifier>, notification_id: NotificationId) -> io::Result<()> {
425        if self.inner.borrow().is_some() {
426            return Err(io::Error::new(
427                io::ErrorKind::Other,
428                "timer already registered",
429            ));
430        }
431
432        let wakeup_state = Arc::new(AtomicUsize::new(usize::MAX));
433        let thread_handle = spawn_wakeup_thread(
434            Arc::clone(&wakeup_state),
435            notifier.clone(),
436            notification_id,
437            self.start,
438            self.tick_ms,
439        );
440        self.inner.fill(Inner {
441            waker: Some(notifier),
442            waker_id: Some(notification_id),
443            wakeup_state,
444            wakeup_thread: thread_handle,
445        }).expect("timer already registered");
446
447        if let Some(next_tick) = self.next_tick() {
448            self.schedule_readiness(next_tick);
449        }
450
451        return Ok(())
452    }
453}
454
455impl<T> Default for Timer<T> {
456    fn default() -> Timer<T> {
457        Builder::default().build()
458    }
459}
460
461fn spawn_wakeup_thread(
462    state: WakeupState,
463    waker: Arc<dyn Notifier>,
464    notification_id: NotificationId,
465    start: Instant,
466    tick_ms: u64,
467) -> thread::JoinHandle<()> {
468    thread::spawn(move || {
469        let mut sleep_until_tick = state.load(Ordering::Acquire) as Tick;
470
471        loop {
472            if sleep_until_tick == TERMINATE_THREAD as Tick {
473                return;
474            }
475
476            let now_tick = current_tick(start, tick_ms);
477
478            trace!(
479                "wakeup thread: sleep_until_tick={:?}; now_tick={:?}",
480                sleep_until_tick, now_tick
481            );
482
483            if now_tick < sleep_until_tick {
484                // Calling park_timeout with u64::MAX leads to undefined
485                // behavior in pthread, causing the park to return immediately
486                // and causing the thread to tightly spin. Instead of u64::MAX
487                // on large values, simply use a blocking park.
488                match tick_ms.checked_mul(sleep_until_tick - now_tick) {
489                    Some(sleep_duration) => {
490                        trace!(
491                            "sleeping; tick_ms={}; now_tick={}; sleep_until_tick={}; duration={:?}",
492                            tick_ms, now_tick, sleep_until_tick, sleep_duration
493                        );
494                        thread::park_timeout(Duration::from_millis(sleep_duration));
495                    }
496                    None => {
497                        trace!(
498                            "sleeping; tick_ms={}; now_tick={}; blocking sleep",
499                            tick_ms, now_tick
500                        );
501                        thread::park();
502                    }
503                }
504                sleep_until_tick = state.load(Ordering::Acquire) as Tick;
505            } else {
506                let actual = state
507                    .compare_exchange(
508                        sleep_until_tick as usize,
509                        usize::MAX,
510                        Ordering::AcqRel,
511                        Ordering::Acquire,
512                    )
513                    .unwrap_or_else(|x| x) as Tick;
514
515                if actual == sleep_until_tick {
516                    trace!("setting readiness from wakeup thread");
517                    let _ = waker.notify(notification_id);
518                    sleep_until_tick = usize::MAX as Tick;
519                } else {
520                    sleep_until_tick = actual as Tick;
521                }
522            }
523        }
524    })
525}
526
527fn duration_to_tick(elapsed: Duration, tick_ms: u64) -> Tick {
528    // Calculate tick rounding up to the closest one
529    let elapsed_ms = convert::millis(elapsed);
530    elapsed_ms.saturating_add(tick_ms / 2) / tick_ms
531}
532
533fn current_tick(start: Instant, tick_ms: u64) -> Tick {
534    duration_to_tick(start.elapsed(), tick_ms)
535}
536
537#[cfg(test)]
538mod test {
539    use super::*;
540    use std::time::{Duration, Instant};
541
542    #[test]
543    pub fn test_timeout_next_tick() {
544        let mut t = timer();
545        let mut tick;
546
547        t.set_timeout_at(Duration::from_millis(100), "a");
548
549        tick = ms_to_tick(&t, 50);
550        assert_eq!(None, t.poll_to(tick));
551
552        tick = ms_to_tick(&t, 100);
553        assert_eq!(Some("a"), t.poll_to(tick));
554        assert_eq!(None, t.poll_to(tick));
555
556        tick = ms_to_tick(&t, 150);
557        assert_eq!(None, t.poll_to(tick));
558
559        tick = ms_to_tick(&t, 200);
560        assert_eq!(None, t.poll_to(tick));
561
562        assert_eq!(count(&t), 0);
563    }
564
565    #[test]
566    pub fn test_clearing_timeout() {
567        let mut t = timer();
568        let mut tick;
569
570        let to = t.set_timeout_at(Duration::from_millis(100), "a");
571        assert_eq!("a", t.cancel_timeout(&to).unwrap());
572
573        tick = ms_to_tick(&t, 100);
574        assert_eq!(None, t.poll_to(tick));
575
576        tick = ms_to_tick(&t, 200);
577        assert_eq!(None, t.poll_to(tick));
578
579        assert_eq!(count(&t), 0);
580    }
581
582    #[test]
583    pub fn test_multiple_timeouts_same_tick() {
584        let mut t = timer();
585        let mut tick;
586
587        t.set_timeout_at(Duration::from_millis(100), "a");
588        t.set_timeout_at(Duration::from_millis(100), "b");
589
590        let mut rcv = vec![];
591
592        tick = ms_to_tick(&t, 100);
593        rcv.push(t.poll_to(tick).unwrap());
594        rcv.push(t.poll_to(tick).unwrap());
595
596        assert_eq!(None, t.poll_to(tick));
597
598        rcv.sort();
599        assert!(rcv == ["a", "b"], "actual={:?}", rcv);
600
601        tick = ms_to_tick(&t, 200);
602        assert_eq!(None, t.poll_to(tick));
603
604        assert_eq!(count(&t), 0);
605    }
606
607    #[test]
608    pub fn test_multiple_timeouts_diff_tick() {
609        let mut t = timer();
610        let mut tick;
611
612        t.set_timeout_at(Duration::from_millis(110), "a");
613        t.set_timeout_at(Duration::from_millis(220), "b");
614        t.set_timeout_at(Duration::from_millis(230), "c");
615        t.set_timeout_at(Duration::from_millis(440), "d");
616        t.set_timeout_at(Duration::from_millis(560), "e");
617
618        tick = ms_to_tick(&t, 100);
619        assert_eq!(Some("a"), t.poll_to(tick));
620        assert_eq!(None, t.poll_to(tick));
621
622        tick = ms_to_tick(&t, 200);
623        assert_eq!(Some("c"), t.poll_to(tick));
624        assert_eq!(Some("b"), t.poll_to(tick));
625        assert_eq!(None, t.poll_to(tick));
626
627        tick = ms_to_tick(&t, 300);
628        assert_eq!(None, t.poll_to(tick));
629
630        tick = ms_to_tick(&t, 400);
631        assert_eq!(Some("d"), t.poll_to(tick));
632        assert_eq!(None, t.poll_to(tick));
633
634        tick = ms_to_tick(&t, 500);
635        assert_eq!(None, t.poll_to(tick));
636
637        tick = ms_to_tick(&t, 600);
638        assert_eq!(Some("e"), t.poll_to(tick));
639        assert_eq!(None, t.poll_to(tick));
640    }
641
642    #[test]
643    pub fn test_catching_up() {
644        let mut t = timer();
645
646        t.set_timeout_at(Duration::from_millis(110), "a");
647        t.set_timeout_at(Duration::from_millis(220), "b");
648        t.set_timeout_at(Duration::from_millis(230), "c");
649        t.set_timeout_at(Duration::from_millis(440), "d");
650
651        let tick = ms_to_tick(&t, 600);
652        assert_eq!(Some("a"), t.poll_to(tick));
653        assert_eq!(Some("c"), t.poll_to(tick));
654        assert_eq!(Some("b"), t.poll_to(tick));
655        assert_eq!(Some("d"), t.poll_to(tick));
656        assert_eq!(None, t.poll_to(tick));
657    }
658
659    #[test]
660    pub fn test_timeout_hash_collision() {
661        let mut t = timer();
662        let mut tick;
663
664        t.set_timeout_at(Duration::from_millis(100), "a");
665        t.set_timeout_at(Duration::from_millis(100 + TICK * SLOTS as u64), "b");
666
667        tick = ms_to_tick(&t, 100);
668        assert_eq!(Some("a"), t.poll_to(tick));
669        assert_eq!(1, count(&t));
670
671        tick = ms_to_tick(&t, 200);
672        assert_eq!(None, t.poll_to(tick));
673        assert_eq!(1, count(&t));
674
675        tick = ms_to_tick(&t, 100 + TICK * SLOTS as u64);
676        assert_eq!(Some("b"), t.poll_to(tick));
677        assert_eq!(0, count(&t));
678    }
679
680    #[test]
681    pub fn test_clearing_timeout_between_triggers() {
682        let mut t = timer();
683        let mut tick;
684
685        let a = t.set_timeout_at(Duration::from_millis(100), "a");
686        let _ = t.set_timeout_at(Duration::from_millis(100), "b");
687        let _ = t.set_timeout_at(Duration::from_millis(200), "c");
688
689        tick = ms_to_tick(&t, 100);
690        assert_eq!(Some("b"), t.poll_to(tick));
691        assert_eq!(2, count(&t));
692
693        t.cancel_timeout(&a);
694        assert_eq!(1, count(&t));
695
696        assert_eq!(None, t.poll_to(tick));
697
698        tick = ms_to_tick(&t, 200);
699        assert_eq!(Some("c"), t.poll_to(tick));
700        assert_eq!(0, count(&t));
701    }
702
703    const TICK: u64 = 100;
704    const SLOTS: usize = 16;
705    const CAPACITY: usize = 32;
706
707    fn count<T>(timer: &Timer<T>) -> usize {
708        timer.entries.len()
709    }
710
711    fn timer() -> Timer<&'static str> {
712        Timer::new(TICK, SLOTS, CAPACITY, Instant::now())
713    }
714
715    fn ms_to_tick<T>(timer: &Timer<T>, ms: u64) -> u64 {
716        ms / timer.tick_ms
717    }
718}