Add a timer wheel

This commit is contained in:
Alex Crichton
2016-08-03 22:57:07 -07:00
parent 1d7098eece
commit 5c9daad88b
6 changed files with 740 additions and 30 deletions
+210 -30
View File
@@ -1,17 +1,19 @@
use std::cell::{Cell, RefCell};
use std::io::{self, ErrorKind};
use std::mem;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering};
use std::sync::mpsc;
use std::time::Instant;
use std::time::{Instant, Duration};
use mio;
use mio::channel::SendError;
use slab::Slab;
use futures::{Future, Task, TaskHandle, Poll};
use futures_io::Ready;
use mio::channel::SendError;
use mio;
use slab::Slab;
use slot::{self, Slot};
use timer_wheel::{TimerWheel, Timeout};
static NEXT_LOOP_ID: AtomicUsize = ATOMIC_USIZE_INIT;
scoped_thread_local!(static CURRENT_LOOP: Loop);
@@ -32,6 +34,15 @@ pub struct Loop {
tx: mio::channel::Sender<Message>,
rx: mio::channel::Receiver<Message>,
dispatch: RefCell<Slab<Scheduled, usize>>,
// Timer wheel keeping track of all timeouts. The `usize` stored in the
// timer wheel is an index into the slab below.
//
// The slab below keeps track of the timeouts themselves as well as the
// state of the timeout itself. The `TimeoutToken` type is an index into the
// `timeouts` slab.
timer_wheel: RefCell<TimerWheel<usize>>,
timeouts: RefCell<Slab<(Timeout, TimeoutState), usize>>,
}
/// Handle to an event loop, used to construct I/O objects, send messages, and
@@ -50,11 +61,20 @@ struct Scheduled {
waiter: Option<TaskHandle>,
}
enum TimeoutState {
NotFired,
Fired,
Waiting(TaskHandle),
}
enum Message {
AddSource(IoSource, Arc<Slot<io::Result<usize>>>),
DropSource(usize),
Schedule(usize, TaskHandle),
Deschedule(usize),
AddTimeout(Instant, Arc<Slot<io::Result<TimeoutToken>>>),
UpdateTimeout(TimeoutToken, TaskHandle),
CancelTimeout(TimeoutToken),
Shutdown,
}
@@ -96,6 +116,8 @@ impl Loop {
tx: tx,
rx: rx,
dispatch: RefCell::new(Slab::new_starting_at(1, SLAB_CAPACITY)),
timeouts: RefCell::new(Slab::new_starting_at(0, SLAB_CAPACITY)),
timer_wheel: RefCell::new(TimerWheel::new()),
})
}
@@ -138,7 +160,14 @@ impl Loop {
// attaching strace, or similar.
let start = Instant::now();
loop {
match self.io.poll(&mut events, None) {
let timeout = self.timer_wheel.borrow().next_timeout().map(|t| {
if t < start {
Duration::new(0, 0)
} else {
t - start
}
});
match self.io.poll(&mut events, timeout) {
Ok(a) => {
amt = a;
break;
@@ -151,21 +180,29 @@ impl Loop {
}
debug!("loop poll - {:?}", start.elapsed());
// TODO: coalesce token sets for a given Wake?
// First up, process all timeouts that may have just occurred.
let start = Instant::now();
self.consume_timeouts(start);
// Next, process all the events that came in.
for i in 0..events.len() {
let event = events.get(i).unwrap();
let token = usize::from(event.token());
// Token 0 == our incoming message queue, so this means we
// process the whole queue of messages.
if token == 0 {
debug!("consuming notification queue");
self.consume_queue();
continue
}
// For any other token we look at `dispatch` to see what we're
// supposed to do. If there's a waiter we get ready to notify
// it, and we also or-in atomically any events that have
// happened (currently read/write events).
let mut waiter = None;
if let Some(sched) = self.dispatch.borrow_mut().get_mut(token) {
if let Some(sched) = self.dispatch.get_mut().get_mut(token) {
waiter = sched.waiter.take();
if event.kind().is_readable() {
sched.source.readiness.fetch_or(1, Ordering::Relaxed);
@@ -176,14 +213,11 @@ impl Loop {
} else {
debug!("notified on {} which no longer exists", token);
}
debug!("dispatching {:?} {:?}", event.token(), event.kind());
CURRENT_LOOP.set(&self, move || {
match waiter {
Some(waiter) => waiter.notify(),
None => debug!("no waiter"),
}
});
// If we actually got a waiter, then notify!
if let Some(waiter) = waiter {
self.notify_handle(waiter);
}
}
debug!("loop process - {} events, {:?}", amt, start.elapsed());
@@ -192,6 +226,24 @@ impl Loop {
debug!("loop is done!");
}
fn consume_timeouts(&mut self, now: Instant) {
while let Some(idx) = self.timer_wheel.get_mut().poll(now) {
trace!("firing timeout: {}", idx);
let handle = self.timeouts.get_mut()[idx].1.fire();
if let Some(handle) = handle {
self.notify_handle(handle);
}
}
}
/// Method used to notify a task handle.
///
/// Note that this should be used instead fo `handle.notify()` to ensure
/// that the `CURRENT_LOOP` variable is set appropriately.
fn notify_handle(&self, handle: TaskHandle) {
CURRENT_LOOP.set(&self, || handle.notify());
}
fn add_source(&self, source: IoSource) -> io::Result<usize> {
let sched = Scheduled {
source: source,
@@ -225,7 +277,7 @@ impl Loop {
}
};
if let Some(to_call) = to_call {
to_call.notify();
self.notify_handle(to_call);
}
}
@@ -235,6 +287,32 @@ impl Loop {
sched.waiter = None;
}
fn add_timeout(&self, at: Instant) -> io::Result<TimeoutToken> {
let mut timeouts = self.timeouts.borrow_mut();
if timeouts.vacant_entry().is_none() {
let len = timeouts.count();
timeouts.grow(len);
}
let entry = timeouts.vacant_entry().unwrap();
let timeout = self.timer_wheel.borrow_mut().insert(at, entry.index());
let entry = entry.insert((timeout, TimeoutState::NotFired));
Ok(TimeoutToken { token: entry.index() })
}
fn update_timeout(&self, token: &TimeoutToken, handle: TaskHandle) {
let to_wake = self.timeouts.borrow_mut()[token.token].1.block(handle);
if let Some(to_wake) = to_wake {
self.notify_handle(to_wake);
}
}
fn cancel_timeout(&self, token: &TimeoutToken) {
let pair = self.timeouts.borrow_mut().remove(token.token);
if let Some((timeout, _state)) = pair {
self.timer_wheel.borrow_mut().cancel(&timeout);
}
}
fn consume_queue(&self) {
while let Ok(msg) = self.rx.try_recv() {
self.notify(msg);
@@ -252,6 +330,13 @@ impl Loop {
Message::Schedule(tok, wake) => self.schedule(tok, wake),
Message::Deschedule(tok) => self.deschedule(tok),
Message::Shutdown => self.active.set(false),
Message::AddTimeout(at, slot) => {
slot.try_produce(self.add_timeout(at))
.ok().expect("interference with try_produce on timeout");
}
Message::UpdateTimeout(t, handle) => self.update_timeout(&t, handle),
Message::CancelTimeout(t) => self.cancel_timeout(&t),
}
}
}
@@ -323,16 +408,14 @@ impl LoopHandle {
/// with the event loop.
pub fn add_source(&self, source: IoSource) -> AddSource {
AddSource {
loop_handle: self.clone(),
source: Some(source),
result: None,
inner: LoopFuture {
loop_handle: self.clone(),
data: Some(source),
result: None,
}
}
}
fn add_source_(&self, source: IoSource, slot: Arc<Slot<io::Result<usize>>>) {
self.send(Message::AddSource(source, slot));
}
/// Begin listening for events on an event loop.
///
/// Once an I/O object has been registered with the event loop through the
@@ -394,6 +477,40 @@ impl LoopHandle {
self.send(Message::DropSource(tok));
}
/// Adds a new timeout to get fired at the specified instant, notifying the
/// specified task.
pub fn add_timeout(&self, at: Instant) -> AddTimeout {
AddTimeout {
inner: LoopFuture {
loop_handle: self.clone(),
data: Some(at),
result: None,
},
}
}
/// Updates a previously added timeout to notify a new task instead.
///
/// # Panics
///
/// This method will panic if the timeout specified was not created by this
/// loop handle's `add_timeout` method.
pub fn update_timeout(&self, timeout: &TimeoutToken, task: &mut Task) {
let timeout = TimeoutToken { token: timeout.token };
self.send(Message::UpdateTimeout(timeout, task.handle().clone()))
}
/// Cancel a previously added timeout.
///
/// # Panics
///
/// This method will panic if the timeout specified was not created by this
/// loop handle's `add_timeout` method.
pub fn cancel_timeout(&self, timeout: &TimeoutToken) {
let timeout = TimeoutToken { token: timeout.token };
self.send(Message::CancelTimeout(timeout))
}
/// Send a message to the associated event loop that it should shut down, or
/// otherwise break out of its current loop of iteration.
///
@@ -417,9 +534,7 @@ impl LoopHandle {
/// Created through the `LoopHandle::add_source` method, this future can also
/// resolve to an error if there's an issue communicating with the event loop.
pub struct AddSource {
loop_handle: LoopHandle,
source: Option<IoSource>,
result: Option<(Arc<Slot<io::Result<usize>>>, slot::Token)>,
inner: LoopFuture<usize, IoSource>,
}
impl Future for AddSource {
@@ -427,6 +542,50 @@ impl Future for AddSource {
type Error = io::Error;
fn poll(&mut self, _task: &mut Task) -> Poll<usize, io::Error> {
self.inner.poll(Loop::add_source)
}
fn schedule(&mut self, task: &mut Task) {
self.inner.schedule(task, Message::AddSource)
}
}
/// Return value from the `LoopHandle::add_timeout` method, a future that will
/// resolve to a `TimeoutToken` to configure the behavior of that timeout.
pub struct AddTimeout {
inner: LoopFuture<TimeoutToken, Instant>,
}
/// A token that identifies an active timeout.
pub struct TimeoutToken {
token: usize,
}
impl Future for AddTimeout {
type Item = TimeoutToken;
type Error = io::Error;
fn poll(&mut self, _task: &mut Task) -> Poll<TimeoutToken, io::Error> {
self.inner.poll(Loop::add_timeout)
}
fn schedule(&mut self, task: &mut Task) {
self.inner.schedule(task, Message::AddTimeout)
}
}
struct LoopFuture<T, U> {
loop_handle: LoopHandle,
data: Option<U>,
result: Option<(Arc<Slot<io::Result<T>>>, slot::Token)>,
}
impl<T, U> LoopFuture<T, U>
where T: Send + 'static,
{
fn poll<F>(&mut self, f: F) -> Poll<T, io::Error>
where F: FnOnce(&Loop, U) -> io::Result<T>,
{
match self.result {
Some((ref result, ref token)) => {
result.cancel(*token);
@@ -436,10 +595,10 @@ impl Future for AddSource {
}
}
None => {
let source = &mut self.source;
let data = &mut self.data;
self.loop_handle.with_loop(|lp| {
match lp {
Some(lp) => lp.add_source(source.take().unwrap()).into(),
Some(lp) => f(lp, data.take().unwrap()).into(),
None => Poll::NotReady,
}
})
@@ -447,7 +606,9 @@ impl Future for AddSource {
}
}
fn schedule(&mut self, task: &mut Task) {
fn schedule<F>(&mut self, task: &mut Task, f: F)
where F: FnOnce(U, Arc<Slot<io::Result<T>>>) -> Message,
{
if let Some((ref result, ref mut token)) = self.result {
result.cancel(*token);
let handle = task.handle().clone();
@@ -463,7 +624,26 @@ impl Future for AddSource {
handle.notify();
});
self.result = Some((result.clone(), token));
self.loop_handle.add_source_(self.source.take().unwrap(), result);
self.loop_handle.send(f(self.data.take().unwrap(), result))
}
}
impl TimeoutState {
fn block(&mut self, handle: TaskHandle) -> Option<TaskHandle> {
match *self {
TimeoutState::Fired => return Some(handle),
_ => {}
}
*self = TimeoutState::Waiting(handle);
None
}
fn fire(&mut self) -> Option<TaskHandle> {
match mem::replace(self, TimeoutState::Fired) {
TimeoutState::NotFired => None,
TimeoutState::Fired => panic!("fired twice?"),
TimeoutState::Waiting(handle) => Some(handle),
}
}
}
+3
View File
@@ -23,6 +23,8 @@ mod readiness_stream;
mod event_loop;
mod tcp;
mod udp;
mod timeout;
pub mod timer_wheel;
#[path = "../../src/slot.rs"]
mod slot;
#[path = "../../src/lock.rs"]
@@ -31,4 +33,5 @@ mod lock;
pub use event_loop::{Loop, LoopHandle};
pub use readiness_stream::ReadinessStream;
pub use tcp::{TcpListener, TcpStream};
pub use timeout::Timeout;
pub use udp::UdpSocket;
+71
View File
@@ -0,0 +1,71 @@
use std::io;
use std::time::{Duration, Instant};
use futures::{Future, Task, Poll};
use futures_io::IoFuture;
use LoopHandle;
use event_loop::TimeoutToken;
/// A future representing the notification that a timeout has occurred.
///
/// Timeouts are created through the `LoopHandle::timeout` or
/// `LoopHandle::timeout_at` methods indicating when a timeout should fire at.
/// Note that timeouts are not intended for high resolution timers, but rather
/// they will likely fire some granularity after the exact instant that they're
/// otherwise indicated to fire at.
pub struct Timeout {
at: Instant,
token: TimeoutToken,
handle: LoopHandle,
}
impl LoopHandle {
/// Creates a new timeout which will fire at `dur` time into the future.
///
/// This function will return a future that will resolve to the actual
/// timeout object. The timeout object itself is then a future which will be
/// set to fire at the specified point in the future.
pub fn timeout(self, dur: Duration) -> Box<IoFuture<Timeout>> {
self.timeout_at(Instant::now() + dur)
}
/// Creates a new timeout which will fire at the time specified by `at`.
///
/// This function will return a future that will resolve to the actual
/// timeout object. The timeout object itself is then a future which will be
/// set to fire at the specified point in the future.
pub fn timeout_at(self, at: Instant) -> Box<IoFuture<Timeout>> {
self.add_timeout(at).map(move |token| {
Timeout {
at: at,
token: token,
handle: self,
}
}).boxed()
}
}
impl Future for Timeout {
type Item = ();
type Error = io::Error;
fn poll(&mut self, _task: &mut Task) -> Poll<(), io::Error> {
// TODO: is this fast enough?
if self.at <= Instant::now() {
Poll::Ok(())
} else {
Poll::NotReady
}
}
fn schedule(&mut self, task: &mut Task) {
self.handle.update_timeout(&self.token, task);
}
}
impl Drop for Timeout {
fn drop(&mut self) {
self.handle.cancel_timeout(&self.token);
}
}
+294
View File
@@ -0,0 +1,294 @@
//! A timer wheel implementation
use std::cmp;
use std::mem;
use std::time::{Instant, Duration};
use slab::Slab;
/// An implementation of a timer wheel where data can be associated with each
/// timer firing.
///
/// This structure implements a timer wheel data structure where each timeout
/// has a piece of associated data, `T`. A timer wheel supports O(1) insertion
/// and removal of timers, as well as quickly figuring out what needs to get
/// fired.
///
/// Note, though, that the resolution of a timer wheel means that timeouts will
/// not arrive promptly when they expire, but rather in certain increments of
/// each time. The time delta between each slot of a time wheel is of a fixed
/// length, meaning that if a timeout is scheduled between two slots it'll end
/// up getting scheduled into the later slot.
pub struct TimerWheel<T> {
// Actual timer wheel itself.
//
// Each slot represents a fixed duration of time, and this wheel also
// behaves like a ring buffer. All timeouts scheduled will correspond to one
// slot and therefore each slot has a linked list of timeouts scheduled in
// it. Right now linked lists are done through indices into the `slab`
// below.
//
// Each slot also contains the next timeout associated with it (the minimum
// of the entire linked list).
wheel: Vec<Slot>,
// A slab containing all the timeout entries themselves. This is the memory
// backing the "linked lists" in the wheel above. Each entry has a prev/next
// pointer (indices in this array) along with the data associated with the
// timeout and the time the timeout will fire.
slab: Slab<Entry<T>, usize>,
// The instant that this timer was created, through which all other timeout
// computations are relative to.
start: Instant,
// State used during `poll`. The `cur_wheel_tick` field is the current tick
// we've poll'd to. That is, all events from `cur_wheel_tick` to the
// actual current tick in time still need to be processed.
//
// The `cur_slab_idx` variable is basically just an iterator over the linked
// list associated with a wheel slot. This will get incremented as we move
// forward in `poll`
cur_wheel_tick: u64,
cur_slab_idx: usize,
}
#[derive(Clone)]
struct Slot {
head: usize,
next_timeout: Option<Instant>,
}
struct Entry<T> {
data: T,
when: Instant,
prev: usize,
next: usize,
}
/// A timeout which has been scheduled with a timer wheel.
///
/// This can be used to later cancel a timeout, if necessary.
pub struct Timeout {
when: Instant,
slab_idx: usize,
}
const EMPTY: usize = 0;
const LEN: usize = 256;
const MASK: usize = LEN - 1;
const TICK_MS: u64 = 100;
impl<T> TimerWheel<T> {
/// Creates a new timer wheel configured with no timeouts and with the
/// default parameters.
///
/// Currently this is a timer wheel of length 256 with a 100ms time
/// resolution.
pub fn new() -> TimerWheel<T> {
TimerWheel {
wheel: vec![Slot { head: EMPTY, next_timeout: None }; LEN],
slab: Slab::new_starting_at(1, 256),
start: Instant::now(),
cur_wheel_tick: 0,
cur_slab_idx: EMPTY,
}
}
/// Creates a new timeout to get fired at a particular point in the future.
///
/// The timeout will be associated with the specified `data`, and this data
/// will be returned from `poll` when it's ready.
///
/// The returned `Timeout` can later get passesd to `cancel` to retrieve the
/// data and ensure the timeout doesn't fire.
///
/// This method completes in O(1) time.
///
/// # Panics
///
/// This method will panic if `at` is before the time that this timer wheel
/// was created.
pub fn insert(&mut self, at: Instant, data: T) -> Timeout {
// First up, figure out where we're gonna go in the wheel. Note that if
// we're being scheduled on or before the current wheel tick we just
// make sure to defer ourselves to the next tick.
let mut tick = self.time_to_ticks(at);
if tick <= self.cur_wheel_tick {
debug!("moving {} to {}", tick, self.cur_wheel_tick + 1);
tick = self.cur_wheel_tick + 1;
}
let wheel_idx = self.ticks_to_wheel_idx(tick);
trace!("inserting timeout at {} for {}", wheel_idx, tick);
// Next, make sure there's enough space in the slab for the timeout.
if self.slab.vacant_entry().is_none() {
let amt = self.slab.count();
self.slab.grow(amt);
}
// Insert ourselves at the head of the linked list in the wheel.
let slot = &mut self.wheel[wheel_idx];
let prev_head;
{
let entry = self.slab.vacant_entry().unwrap();
prev_head = mem::replace(&mut slot.head, entry.index());
entry.insert(Entry {
data: data,
when: at,
prev: EMPTY,
next: prev_head,
});
}
if prev_head != EMPTY {
self.slab[prev_head].prev = slot.head;
}
// Update the wheel slot's next timeout field.
if at <= slot.next_timeout.unwrap_or(at) {
let tick = tick as u32;
let actual_tick = self.start + Duration::from_millis(TICK_MS) * tick;
let at = cmp::max(actual_tick, at);
slot.next_timeout = Some(at);
}
Timeout {
when: at,
slab_idx: slot.head,
}
}
/// Queries this timer to see if any timeouts are ready to fire.
///
/// This function will advance the internal wheel to the time specified by
/// `at`, returning any timeout which has happened up to that point. This
/// method should be called in a loop until it returns `None` to ensure that
/// all timeouts are processed.
///
/// # Panics
///
/// This method will panic if `at` is before the instant that this timer
/// wheel was created.
pub fn poll(&mut self, at: Instant) -> Option<T> {
let wheel_tick = self.time_to_ticks(at);
trace!("polling {} => {}", self.cur_wheel_tick, wheel_tick);
// Advance forward in time to the `wheel_tick` specified.
//
// TODO: don't visit slots in the wheel more than once
while self.cur_wheel_tick <= wheel_tick {
let head = self.cur_slab_idx;
trace!("next head[{} => {}]: {}",
self.cur_wheel_tick, wheel_tick, head);
// If the current slot has no entries or we're done iterating go to
// the next tick.
if head == EMPTY {
self.cur_wheel_tick += 1;
let idx = self.ticks_to_wheel_idx(self.cur_wheel_tick);
self.cur_slab_idx = self.wheel[idx].head;
continue
}
// If we're starting to iterate over a slot, clear its timeout as
// we're probably going to remove entries. As we skip over each
// element of this slot we'll restore the `next_timeout` field if
// necessary.
let idx = self.ticks_to_wheel_idx(self.cur_wheel_tick);
if head == self.wheel[idx].head {
self.wheel[idx].next_timeout = None;
}
// Otherwise, continue iterating over the linked list in the wheel
// slot we're on and remove anything which has expired.
self.cur_slab_idx = self.slab[head].next;
let head_timeout = self.slab[head].when;
if self.time_to_ticks(head_timeout) <= self.time_to_ticks(at) {
return self.remove_slab(head).map(|e| e.data)
} else {
let next = self.wheel[idx].next_timeout.unwrap_or(head_timeout);
if head_timeout <= next {
self.wheel[idx].next_timeout = Some(head_timeout);
}
}
}
None
}
/// Returns the instant in time that corresponds to the next timeout
/// scheduled in this wheel.
pub fn next_timeout(&self) -> Option<Instant> {
// TODO: can this be optimized to not look at the whole array?
let timeouts = self.wheel.iter().map(|slot| slot.next_timeout);
let min = timeouts.fold(None, |prev, cur| {
match (prev, cur) {
(None, cur) => cur,
(Some(time), None) => Some(time),
(Some(a), Some(b)) => Some(cmp::min(a, b)),
}
});
let time = min.map(|min| min + Duration::from_millis(TICK_MS / 2));
if let Some(time) = time {
debug!("next timeout {:?}", time);
debug!("now {:?}", Instant::now());
}
return time
}
/// Cancels the specified timeout.
///
/// For timeouts previously registered via `insert` they can be passed back
/// to this method to cancel the associated timeout, retrieving the value
/// inserted if the timeout has not already fired.
///
/// This method completes in O(1) time.
///
/// # Panics
///
/// This method may panic if `timeout` wasn't created by this timer wheel.
pub fn cancel(&mut self, timeout: &Timeout) -> Option<T> {
match self.slab.get(timeout.slab_idx) {
Some(e) if e.when == timeout.when => {}
_ => return None,
}
self.remove_slab(timeout.slab_idx).map(|e| e.data)
}
fn remove_slab(&mut self, slab_idx: usize) -> Option<Entry<T>> {
let entry = match self.slab.remove(slab_idx) {
Some(e) => e,
None => return None,
};
// Remove the node from the linked list
if entry.prev == EMPTY {
let idx = self.ticks_to_wheel_idx(self.time_to_ticks(entry.when));
self.wheel[idx].head = entry.next;
} else {
self.slab[entry.prev].next = entry.next;
}
if entry.next != EMPTY {
self.slab[entry.next].prev = entry.prev;
}
return Some(entry)
}
fn time_to_ticks(&self, time: Instant) -> u64 {
let dur = time - self.start;
let ms = dur.subsec_nanos() as u64 / 1_000_000;
let ms = dur.as_secs()
.checked_mul(1_000)
.and_then(|m| m.checked_add(ms))
.expect("overflow scheduling timeout");
(ms + TICK_MS / 2) / TICK_MS
}
fn ticks_to_wheel_idx(&self, ticks: u64) -> usize {
(ticks as usize) & MASK
}
}
+25
View File
@@ -0,0 +1,25 @@
extern crate env_logger;
extern crate futures;
extern crate futures_mio;
use std::time::{Instant, Duration};
use futures::Future;
macro_rules! t {
($e:expr) => (match $e {
Ok(e) => e,
Err(e) => panic!("{} failed with {:?}", stringify!($e), e),
})
}
#[test]
fn smoke() {
drop(env_logger::init());
let mut l = t!(futures_mio::Loop::new());
let dur = Duration::from_millis(10);
let timeout = l.handle().timeout(dur).and_then(|t| t);
let start = Instant::now();
t!(l.run(timeout));
assert!(start.elapsed() >= dur);
}
+137
View File
@@ -0,0 +1,137 @@
extern crate env_logger;
extern crate futures_mio;
use std::time::{Instant, Duration};
use futures_mio::timer_wheel::TimerWheel;
fn ms(amt: u64) -> Duration {
Duration::from_millis(amt)
}
#[test]
fn smoke() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
assert!(timer.poll(now).is_none());
assert!(timer.poll(now).is_none());
timer.insert(now + ms(200), 3);
assert!(timer.poll(now).is_none());
assert!(timer.poll(now + ms(100)).is_none());
let res = timer.poll(now + ms(200));
assert!(res.is_some());
assert_eq!(res.unwrap(), 3);
}
#[test]
fn poll_past_done() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
timer.insert(now + ms(200), 3);
let res = timer.poll(now + ms(300));
assert!(res.is_some());
assert_eq!(res.unwrap(), 3);
}
#[test]
fn multiple_ready() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
timer.insert(now + ms(200), 3);
timer.insert(now + ms(201), 4);
timer.insert(now + ms(202), 5);
timer.insert(now + ms(300), 6);
timer.insert(now + ms(301), 7);
let mut found = Vec::new();
while let Some(i) = timer.poll(now + ms(400)) {
found.push(i);
}
found.sort();
assert_eq!(found, [3, 4, 5, 6, 7]);
}
#[test]
fn poll_now() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
timer.insert(now, 3);
let res = timer.poll(now + ms(100));
assert!(res.is_some());
assert_eq!(res.unwrap(), 3);
}
#[test]
fn cancel() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
let timeout = timer.insert(now + ms(800), 3);
assert!(timer.poll(now + ms(200)).is_none());
assert!(timer.poll(now + ms(400)).is_none());
assert_eq!(timer.cancel(&timeout), Some(3));
assert!(timer.poll(now + ms(600)).is_none());
assert!(timer.poll(now + ms(800)).is_none());
assert!(timer.poll(now + ms(1000)).is_none());
}
#[test]
fn next_timeout() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
assert!(timer.next_timeout().is_none());
timer.insert(now + ms(400), 3);
let timeout = timer.next_timeout().expect("wanted a next_timeout");
assert_eq!(timeout, now + ms(400));
timer.insert(now + ms(1000), 3);
let timeout = timer.next_timeout().expect("wanted a next_timeout");
assert_eq!(timeout, now + ms(400));
}
#[test]
fn around_the_boundary() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
timer.insert(now + ms(199), 3);
timer.insert(now + ms(200), 4);
timer.insert(now + ms(201), 5);
timer.insert(now + ms(251), 6);
let mut found = Vec::new();
while let Some(i) = timer.poll(now + ms(200)) {
found.push(i);
}
found.sort();
assert_eq!(found, [3, 4, 5]);
assert_eq!(timer.poll(now + ms(300)), Some(6));
assert_eq!(timer.poll(now + ms(300)), None);
}
#[test]
fn remove_clears_timeout() {
drop(env_logger::init());
let mut timer = TimerWheel::<i32>::new();
let now = Instant::now();
timer.insert(now + ms(100), 3);
assert_eq!(timer.next_timeout(), Some(now + ms(100)));
assert_eq!(timer.poll(now + ms(200)), Some(3));
assert_eq!(timer.next_timeout(), None);
}