mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-27 00:00:12 +02:00
Add DelayQueue implementation to tokio-timer (#550)
This patch adds a `DelayQueue` to tokio_timer. The `DelayQueue` allows inserting elements as well as specifying a time at which the element should be returned to the user. This allows handling more complex timeout situations.
This commit is contained in:
+61
-228
@@ -31,15 +31,17 @@
|
||||
// This allows the usage of the old `Now` trait.
|
||||
#![allow(deprecated)]
|
||||
|
||||
mod atomic_stack;
|
||||
mod entry;
|
||||
mod handle;
|
||||
mod level;
|
||||
mod now;
|
||||
mod registration;
|
||||
mod stack;
|
||||
|
||||
use self::atomic_stack::AtomicStack;
|
||||
use self::entry::Entry;
|
||||
use self::stack::Stack;
|
||||
use self::handle::HandlePriv;
|
||||
use self::level::{Level, Expiration};
|
||||
|
||||
pub use self::handle::{Handle, with_default};
|
||||
pub use self::now::{Now, SystemNow};
|
||||
@@ -47,6 +49,7 @@ pub(crate) use self::registration::Registration;
|
||||
|
||||
use Error;
|
||||
use atomic::AtomicU64;
|
||||
use wheel;
|
||||
|
||||
use tokio_executor::park::{Park, Unpark, ParkThread};
|
||||
|
||||
@@ -125,20 +128,8 @@ pub struct Timer<T, N = SystemNow> {
|
||||
/// Shared state
|
||||
inner: Arc<Inner>,
|
||||
|
||||
/// The number of milliseconds elapsed since the timer started.
|
||||
elapsed: u64,
|
||||
|
||||
/// Timer wheel.
|
||||
///
|
||||
/// Levels:
|
||||
///
|
||||
/// * 1 ms slots / 64 ms range
|
||||
/// * 64 ms slots / ~ 4 sec range
|
||||
/// * ~ 4 sec slots / ~ 4 min range
|
||||
/// * ~ 4 min slots / ~ 4 hr range
|
||||
/// * ~ 4 hr slots / ~ 12 day range
|
||||
/// * ~ 12 day slots / ~ 2 yr range
|
||||
levels: Vec<Level>,
|
||||
/// Timer wheel
|
||||
wheel: wheel::Wheel<Stack>,
|
||||
|
||||
/// Thread parker. The `Timer` park implementation delegates to this.
|
||||
park: T,
|
||||
@@ -166,20 +157,12 @@ pub(crate) struct Inner {
|
||||
num: AtomicUsize,
|
||||
|
||||
/// Head of the "process" linked list.
|
||||
process: entry::AtomicStack,
|
||||
process: AtomicStack,
|
||||
|
||||
/// Unparks the timer thread.
|
||||
unpark: Box<Unpark>,
|
||||
}
|
||||
|
||||
/// Number of levels. Each level has 64 slots. By using 6 levels with 64 slots
|
||||
/// each, the timer is able to track time up to 2 years into the future with a
|
||||
/// precision of 1 millisecond.
|
||||
const NUM_LEVELS: usize = 6;
|
||||
|
||||
/// The maximum duration of a delay
|
||||
const MAX_DURATION: u64 = 1 << (6 * NUM_LEVELS);
|
||||
|
||||
/// Maximum number of timeouts the system can handle concurrently.
|
||||
const MAX_TIMEOUTS: usize = usize::MAX >> 1;
|
||||
|
||||
@@ -226,14 +209,9 @@ where T: Park,
|
||||
pub fn new_with_now(park: T, mut now: N) -> Self {
|
||||
let unpark = Box::new(park.unpark());
|
||||
|
||||
let levels = (0..NUM_LEVELS)
|
||||
.map(Level::new)
|
||||
.collect();
|
||||
|
||||
Timer {
|
||||
inner: Arc::new(Inner::new(now.now(), unpark)),
|
||||
elapsed: 0,
|
||||
levels,
|
||||
wheel: wheel::Wheel::new(),
|
||||
park,
|
||||
now,
|
||||
}
|
||||
@@ -277,102 +255,29 @@ where T: Park,
|
||||
Ok(Turn(()))
|
||||
}
|
||||
|
||||
/// Returns the instant at which the next timeout expires.
|
||||
fn next_expiration(&self) -> Option<Expiration> {
|
||||
// Check all levels
|
||||
for level in 0..NUM_LEVELS {
|
||||
if let Some(expiration) = self.levels[level].next_expiration(self.elapsed) {
|
||||
// There cannot be any expirations at a higher level that happen
|
||||
// before this one.
|
||||
debug_assert!({
|
||||
let mut res = true;
|
||||
|
||||
for l2 in (level+1)..NUM_LEVELS {
|
||||
if let Some(e2) = self.levels[l2].next_expiration(self.elapsed) {
|
||||
if e2.deadline < expiration.deadline {
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
});
|
||||
|
||||
return Some(expiration);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Converts an `Expiration` to an `Instant`.
|
||||
fn expiration_instant(&self, expiration: &Expiration) -> Instant {
|
||||
self.inner.start + Duration::from_millis(expiration.deadline)
|
||||
fn expiration_instant(&self, when: u64) -> Instant {
|
||||
self.inner.start + Duration::from_millis(when)
|
||||
}
|
||||
|
||||
/// Run timer related logic
|
||||
fn process(&mut self) {
|
||||
let now = ms(self.now.now() - self.inner.start, Round::Down);
|
||||
let now = ::ms(self.now.now() - self.inner.start, ::Round::Down);
|
||||
let mut poll = wheel::Poll::new(now);
|
||||
|
||||
loop {
|
||||
let expiration = match self.next_expiration() {
|
||||
Some(expiration) => expiration,
|
||||
None => break,
|
||||
};
|
||||
while let Some(entry) = self.wheel.poll(&mut poll, &mut ()) {
|
||||
let when = entry.when_internal()
|
||||
.expect("invalid internal entry state");
|
||||
|
||||
if expiration.deadline > now {
|
||||
// This expiration should not fire on this tick
|
||||
break;
|
||||
}
|
||||
// Fire the entry
|
||||
entry.fire(when);
|
||||
|
||||
// Process the slot, either moving it down a level or firing the
|
||||
// timeout if currently at the final (boss) level.
|
||||
self.process_expiration(&expiration);
|
||||
|
||||
self.set_elapsed(expiration.deadline);
|
||||
// Track that the entry has been fired
|
||||
entry.set_when_internal(None);
|
||||
}
|
||||
|
||||
self.set_elapsed(now);
|
||||
}
|
||||
|
||||
fn set_elapsed(&mut self, when: u64) {
|
||||
assert!(self.elapsed <= when, "elapsed={:?}; when={:?}", self.elapsed, when);
|
||||
|
||||
if when > self.elapsed {
|
||||
self.elapsed = when;
|
||||
self.inner.elapsed.store(when, SeqCst);
|
||||
} else {
|
||||
assert_eq!(self.elapsed, when);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_expiration(&mut self, expiration: &Expiration) {
|
||||
while let Some(entry) = self.pop_entry(expiration) {
|
||||
if expiration.level == 0 {
|
||||
let when = entry.when_internal()
|
||||
.expect("invalid internal entry state");
|
||||
|
||||
debug_assert_eq!(when, expiration.deadline);
|
||||
|
||||
// Fire the entry
|
||||
entry.fire(when);
|
||||
|
||||
// Track that the entry has been fired
|
||||
entry.set_when_internal(None);
|
||||
} else {
|
||||
let when = entry.when_internal()
|
||||
.expect("entry not tracked");
|
||||
|
||||
let next_level = expiration.level - 1;
|
||||
|
||||
self.levels[next_level]
|
||||
.add_entry(entry, when);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_entry(&mut self, expiration: &Expiration) -> Option<Arc<Entry>> {
|
||||
self.levels[expiration.level].pop_entry_slot(expiration.slot)
|
||||
// Update the elapsed cache
|
||||
self.inner.elapsed.store(self.wheel.elapsed(), SeqCst);
|
||||
}
|
||||
|
||||
/// Process the entry queue
|
||||
@@ -384,27 +289,24 @@ where T: Park,
|
||||
(None, None) => {
|
||||
// Nothing to do
|
||||
}
|
||||
(Some(when), None) => {
|
||||
(Some(_), None) => {
|
||||
// Remove the entry
|
||||
self.clear_entry(&entry, when);
|
||||
self.clear_entry(&entry);
|
||||
}
|
||||
(None, Some(when)) => {
|
||||
// Queue the entry
|
||||
self.add_entry(entry, when);
|
||||
}
|
||||
(Some(curr), Some(next)) => {
|
||||
self.clear_entry(&entry, curr);
|
||||
(Some(_), Some(next)) => {
|
||||
self.clear_entry(&entry);
|
||||
self.add_entry(entry, next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear_entry(&mut self, entry: &Arc<Entry>, when: u64) {
|
||||
// Get the level at which the entry should be stored
|
||||
let level = self.level_for(when);
|
||||
self.levels[level].remove_entry(entry, when);
|
||||
|
||||
fn clear_entry(&mut self, entry: &Arc<Entry>) {
|
||||
self.wheel.remove(entry, &mut ());
|
||||
entry.set_when_internal(None);
|
||||
}
|
||||
|
||||
@@ -412,48 +314,26 @@ where T: Park,
|
||||
///
|
||||
/// Returns `None` if the entry was fired.
|
||||
fn add_entry(&mut self, entry: Arc<Entry>, when: u64) {
|
||||
if when <= self.elapsed {
|
||||
// The entry's deadline has elapsed, so fire it and update the
|
||||
// internal state accordingly.
|
||||
entry.set_when_internal(None);
|
||||
entry.fire(when);
|
||||
|
||||
return;
|
||||
} else if when - self.elapsed > MAX_DURATION {
|
||||
// The entry's deadline is invalid, so error it and update the
|
||||
// internal state accordingly.
|
||||
entry.set_when_internal(None);
|
||||
entry.error();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the level at which the entry should be stored
|
||||
let level = self.level_for(when);
|
||||
use wheel::InsertError;
|
||||
|
||||
entry.set_when_internal(Some(when));
|
||||
self.levels[level].add_entry(entry, when);
|
||||
|
||||
debug_assert!({
|
||||
self.levels[level].next_expiration(self.elapsed)
|
||||
.map(|e| e.deadline >= self.elapsed)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
match self.wheel.insert(when, entry, &mut ()) {
|
||||
Ok(_) => {}
|
||||
Err((entry, InsertError::Elapsed)) => {
|
||||
// The entry's deadline has elapsed, so fire it and update the
|
||||
// internal state accordingly.
|
||||
entry.set_when_internal(None);
|
||||
entry.fire(when);
|
||||
}
|
||||
Err((entry, InsertError::Invalid)) => {
|
||||
// The entry's deadline is invalid, so error it and update the
|
||||
// internal state accordingly.
|
||||
entry.set_when_internal(None);
|
||||
entry.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn level_for(&self, when: u64) -> usize {
|
||||
level_for(self.elapsed, when)
|
||||
}
|
||||
}
|
||||
|
||||
fn level_for(elapsed: u64, when: u64) -> usize {
|
||||
let masked = elapsed ^ when;
|
||||
|
||||
assert!(masked != 0, "elapsed={}; when={}", elapsed, when);
|
||||
|
||||
let leading_zeros = masked.leading_zeros() as usize;
|
||||
let significant = 63 - leading_zeros;
|
||||
significant / 6
|
||||
}
|
||||
|
||||
impl Default for Timer<ParkThread, SystemNow> {
|
||||
@@ -476,10 +356,10 @@ where T: Park,
|
||||
fn park(&mut self) -> Result<(), Self::Error> {
|
||||
self.process_queue();
|
||||
|
||||
match self.next_expiration() {
|
||||
Some(expiration) => {
|
||||
match self.wheel.poll_at() {
|
||||
Some(when) => {
|
||||
let now = self.now.now();
|
||||
let deadline = self.expiration_instant(&expiration);
|
||||
let deadline = self.expiration_instant(when);
|
||||
|
||||
if deadline > now {
|
||||
self.park.park_timeout(deadline - now)?;
|
||||
@@ -500,10 +380,10 @@ where T: Park,
|
||||
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
|
||||
self.process_queue();
|
||||
|
||||
match self.next_expiration() {
|
||||
Some(expiration) => {
|
||||
match self.wheel.poll_at() {
|
||||
Some(when) => {
|
||||
let now = self.now.now();
|
||||
let deadline = self.expiration_instant(&expiration);
|
||||
let deadline = self.expiration_instant(when);
|
||||
|
||||
if deadline > now {
|
||||
self.park.park_timeout(cmp::min(deadline - now, duration))?;
|
||||
@@ -524,9 +404,18 @@ where T: Park,
|
||||
|
||||
impl<T, N> Drop for Timer<T, N> {
|
||||
fn drop(&mut self) {
|
||||
use std::u64;
|
||||
|
||||
// Shutdown the stack of entries to process, preventing any new entries
|
||||
// from being pushed.
|
||||
self.inner.process.shutdown();
|
||||
|
||||
// Clear the wheel, using u64::MAX allows us to drain everything
|
||||
let mut poll = wheel::Poll::new(u64::MAX);
|
||||
|
||||
while let Some(entry) = self.wheel.poll(&mut poll, &mut ()) {
|
||||
entry.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +426,7 @@ impl Inner {
|
||||
Inner {
|
||||
num: AtomicUsize::new(0),
|
||||
elapsed: AtomicU64::new(0),
|
||||
process: entry::AtomicStack::new(),
|
||||
process: AtomicStack::new(),
|
||||
start,
|
||||
unpark,
|
||||
}
|
||||
@@ -586,7 +475,7 @@ impl Inner {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ms(deadline - self.start, Round::Up)
|
||||
::ms(deadline - self.start, ::Round::Up)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,59 +485,3 @@ impl fmt::Debug for Inner {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
enum Round {
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
/// Convert a `Duration` to milliseconds, rounding up and saturating at
|
||||
/// `u64::MAX`.
|
||||
///
|
||||
/// The saturating is fine because `u64::MAX` milliseconds are still many
|
||||
/// million years.
|
||||
#[inline]
|
||||
fn ms(duration: Duration, round: Round) -> u64 {
|
||||
const NANOS_PER_MILLI: u32 = 1_000_000;
|
||||
const MILLIS_PER_SEC: u64 = 1_000;
|
||||
|
||||
// Round up.
|
||||
let millis = match round {
|
||||
Round::Up => (duration.subsec_nanos() + NANOS_PER_MILLI - 1) / NANOS_PER_MILLI,
|
||||
Round::Down => duration.subsec_nanos() / NANOS_PER_MILLI,
|
||||
};
|
||||
|
||||
duration.as_secs().saturating_mul(MILLIS_PER_SEC).saturating_add(millis as u64)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_level_for() {
|
||||
for pos in 1..64 {
|
||||
assert_eq!(0, level_for(0, pos), "level_for({}) -- binary = {:b}", pos, pos);
|
||||
}
|
||||
|
||||
for level in 1..5 {
|
||||
for pos in level..64 {
|
||||
let a = pos * 64_usize.pow(level as u32);
|
||||
assert_eq!(level, level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}", a, a);
|
||||
|
||||
if pos > level {
|
||||
let a = a - 1;
|
||||
assert_eq!(level, level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}", a, a);
|
||||
}
|
||||
|
||||
if pos < 64 {
|
||||
let a = a + 1;
|
||||
assert_eq!(level, level_for(0, a as u64),
|
||||
"level_for({}) -- binary = {:b}", a, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user