mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-29 00:00:11 +02:00
Provide a timer implementation (#249)
This patch adds a new crate: tokio-timer. This crate provides an efficient timer implemeentation designed for use in Tokio based applications. The timer users a hierarchical hashed timer wheel algorithm with six levels, each having 64 slots. This allows the timer to have a resolution of 1ms while maintaining O(1) complexity for insert, removal, and firing of timeouts. There already exists a tokio-timer crate. This is a complete rewrite which solves the outstanding problems with the existing tokio-timer library. Closes #146.
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
extern crate futures;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_timer;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
use support::*;
|
||||
|
||||
use tokio_timer::*;
|
||||
|
||||
use futures::{future, Future};
|
||||
use futures::sync::oneshot;
|
||||
|
||||
#[test]
|
||||
fn simultaneous_deadline_future_completion() {
|
||||
mocked(|_, time| {
|
||||
// Create a future that is immediately ready
|
||||
let fut = future::ok::<_, ()>(());
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(fut, time.now());
|
||||
|
||||
// Ready!
|
||||
assert_ready!(fut);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_future_past_deadline() {
|
||||
mocked(|_, time| {
|
||||
// Create a future that is immediately ready
|
||||
let fut = future::ok::<_, ()>(());
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(fut, time.now() - ms(1000));
|
||||
|
||||
// Ready!
|
||||
assert_ready!(fut);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_and_deadline_in_future() {
|
||||
mocked(|timer, time| {
|
||||
// Not yet complete
|
||||
let (tx, rx) = oneshot::channel();
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(rx, time.now() + ms(100));
|
||||
|
||||
// Ready!
|
||||
assert_not_ready!(fut);
|
||||
|
||||
// Turn the timer, it runs for the elapsed time
|
||||
advance(timer, ms(90));
|
||||
|
||||
assert_not_ready!(fut);
|
||||
|
||||
// Complete the future
|
||||
tx.send(()).unwrap();
|
||||
|
||||
assert_ready!(fut);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deadline_now_elapses() {
|
||||
mocked(|_, time| {
|
||||
let fut = future::empty::<(), ()>();
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(fut, time.now());
|
||||
|
||||
assert_elapsed!(fut);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deadline_future_elapses() {
|
||||
mocked(|timer, time| {
|
||||
let fut = future::empty::<(), ()>();
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(fut, time.now() + ms(300));
|
||||
|
||||
assert_not_ready!(fut);
|
||||
|
||||
advance(timer, ms(300));
|
||||
|
||||
assert_elapsed!(fut);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn future_errors_first() {
|
||||
mocked(|_, time| {
|
||||
let fut = future::err::<(), ()>(());
|
||||
|
||||
// Wrap it with a deadline
|
||||
let mut fut = Deadline::new(fut, time.now() + ms(100));
|
||||
|
||||
// Ready!
|
||||
assert!(fut.poll().unwrap_err().is_inner());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
extern crate futures;
|
||||
extern crate rand;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_timer;
|
||||
|
||||
use tokio_executor::park::{Park, Unpark, UnparkThread};
|
||||
use tokio_timer::*;
|
||||
|
||||
use futures::{Future, Stream};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use rand::Rng;
|
||||
|
||||
use std::cmp;
|
||||
use std::sync::{Arc, Barrier};
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
struct Signal {
|
||||
rem: AtomicUsize,
|
||||
unpark: UnparkThread,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_complete() {
|
||||
const ITERS: usize = 5;
|
||||
const THREADS: usize = 4;
|
||||
const PER_THREAD: usize = 40;
|
||||
const MIN_DELAY: u64 = 1;
|
||||
const MAX_DELAY: u64 = 5_000;
|
||||
|
||||
for _ in 0..ITERS {
|
||||
let mut timer = Timer::default();
|
||||
let handle = timer.handle();
|
||||
let barrier = Arc::new(Barrier::new(THREADS));
|
||||
|
||||
let done = Arc::new(Signal {
|
||||
rem: AtomicUsize::new(THREADS),
|
||||
unpark: timer.get_park().unpark(),
|
||||
});
|
||||
|
||||
for _ in 0..THREADS {
|
||||
let handle = handle.clone();
|
||||
let barrier = barrier.clone();
|
||||
let done = done.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut exec = FuturesUnordered::new();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
barrier.wait();
|
||||
|
||||
for _ in 0..PER_THREAD {
|
||||
let deadline = Instant::now() + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
exec.push({
|
||||
handle.sleep(deadline)
|
||||
.and_then(move |_| {
|
||||
let now = Instant::now();
|
||||
assert!(now >= deadline, "deadline greater by {:?}", deadline - now);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Run the logic
|
||||
exec.for_each(|_| Ok(()))
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
if 1 == done.rem.fetch_sub(1, SeqCst) {
|
||||
done.unpark.unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
while done.rem.load(SeqCst) > 0 {
|
||||
timer.turn(None).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_cancel() {
|
||||
const ITERS: usize = 5;
|
||||
const THREADS: usize = 4;
|
||||
const PER_THREAD: usize = 40;
|
||||
const MIN_DELAY: u64 = 1;
|
||||
const MAX_DELAY: u64 = 5_000;
|
||||
|
||||
for _ in 0..ITERS {
|
||||
let mut timer = Timer::default();
|
||||
let handle = timer.handle();
|
||||
let barrier = Arc::new(Barrier::new(THREADS));
|
||||
|
||||
let done = Arc::new(Signal {
|
||||
rem: AtomicUsize::new(THREADS),
|
||||
unpark: timer.get_park().unpark(),
|
||||
});
|
||||
|
||||
for _ in 0..THREADS {
|
||||
let handle = handle.clone();
|
||||
let barrier = barrier.clone();
|
||||
let done = done.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut exec = FuturesUnordered::new();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
barrier.wait();
|
||||
|
||||
for _ in 0..PER_THREAD {
|
||||
let deadline1 = Instant::now() + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
let deadline2 = Instant::now() + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
let deadline = cmp::min(deadline1, deadline2);
|
||||
|
||||
let sleep = handle.sleep(deadline1);
|
||||
let join = handle.deadline(sleep, deadline2);
|
||||
|
||||
exec.push({
|
||||
join
|
||||
.and_then(move |_| {
|
||||
let now = Instant::now();
|
||||
assert!(now >= deadline, "deadline greater by {:?}", deadline - now);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Run the logic
|
||||
exec
|
||||
.or_else(|e| {
|
||||
assert!(e.is_elapsed());
|
||||
Ok::<_, ()>(())
|
||||
})
|
||||
.for_each(|_| Ok(()))
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
if 1 == done.rem.fetch_sub(1, SeqCst) {
|
||||
done.unpark.unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
while done.rem.load(SeqCst) > 0 {
|
||||
timer.turn(None).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hammer_reset() {
|
||||
const ITERS: usize = 5;
|
||||
const THREADS: usize = 4;
|
||||
const PER_THREAD: usize = 40;
|
||||
const MIN_DELAY: u64 = 1;
|
||||
const MAX_DELAY: u64 = 250;
|
||||
|
||||
for _ in 0..ITERS {
|
||||
let mut timer = Timer::default();
|
||||
let handle = timer.handle();
|
||||
let barrier = Arc::new(Barrier::new(THREADS));
|
||||
|
||||
let done = Arc::new(Signal {
|
||||
rem: AtomicUsize::new(THREADS),
|
||||
unpark: timer.get_park().unpark(),
|
||||
});
|
||||
|
||||
for _ in 0..THREADS {
|
||||
let handle = handle.clone();
|
||||
let barrier = barrier.clone();
|
||||
let done = done.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut exec = FuturesUnordered::new();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
barrier.wait();
|
||||
|
||||
for _ in 0..PER_THREAD {
|
||||
let deadline1 = Instant::now() + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
let deadline2 = deadline1 + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
let deadline3 = deadline2 + Duration::from_millis(
|
||||
rng.gen_range(MIN_DELAY, MAX_DELAY));
|
||||
|
||||
exec.push({
|
||||
handle.sleep(deadline1)
|
||||
// Select over a second sleep
|
||||
.select2(handle.sleep(deadline2))
|
||||
.map_err(|e| panic!("boom; err={:?}", e))
|
||||
.and_then(move |res| {
|
||||
use futures::future::Either::*;
|
||||
|
||||
let now = Instant::now();
|
||||
assert!(now >= deadline1, "deadline greater by {:?}", deadline1 - now);
|
||||
|
||||
let mut other = match res {
|
||||
A((_, other)) => other,
|
||||
B((_, other)) => other,
|
||||
};
|
||||
|
||||
other.reset(deadline3);
|
||||
other
|
||||
})
|
||||
.and_then(move |_| {
|
||||
let now = Instant::now();
|
||||
assert!(now >= deadline3, "deadline greater by {:?}", deadline3 - now);
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Run the logic
|
||||
exec
|
||||
.for_each(|_| Ok(()))
|
||||
.wait()
|
||||
.unwrap();
|
||||
|
||||
if 1 == done.rem.fetch_sub(1, SeqCst) {
|
||||
done.unpark.unpark();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
while done.rem.load(SeqCst) > 0 {
|
||||
timer.turn(None).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
extern crate futures;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_timer;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
use support::*;
|
||||
|
||||
use tokio_timer::*;
|
||||
|
||||
use futures::{Stream};
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn interval_zero_duration() {
|
||||
mocked(|_, time| {
|
||||
let _ = Interval::new(time.now(), ms(0));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage() {
|
||||
mocked(|timer, time| {
|
||||
let start = time.now();
|
||||
let mut int = Interval::new(start, ms(300));
|
||||
|
||||
assert_ready!(int, Some(start));
|
||||
assert_not_ready!(int);
|
||||
|
||||
advance(timer, ms(100));
|
||||
assert_not_ready!(int);
|
||||
|
||||
advance(timer, ms(200));
|
||||
assert_ready!(int, Some(start + ms(300)));
|
||||
assert_not_ready!(int);
|
||||
|
||||
advance(timer, ms(400));
|
||||
assert_ready!(int, Some(start + ms(600)));
|
||||
assert_not_ready!(int);
|
||||
|
||||
advance(timer, ms(500));
|
||||
assert_ready!(int, Some(start + ms(900)));
|
||||
assert_ready!(int, Some(start + ms(1200)));
|
||||
assert_not_ready!(int);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
extern crate futures;
|
||||
extern crate tokio_executor;
|
||||
extern crate tokio_timer;
|
||||
|
||||
#[macro_use]
|
||||
mod support;
|
||||
use support::*;
|
||||
|
||||
use tokio_timer::*;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[test]
|
||||
fn immediate_sleep() {
|
||||
mocked(|timer, time| {
|
||||
// Create `Sleep` that elapsed immediately.
|
||||
let mut sleep = Sleep::new(time.now());
|
||||
|
||||
// Ready!
|
||||
assert_ready!(sleep);
|
||||
|
||||
// Turn the timer, it runs for the elapsed time
|
||||
turn(timer, ms(1000));
|
||||
|
||||
// The time has not advanced. The `turn` completed immediately.
|
||||
assert_eq!(time.advanced(), ms(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delayed_sleep_level_0() {
|
||||
for &i in &[1, 10, 60] {
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(i));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(i));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_ms_delayed_sleep() {
|
||||
mocked(|timer, time| {
|
||||
for _ in 0..5 {
|
||||
let deadline = time.now()
|
||||
+ Duration::from_millis(1)
|
||||
+ Duration::new(0, 1);
|
||||
|
||||
let mut sleep = Sleep::new(deadline);
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_ready!(sleep);
|
||||
|
||||
assert!(time.now() >= deadline);
|
||||
|
||||
time.advance(Duration::new(0, 1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delayed_sleep_wrapping_level_0() {
|
||||
mocked(|timer, time| {
|
||||
turn(timer, ms(5));
|
||||
assert_eq!(time.advanced(), ms(5));
|
||||
|
||||
let mut sleep = Sleep::new(time.now() + ms(60));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(64));
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(65));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timer_wrapping_with_higher_levels() {
|
||||
mocked(|timer, time| {
|
||||
// Set sleep to hit level 1
|
||||
let mut s1 = Sleep::new(time.now() + ms(64));
|
||||
assert_not_ready!(s1);
|
||||
|
||||
// Turn a bit
|
||||
turn(timer, ms(5));
|
||||
|
||||
// Set timeout such that it will hit level 0, but wrap
|
||||
let mut s2 = Sleep::new(time.now() + ms(60));
|
||||
assert_not_ready!(s2);
|
||||
|
||||
// This should result in s1 firing
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(64));
|
||||
|
||||
assert_ready!(s1);
|
||||
assert_not_ready!(s2);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(65));
|
||||
|
||||
assert_ready!(s2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sleep_with_deadline_in_past() {
|
||||
mocked(|timer, time| {
|
||||
// Create `Sleep` that elapsed immediately.
|
||||
let mut sleep = Sleep::new(time.now() - ms(100));
|
||||
|
||||
// Even though the sleep expires in the past, it is not ready yet
|
||||
// because the timer must observe it.
|
||||
assert_ready!(sleep);
|
||||
|
||||
// Turn the timer, it runs for the elapsed time
|
||||
turn(timer, ms(1000));
|
||||
|
||||
// The time has not advanced. The `turn` completed immediately.
|
||||
assert_eq!(time.advanced(), ms(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delayed_sleep_level_1() {
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(234));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer, this will wake up to cascade the timer down.
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(192));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer again
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(234));
|
||||
|
||||
// The sleep has elapsed.
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(234));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer with a smaller timeout than the cascade.
|
||||
turn(timer, ms(100));
|
||||
assert_eq!(time.advanced(), ms(100));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer, this will wake up to cascade the timer down.
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(192));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer again
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(234));
|
||||
|
||||
// The sleep has elapsed.
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creating_sleep_outside_of_context() {
|
||||
let now = Instant::now();
|
||||
|
||||
// This creates a sleep outside of the context of a mock timer. This tests
|
||||
// that it will still expire.
|
||||
let mut sleep = Sleep::new(now + ms(500));
|
||||
|
||||
mocked_with_now(now, |timer, time| {
|
||||
// This registers the sleep with the timer
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Wait some time... the timer is cascading
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(448));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(500));
|
||||
|
||||
// The sleep has elapsed
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrently_set_two_timers_second_one_shorter() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep1 = Sleep::new(time.now() + ms(500));
|
||||
let mut sleep2 = Sleep::new(time.now() + ms(200));
|
||||
|
||||
// The sleep has not elapsed
|
||||
assert_not_ready!(sleep1);
|
||||
assert_not_ready!(sleep2);
|
||||
|
||||
// Sleep until a cascade
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(192));
|
||||
|
||||
// Sleep until the second timer.
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(200));
|
||||
|
||||
// The shorter sleep fires
|
||||
assert_ready!(sleep2);
|
||||
assert_not_ready!(sleep1);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(448));
|
||||
|
||||
assert_not_ready!(sleep1);
|
||||
|
||||
// Turn again, this time the time will advance to the second sleep
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(500));
|
||||
|
||||
assert_ready!(sleep1);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_sleep() {
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(1));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
// Turn the timer, but not enough timee will go by.
|
||||
turn(timer, None);
|
||||
|
||||
// The sleep has elapsed.
|
||||
assert_ready!(sleep);
|
||||
|
||||
// The time has advanced to the point of the sleep elapsing.
|
||||
assert_eq!(time.advanced(), ms(1));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorta_long_sleep() {
|
||||
const MIN_5: u64 = 5 * 60 * 1000;
|
||||
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(MIN_5));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
let cascades = &[
|
||||
262_144,
|
||||
262_144 + 9 * 4096,
|
||||
262_144 + 9 * 4096 + 15 * 64,
|
||||
];
|
||||
|
||||
for &elapsed in cascades {
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(elapsed));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
}
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(MIN_5));
|
||||
|
||||
// The sleep has elapsed.
|
||||
assert_ready!(sleep);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn very_long_sleep() {
|
||||
const MO_5: u64 = 5 * 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(MO_5));
|
||||
|
||||
// The sleep has not elapsed.
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
let cascades = &[
|
||||
12_884_901_888,
|
||||
12_952_010_752,
|
||||
12_959_875_072,
|
||||
12_959_997_952,
|
||||
];
|
||||
|
||||
for &elapsed in cascades {
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(elapsed));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
}
|
||||
|
||||
// Turn the timer, but not enough time will go by.
|
||||
turn(timer, None);
|
||||
|
||||
// The time has advanced to the point of the sleep elapsing.
|
||||
assert_eq!(time.advanced(), ms(MO_5));
|
||||
|
||||
// The sleep has elapsed.
|
||||
assert_ready!(sleep);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn greater_than_max() {
|
||||
const YR_5: u64 = 5 * 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
mocked(|timer, time| {
|
||||
// Create a `Sleep` that elapses in the future
|
||||
let mut sleep = Sleep::new(time.now() + ms(YR_5));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(0));
|
||||
|
||||
assert!(sleep.poll().is_err());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unpark_is_delayed() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep1 = Sleep::new(time.now() + ms(100));
|
||||
let mut sleep2 = Sleep::new(time.now() + ms(101));
|
||||
let mut sleep3 = Sleep::new(time.now() + ms(200));
|
||||
|
||||
assert_not_ready!(sleep1);
|
||||
assert_not_ready!(sleep2);
|
||||
assert_not_ready!(sleep3);
|
||||
|
||||
time.park_for(ms(500));
|
||||
|
||||
turn(timer, None);
|
||||
|
||||
assert_eq!(time.advanced(), ms(500));
|
||||
|
||||
assert_ready!(sleep1);
|
||||
assert_ready!(sleep2);
|
||||
assert_ready!(sleep3);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_timeout_at_deadline_greater_than_max_timer() {
|
||||
const YR_1: u64 = 365 * 24 * 60 * 60 * 1000;
|
||||
const YR_5: u64 = 5 * YR_1;
|
||||
|
||||
mocked(|timer, time| {
|
||||
for _ in 0..5 {
|
||||
turn(timer, ms(YR_1));
|
||||
}
|
||||
|
||||
let mut sleep = Sleep::new(time.now() + ms(1));
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), Duration::from_millis(YR_5) + ms(1));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_future_sleep_before_fire() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep = Sleep::new(time.now() + ms(100));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
sleep.reset(time.now() + ms(200));
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(192));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(200));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_past_sleep_before_turn() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep = Sleep::new(time.now() + ms(100));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
sleep.reset(time.now() + ms(80));
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(64));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(80));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_past_sleep_before_fire() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep = Sleep::new(time.now() + ms(100));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
turn(timer, ms(10));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
sleep.reset(time.now() + ms(80));
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(64));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(90));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_future_sleep_after_fire() {
|
||||
mocked(|timer, time| {
|
||||
let mut sleep = Sleep::new(time.now() + ms(100));
|
||||
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(64));
|
||||
|
||||
turn(timer, None);
|
||||
assert_eq!(time.advanced(), ms(100));
|
||||
|
||||
assert_ready!(sleep);
|
||||
|
||||
sleep.reset(time.now() + ms(10));
|
||||
assert_not_ready!(sleep);
|
||||
|
||||
turn(timer, ms(1000));
|
||||
assert_eq!(time.advanced(), ms(110));
|
||||
|
||||
assert_ready!(sleep);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
#![allow(unused_macros, unused_imports, dead_code)]
|
||||
|
||||
use tokio_executor::park::{Park, Unpark};
|
||||
use tokio_timer::timer::{Timer, Now};
|
||||
|
||||
use futures::future::{lazy, Future};
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
macro_rules! assert_ready {
|
||||
($f:expr) => {
|
||||
assert!($f.poll().unwrap().is_ready());
|
||||
};
|
||||
($f:expr, $expect:expr) => {
|
||||
assert_eq!($f.poll().unwrap(), ::futures::Async::Ready($expect));
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! assert_not_ready {
|
||||
($f:expr) => {
|
||||
assert!(!$f.poll().unwrap().is_ready());
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! assert_elapsed {
|
||||
($f:expr) => {
|
||||
assert!($f.poll().unwrap_err().is_elapsed());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockTime {
|
||||
inner: Inner,
|
||||
_p: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockNow {
|
||||
inner: Inner,
|
||||
_p: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockPark {
|
||||
inner: Inner,
|
||||
_p: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MockUnpark {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
type Inner = Arc<Mutex<State>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
base: Instant,
|
||||
advance: Duration,
|
||||
unparked: bool,
|
||||
park_for: Option<Duration>,
|
||||
}
|
||||
|
||||
pub fn ms(num: u64) -> Duration {
|
||||
Duration::from_millis(num)
|
||||
}
|
||||
|
||||
pub trait IntoTimeout {
|
||||
fn into_timeout(self) -> Option<Duration>;
|
||||
}
|
||||
|
||||
impl IntoTimeout for Option<Duration> {
|
||||
fn into_timeout(self) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTimeout for Duration {
|
||||
fn into_timeout(self) -> Option<Duration> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Turn the timer state once
|
||||
pub fn turn<T: IntoTimeout>(timer: &mut Timer<MockPark, MockNow>, duration: T) {
|
||||
timer.turn(duration.into_timeout()).unwrap();
|
||||
}
|
||||
|
||||
/// Advance the timer the specified amount
|
||||
pub fn advance(timer: &mut Timer<MockPark, MockNow>, duration: Duration) {
|
||||
let inner = timer.get_park().inner.clone();
|
||||
let deadline = inner.lock().unwrap().now() + duration;
|
||||
|
||||
while inner.lock().unwrap().now() < deadline {
|
||||
let dur = deadline - inner.lock().unwrap().now();
|
||||
turn(timer, dur);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mocked<F, R>(f: F) -> R
|
||||
where F: FnOnce(&mut Timer<MockPark, MockNow>, &mut MockTime) -> R
|
||||
{
|
||||
mocked_with_now(Instant::now(), f)
|
||||
}
|
||||
|
||||
pub fn mocked_with_now<F, R>(now: Instant, f: F) -> R
|
||||
where F: FnOnce(&mut Timer<MockPark, MockNow>, &mut MockTime) -> R
|
||||
{
|
||||
let mut time = MockTime::new(now);
|
||||
let park = time.mock_park();
|
||||
let now = time.mock_now();
|
||||
|
||||
let mut timer = Timer::new_with_now(park, now);
|
||||
let handle = timer.handle();
|
||||
|
||||
let mut enter = ::tokio_executor::enter().unwrap();
|
||||
|
||||
::tokio_timer::with_default(&handle, &mut enter, |_| {
|
||||
lazy(|| {
|
||||
Ok::<_, ()>(f(&mut timer, &mut time))
|
||||
}).wait().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
impl MockTime {
|
||||
pub fn new(now: Instant) -> MockTime {
|
||||
let state = State {
|
||||
base: now,
|
||||
advance: Duration::default(),
|
||||
unparked: false,
|
||||
park_for: None,
|
||||
};
|
||||
|
||||
MockTime {
|
||||
inner: Arc::new(Mutex::new(state)),
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_now(&self) -> MockNow {
|
||||
let inner = self.inner.clone();
|
||||
MockNow {
|
||||
inner,
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_park(&self) -> MockPark {
|
||||
let inner = self.inner.clone();
|
||||
MockPark {
|
||||
inner,
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn now(&self) -> Instant {
|
||||
self.inner.lock().unwrap().now()
|
||||
}
|
||||
|
||||
/// Returns the total amount of time the time has been advanced.
|
||||
pub fn advanced(&self) -> Duration {
|
||||
self.inner.lock().unwrap().advance
|
||||
}
|
||||
|
||||
pub fn advance(&self, duration: Duration) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.advance(duration);
|
||||
}
|
||||
|
||||
/// The next call to park_timeout will be for this duration, regardless of
|
||||
/// the timeout passed to `park_timeout`.
|
||||
pub fn park_for(&self, duration: Duration) {
|
||||
self.inner.lock().unwrap().park_for = Some(duration);
|
||||
}
|
||||
}
|
||||
|
||||
impl Park for MockPark {
|
||||
type Unpark = MockUnpark;
|
||||
type Error = ();
|
||||
|
||||
fn unpark(&self) -> Self::Unpark {
|
||||
let inner = self.inner.clone();
|
||||
MockUnpark { inner }
|
||||
}
|
||||
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
let mut inner = self.inner.lock().map_err(|_| ())?;
|
||||
|
||||
let duration = inner.park_for.take()
|
||||
.expect("call park_for first");
|
||||
|
||||
inner.advance(duration);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
|
||||
if let Some(duration) = inner.park_for.take() {
|
||||
inner.advance(duration);
|
||||
} else {
|
||||
inner.advance(duration);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Unpark for MockUnpark {
|
||||
fn unpark(&self) {
|
||||
if let Ok(mut inner) = self.inner.lock() {
|
||||
inner.unparked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Now for MockNow {
|
||||
fn now(&mut self) -> Instant {
|
||||
self.inner.lock().unwrap().now()
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn now(&self) -> Instant {
|
||||
self.base + self.advance
|
||||
}
|
||||
|
||||
fn advance(&mut self, duration: Duration) {
|
||||
self.advance += duration;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user