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:
@@ -0,0 +1,122 @@
|
||||
use Error;
|
||||
use super::Entry;
|
||||
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicPtr;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
|
||||
/// A stack of `Entry` nodes
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AtomicStack {
|
||||
/// Stack head
|
||||
head: AtomicPtr<Entry>,
|
||||
}
|
||||
|
||||
/// Entries that were removed from the stack
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AtomicStackEntries {
|
||||
ptr: *mut Entry,
|
||||
}
|
||||
|
||||
/// Used to indicate that the timer has shutdown.
|
||||
const SHUTDOWN: *mut Entry = 1 as *mut _;
|
||||
|
||||
impl AtomicStack {
|
||||
pub fn new() -> AtomicStack {
|
||||
AtomicStack { head: AtomicPtr::new(ptr::null_mut()) }
|
||||
}
|
||||
|
||||
/// Push an entry onto the stack.
|
||||
///
|
||||
/// Returns `true` if the entry was pushed, `false` if the entry is already
|
||||
/// on the stack, `Err` if the timer is shutdown.
|
||||
pub fn push(&self, entry: &Arc<Entry>) -> Result<bool, Error> {
|
||||
// First, set the queued bit on the entry
|
||||
let queued = entry.queued.fetch_or(true, SeqCst).into();
|
||||
|
||||
if queued {
|
||||
// Already queued, nothing more to do
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let ptr = Arc::into_raw(entry.clone()) as *mut _;
|
||||
|
||||
let mut curr = self.head.load(SeqCst);
|
||||
|
||||
loop {
|
||||
if curr == SHUTDOWN {
|
||||
// Don't leak the entry node
|
||||
let _ = unsafe { Arc::from_raw(ptr) };
|
||||
|
||||
return Err(Error::shutdown());
|
||||
}
|
||||
|
||||
// Update the `next` pointer. This is safe because setting the queued
|
||||
// bit is a "lock" on this field.
|
||||
unsafe {
|
||||
*(entry.next_atomic.get()) = curr;
|
||||
}
|
||||
|
||||
let actual = self.head.compare_and_swap(curr, ptr, SeqCst);
|
||||
|
||||
if actual == curr {
|
||||
break;
|
||||
}
|
||||
|
||||
curr = actual;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Take all entries from the stack
|
||||
pub fn take(&self) -> AtomicStackEntries {
|
||||
let ptr = self.head.swap(ptr::null_mut(), SeqCst);
|
||||
AtomicStackEntries { ptr }
|
||||
}
|
||||
|
||||
/// Drain all remaining nodes in the stack and prevent any new nodes from
|
||||
/// being pushed onto the stack.
|
||||
pub fn shutdown(&self) {
|
||||
// Shutdown the processing queue
|
||||
let ptr = self.head.swap(SHUTDOWN, SeqCst);
|
||||
|
||||
// Let the drop fn of `AtomicStackEntries` handle draining the stack
|
||||
drop(AtomicStackEntries { ptr });
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl AtomicStackEntries =====
|
||||
|
||||
impl Iterator for AtomicStackEntries {
|
||||
type Item = Arc<Entry>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Convert the pointer to an `Arc<Entry>`
|
||||
let entry = unsafe { Arc::from_raw(self.ptr) };
|
||||
|
||||
// Update `self.ptr` to point to the next element of the stack
|
||||
self.ptr = unsafe { (*entry.next_atomic.get()) };
|
||||
|
||||
// Unset the queued flag
|
||||
let res = entry.queued.fetch_and(false, SeqCst);
|
||||
debug_assert!(res);
|
||||
|
||||
// Return the entry
|
||||
Some(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AtomicStackEntries {
|
||||
fn drop(&mut self) {
|
||||
while let Some(entry) = self.next() {
|
||||
// Flag the entry as errored
|
||||
entry.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ use futures::task::AtomicTask;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ptr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::atomic::{AtomicBool, AtomicPtr};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::time::Instant;
|
||||
use std::u64;
|
||||
@@ -56,12 +56,12 @@ pub(crate) struct Entry {
|
||||
|
||||
/// True when the entry is queued in the "process" stack. This value
|
||||
/// is set before pushing the value and unset after popping the value.
|
||||
queued: AtomicBool,
|
||||
pub(super) queued: AtomicBool,
|
||||
|
||||
/// Next entry in the "process" linked list.
|
||||
///
|
||||
/// Represents a strong Arc ref.
|
||||
next_atomic: UnsafeCell<*mut Entry>,
|
||||
pub(super) next_atomic: UnsafeCell<*mut Entry>,
|
||||
|
||||
/// When the entry expires, relative to the `start` of the timer
|
||||
/// (Inner::start). This is only used by the timer.
|
||||
@@ -80,7 +80,7 @@ pub(crate) struct Entry {
|
||||
/// Next entry in the State's linked list.
|
||||
///
|
||||
/// This is only accessed by the timer
|
||||
next_stack: UnsafeCell<Option<Arc<Entry>>>,
|
||||
pub(super) next_stack: UnsafeCell<Option<Arc<Entry>>>,
|
||||
|
||||
/// Previous entry in the State's linked list.
|
||||
///
|
||||
@@ -88,25 +88,7 @@ pub(crate) struct Entry {
|
||||
/// entry.
|
||||
///
|
||||
/// This is a weak reference.
|
||||
prev_stack: UnsafeCell<*const Entry>,
|
||||
}
|
||||
|
||||
/// A doubly linked stack
|
||||
pub(crate) struct Stack {
|
||||
head: Option<Arc<Entry>>,
|
||||
}
|
||||
|
||||
/// A stack of `Entry` nodes
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AtomicStack {
|
||||
/// Stack head
|
||||
head: AtomicPtr<Entry>,
|
||||
}
|
||||
|
||||
/// Entries that were removed from the stack
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AtomicStackEntries {
|
||||
ptr: *mut Entry,
|
||||
pub(super) prev_stack: UnsafeCell<*const Entry>,
|
||||
}
|
||||
|
||||
/// Flag indicating a timer entry has elapsed
|
||||
@@ -115,9 +97,6 @@ const ELAPSED: u64 = 1 << 63;
|
||||
/// Flag indicating a timer entry has reached an error state
|
||||
const ERROR: u64 = u64::MAX;
|
||||
|
||||
/// Used to indicate that the timer has shutdown.
|
||||
const SHUTDOWN: *mut Entry = 1 as *mut _;
|
||||
|
||||
// ===== impl Entry =====
|
||||
|
||||
impl Entry {
|
||||
@@ -349,211 +328,3 @@ impl Drop for Entry {
|
||||
|
||||
unsafe impl Send for Entry {}
|
||||
unsafe impl Sync for Entry {}
|
||||
|
||||
// ===== impl Stack =====
|
||||
|
||||
impl Stack {
|
||||
pub fn new() -> Stack {
|
||||
Stack { head: None }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.head.is_none()
|
||||
}
|
||||
|
||||
/// Push an entry to the head of the linked list
|
||||
pub fn push(&mut self, entry: Arc<Entry>) {
|
||||
// Get a pointer to the entry to for the prev link
|
||||
let ptr: *const Entry = &*entry as *const _;
|
||||
|
||||
// Remove the old head entry
|
||||
let old = self.head.take();
|
||||
|
||||
unsafe {
|
||||
// Ensure the entry is not already in a stack.
|
||||
debug_assert!((*entry.next_stack.get()).is_none());
|
||||
debug_assert!((*entry.prev_stack.get()).is_null());
|
||||
|
||||
if let Some(ref entry) = old.as_ref() {
|
||||
debug_assert!({
|
||||
// The head is not already set to the entry
|
||||
ptr != &***entry as *const _
|
||||
});
|
||||
|
||||
// Set the previous link on the old head
|
||||
*entry.prev_stack.get() = ptr;
|
||||
}
|
||||
|
||||
// Set this entry's next pointer
|
||||
*entry.next_stack.get() = old;
|
||||
|
||||
}
|
||||
|
||||
// Update the head pointer
|
||||
self.head = Some(entry);
|
||||
}
|
||||
|
||||
/// Pop the head of the linked list
|
||||
pub fn pop(&mut self) -> Option<Arc<Entry>> {
|
||||
let entry = self.head.take();
|
||||
|
||||
unsafe {
|
||||
if let Some(entry) = entry.as_ref() {
|
||||
self.head = (*entry.next_stack.get()).take();
|
||||
|
||||
if let Some(entry) = self.head.as_ref() {
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
}
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
/// Remove the entry from the linked list
|
||||
///
|
||||
/// The caller must ensure that the entry actually is contained by the list.
|
||||
pub fn remove(&mut self, entry: &Entry) {
|
||||
unsafe {
|
||||
// Ensure that the entry is in fact contained by the stack
|
||||
debug_assert!({
|
||||
// This walks the full linked list even if an entry is found.
|
||||
let mut next = self.head.as_ref();
|
||||
let mut contains = false;
|
||||
|
||||
while let Some(n) = next {
|
||||
if entry as *const _ == &**n as *const _ {
|
||||
debug_assert!(!contains);
|
||||
contains = true;
|
||||
}
|
||||
|
||||
next = (*n.next_stack.get()).as_ref();
|
||||
}
|
||||
|
||||
contains
|
||||
});
|
||||
|
||||
// Unlink `entry` from the next node
|
||||
let next = (*entry.next_stack.get()).take();
|
||||
|
||||
if let Some(next) = next.as_ref() {
|
||||
(*next.prev_stack.get()) = *entry.prev_stack.get();
|
||||
}
|
||||
|
||||
// Unlink `entry` from the prev node
|
||||
|
||||
if let Some(prev) = (*entry.prev_stack.get()).as_ref() {
|
||||
*prev.next_stack.get() = next;
|
||||
} else {
|
||||
// It is the head
|
||||
self.head = next;
|
||||
}
|
||||
|
||||
// Unset the prev pointer
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl AtomicStack =====
|
||||
|
||||
impl AtomicStack {
|
||||
pub fn new() -> AtomicStack {
|
||||
AtomicStack { head: AtomicPtr::new(ptr::null_mut()) }
|
||||
}
|
||||
|
||||
/// Push an entry onto the stack.
|
||||
///
|
||||
/// Returns `true` if the entry was pushed, `false` if the entry is already
|
||||
/// on the stack, `Err` if the timer is shutdown.
|
||||
pub fn push(&self, entry: &Arc<Entry>) -> Result<bool, Error> {
|
||||
// First, set the queued bit on the entry
|
||||
let queued = entry.queued.fetch_or(true, SeqCst).into();
|
||||
|
||||
if queued {
|
||||
// Already queued, nothing more to do
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let ptr = Arc::into_raw(entry.clone()) as *mut _;
|
||||
|
||||
let mut curr = self.head.load(SeqCst);
|
||||
|
||||
loop {
|
||||
if curr == SHUTDOWN {
|
||||
// Don't leak the entry node
|
||||
let _ = unsafe { Arc::from_raw(ptr) };
|
||||
|
||||
return Err(Error::shutdown());
|
||||
}
|
||||
|
||||
// Update the `next` pointer. This is safe because setting the queued
|
||||
// bit is a "lock" on this field.
|
||||
unsafe {
|
||||
*(entry.next_atomic.get()) = curr;
|
||||
}
|
||||
|
||||
let actual = self.head.compare_and_swap(curr, ptr, SeqCst);
|
||||
|
||||
if actual == curr {
|
||||
break;
|
||||
}
|
||||
|
||||
curr = actual;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Take all entries from the stack
|
||||
pub fn take(&self) -> AtomicStackEntries {
|
||||
let ptr = self.head.swap(ptr::null_mut(), SeqCst);
|
||||
AtomicStackEntries { ptr }
|
||||
}
|
||||
|
||||
/// Drain all remaining nodes in the stack and prevent any new nodes from
|
||||
/// being pushed onto the stack.
|
||||
pub fn shutdown(&self) {
|
||||
// Shutdown the processing queue
|
||||
let ptr = self.head.swap(SHUTDOWN, SeqCst);
|
||||
|
||||
// Let the drop fn of `AtomicStackEntries` handle draining the stack
|
||||
drop(AtomicStackEntries { ptr });
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl AtomicStackEntries =====
|
||||
|
||||
impl Iterator for AtomicStackEntries {
|
||||
type Item = Arc<Entry>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.ptr.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Convert the pointer to an `Arc<Entry>`
|
||||
let entry = unsafe { Arc::from_raw(self.ptr) };
|
||||
|
||||
// Update `self.ptr` to point to the next element of the stack
|
||||
self.ptr = unsafe { (*entry.next_atomic.get()) };
|
||||
|
||||
// Unset the queued flag
|
||||
let res = entry.queued.fetch_and(false, SeqCst);
|
||||
debug_assert!(res);
|
||||
|
||||
// Return the entry
|
||||
Some(entry)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AtomicStackEntries {
|
||||
fn drop(&mut self) {
|
||||
while let Some(entry) = self.next() {
|
||||
// Flag the entry as errored
|
||||
entry.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,201 +0,0 @@
|
||||
use timer::{entry, Entry};
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Wheel for a single level in the timer. This wheel contains 64 slots.
|
||||
pub(crate) struct Level {
|
||||
level: usize,
|
||||
|
||||
/// Bit field tracking which slots currently contain entries.
|
||||
///
|
||||
/// Using a bit field to track slots that contain entries allows avoiding a
|
||||
/// scan to find entries. This field is updated when entries are added or
|
||||
/// removed from a slot.
|
||||
///
|
||||
/// The least-significant bit represents slot zero.
|
||||
occupied: u64,
|
||||
|
||||
/// Slots
|
||||
slot: [entry::Stack; LEVEL_MULT],
|
||||
}
|
||||
|
||||
/// Indicates when a slot must be processed next.
|
||||
#[derive(Debug)]
|
||||
pub struct Expiration {
|
||||
/// The level containing the slot.
|
||||
pub level: usize,
|
||||
|
||||
/// The slot index.
|
||||
pub slot: usize,
|
||||
|
||||
/// The instant at which the slot needs to be processed.
|
||||
pub deadline: u64,
|
||||
}
|
||||
|
||||
/// Level multiplier.
|
||||
///
|
||||
/// Being a power of 2 is very important.
|
||||
const LEVEL_MULT: usize = 64;
|
||||
|
||||
impl Level {
|
||||
pub fn new(level: usize) -> Level {
|
||||
// Rust's derived implementations for arrays require that the value
|
||||
// contained by the array be `Copy`. So, here we have to manually
|
||||
// initialize every single slot.
|
||||
macro_rules! s {
|
||||
() => { entry::Stack::new() };
|
||||
};
|
||||
|
||||
Level {
|
||||
level,
|
||||
occupied: 0,
|
||||
slot: [
|
||||
// It does not look like the necessary traits are
|
||||
// derived for [T; 64].
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
s!(), s!(), s!(), s!(), s!(), s!(), s!(), s!(),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the slot that needs to be processed next and returns the slot and
|
||||
/// `Instant` at which this slot must be processed.
|
||||
pub fn next_expiration(&self, now: u64) -> Option<Expiration> {
|
||||
// Use the `occupied` bit field to get the index of the next slot that
|
||||
// needs to be processed.
|
||||
let slot = match self.next_occupied_slot(now) {
|
||||
Some(slot) => slot,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
// From the slot index, calculate the `Instant` at which it needs to be
|
||||
// processed. This value *must* be in the future with respect to `now`.
|
||||
|
||||
let level_range = level_range(self.level);
|
||||
let slot_range = slot_range(self.level);
|
||||
|
||||
// TODO: This can probably be simplified w/ power of 2 math
|
||||
let level_start = now - (now % level_range);
|
||||
let deadline = level_start + slot as u64 * slot_range;
|
||||
|
||||
debug_assert!(deadline >= now, "deadline={}; now={}; level={}; slot={}; occupied={:b}",
|
||||
deadline, now, self.level, slot, self.occupied);
|
||||
|
||||
Some(Expiration {
|
||||
level: self.level,
|
||||
slot,
|
||||
deadline,
|
||||
})
|
||||
}
|
||||
|
||||
fn next_occupied_slot(&self, now: u64) -> Option<usize> {
|
||||
if self.occupied == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the slot for now using Maths
|
||||
let now_slot = (now / slot_range(self.level)) as usize;
|
||||
let occupied = self.occupied.rotate_right(now_slot as u32);
|
||||
let zeros = occupied.trailing_zeros() as usize;
|
||||
let slot = (zeros + now_slot) % 64;
|
||||
|
||||
Some(slot)
|
||||
}
|
||||
|
||||
pub fn add_entry(&mut self, entry: Arc<Entry>, when: u64) {
|
||||
let slot = slot_for(when, self.level);
|
||||
|
||||
self.slot[slot].push(entry);
|
||||
self.occupied |= occupied_bit(slot);
|
||||
}
|
||||
|
||||
pub fn remove_entry(&mut self, entry: &Entry, when: u64) {
|
||||
let slot = slot_for(when, self.level);
|
||||
|
||||
self.slot[slot].remove(entry);
|
||||
|
||||
if self.slot[slot].is_empty() {
|
||||
// The bit is currently set
|
||||
debug_assert!(self.occupied & occupied_bit(slot) != 0);
|
||||
|
||||
// Unset the bit
|
||||
self.occupied ^= occupied_bit(slot);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_entry_slot(&mut self, slot: usize) -> Option<Arc<Entry>> {
|
||||
let ret = self.slot[slot].pop();
|
||||
|
||||
if ret.is_some() && self.slot[slot].is_empty() {
|
||||
// The bit is currently set
|
||||
debug_assert!(self.occupied & occupied_bit(slot) != 0);
|
||||
|
||||
self.occupied ^= occupied_bit(slot);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Level {
|
||||
fn drop(&mut self) {
|
||||
while let Some(slot) = self.next_occupied_slot(0) {
|
||||
// This should always have one
|
||||
let entry = self.pop_entry_slot(slot)
|
||||
.expect("occupied bit set invalid");
|
||||
|
||||
entry.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Level {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.debug_struct("Level")
|
||||
.field("occupied", &self.occupied)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn occupied_bit(slot: usize) -> u64 {
|
||||
(1 << slot)
|
||||
}
|
||||
|
||||
fn slot_range(level: usize) -> u64 {
|
||||
LEVEL_MULT.pow(level as u32) as u64
|
||||
}
|
||||
|
||||
fn level_range(level: usize) -> u64 {
|
||||
LEVEL_MULT as u64 * slot_range(level)
|
||||
}
|
||||
|
||||
/// Convert a duration (milliseconds) and a level to a slot position
|
||||
fn slot_for(duration: u64, level: usize) -> usize {
|
||||
((duration >> (level * 6)) % LEVEL_MULT as u64) as usize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_slot_for() {
|
||||
for pos in 1..64 {
|
||||
assert_eq!(pos as usize, slot_for(pos, 0));
|
||||
}
|
||||
|
||||
for level in 1..5 {
|
||||
for pos in level..64 {
|
||||
let a = pos * 64_usize.pow(level as u32);
|
||||
assert_eq!(pos as usize, slot_for(a as u64, level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
use super::Entry;
|
||||
use wheel;
|
||||
|
||||
use std::ptr;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A doubly linked stack
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Stack {
|
||||
head: Option<Arc<Entry>>,
|
||||
}
|
||||
|
||||
impl Default for Stack {
|
||||
fn default() -> Stack {
|
||||
Stack { head: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl wheel::Stack for Stack {
|
||||
type Owned = Arc<Entry>;
|
||||
type Borrowed = Entry;
|
||||
type Store = ();
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.head.is_none()
|
||||
}
|
||||
|
||||
fn push(&mut self, entry: Self::Owned, _: &mut Self::Store) {
|
||||
// Get a pointer to the entry to for the prev link
|
||||
let ptr: *const Entry = &*entry as *const _;
|
||||
|
||||
// Remove the old head entry
|
||||
let old = self.head.take();
|
||||
|
||||
unsafe {
|
||||
// Ensure the entry is not already in a stack.
|
||||
debug_assert!((*entry.next_stack.get()).is_none());
|
||||
debug_assert!((*entry.prev_stack.get()).is_null());
|
||||
|
||||
if let Some(ref entry) = old.as_ref() {
|
||||
debug_assert!({
|
||||
// The head is not already set to the entry
|
||||
ptr != &***entry as *const _
|
||||
});
|
||||
|
||||
// Set the previous link on the old head
|
||||
*entry.prev_stack.get() = ptr;
|
||||
}
|
||||
|
||||
// Set this entry's next pointer
|
||||
*entry.next_stack.get() = old;
|
||||
|
||||
}
|
||||
|
||||
// Update the head pointer
|
||||
self.head = Some(entry);
|
||||
}
|
||||
|
||||
/// Pop an item from the stack
|
||||
fn pop(&mut self, _: &mut ()) -> Option<Arc<Entry>> {
|
||||
let entry = self.head.take();
|
||||
|
||||
unsafe {
|
||||
if let Some(entry) = entry.as_ref() {
|
||||
self.head = (*entry.next_stack.get()).take();
|
||||
|
||||
if let Some(entry) = self.head.as_ref() {
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
}
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
fn remove(&mut self, entry: &Entry, _: &mut ()) {
|
||||
unsafe {
|
||||
// Ensure that the entry is in fact contained by the stack
|
||||
debug_assert!({
|
||||
// This walks the full linked list even if an entry is found.
|
||||
let mut next = self.head.as_ref();
|
||||
let mut contains = false;
|
||||
|
||||
while let Some(n) = next {
|
||||
if entry as *const _ == &**n as *const _ {
|
||||
debug_assert!(!contains);
|
||||
contains = true;
|
||||
}
|
||||
|
||||
next = (*n.next_stack.get()).as_ref();
|
||||
}
|
||||
|
||||
contains
|
||||
});
|
||||
|
||||
// Unlink `entry` from the next node
|
||||
let next = (*entry.next_stack.get()).take();
|
||||
|
||||
if let Some(next) = next.as_ref() {
|
||||
(*next.prev_stack.get()) = *entry.prev_stack.get();
|
||||
}
|
||||
|
||||
// Unlink `entry` from the prev node
|
||||
|
||||
if let Some(prev) = (*entry.prev_stack.get()).as_ref() {
|
||||
*prev.next_stack.get() = next;
|
||||
} else {
|
||||
// It is the head
|
||||
self.head = next;
|
||||
}
|
||||
|
||||
// Unset the prev pointer
|
||||
*entry.prev_stack.get() = ptr::null();
|
||||
}
|
||||
}
|
||||
|
||||
fn when(item: &Entry, _: &()) -> u64 {
|
||||
item.when_internal()
|
||||
.expect("invalid internal state")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user