Another attempt at abstracting Instant::now (#381)

Currently, the timer uses a `Now` trait to abstract the source of time.
This allows time to be mocked out. However, the current implementation
has a number of limitations as represented by #288 and #296.

The main issues are that `Now` requires `&mut self` which prevents a
value from being easily used in a concurrent environment. Also, when
wanting to write code that is abstract over the source of time, generics
get out of hand.

This patch provides an alternate solution. A new type, `Clock` is
provided which defaults to `Instant::now` as the source of time, but
allows configuring the actual source using a new iteration of the `Now`
trait. This time, `Now` is `Send + Sync + 'static`. Internally, `Clock`
stores the now value in an `Arc<Now>` value, which introduces dynamism
and allows `Clock` values to be cloned and be `Sync`.

Also, the current clock can be set for the current execution context
using the `with_default` pattern.

Because using the `Instant::now` will be the most common case by far, it
is special cased in order to avoid the need to allocate an `Arc` and use
dynamic dispatch.
This commit is contained in:
Carl Lerche
2018-06-06 16:04:39 -07:00
committed by GitHub
parent 9013ed9bd4
commit db620b42ec
15 changed files with 467 additions and 52 deletions
+138
View File
@@ -0,0 +1,138 @@
use clock::Now;
use timer;
use tokio_executor::Enter;
use std::cell::Cell;
use std::fmt;
use std::sync::Arc;
use std::time::Instant;
/// A handle to a source of time.
///
/// `Clock` instances return `Instant` values corresponding to "now". The source
/// of these values is configurable. The default source is `Instant::now()`.
#[derive(Default, Clone)]
pub struct Clock {
now: Option<Arc<Now>>,
}
/// Thread-local tracking the current clock
thread_local!(static CLOCK: Cell<Option<*const Clock>> = Cell::new(None));
/// Returns an `Instant` corresponding to "now".
///
/// This function delegates to the source of time configured for the current
/// execution context. By default, this is `Instant::now()`.
///
/// Note that, because the source of time is configurable, it is possible to
/// observe non-monotonic behavior when calling [`now`] from different
/// executors.
///
/// See [module](index.html) level documentation for more details.
///
/// # Examples
///
/// ```
/// # use tokio_timer::clock;
/// let now = clock::now();
/// ```
pub fn now() -> Instant {
CLOCK.with(|current| {
match current.get() {
Some(ptr) => {
unsafe { (*ptr).now() }
}
None => Instant::now(),
}
})
}
impl Clock {
/// Return a new `Clock` instance that uses the current execution context's
/// source of time.
pub fn new() -> Clock {
CLOCK.with(|current| {
match current.get() {
Some(ptr) => {
unsafe { (*ptr).clone() }
}
None => Clock::system(),
}
})
}
/// Return a new `Clock` instance that uses `now` as the source of time.
pub fn new_with_now<T: Now>(now: T) -> Clock {
Clock {
now: Some(Arc::new(now)),
}
}
/// Return a new `Clock` instance that uses `Instant::now()` as the source
/// of time.
pub fn system() -> Clock {
Clock {
now: None,
}
}
/// Returns an instant corresponding to "now" by using the instance's source
/// of time.
pub fn now(&self) -> Instant {
match self.now {
Some(ref now) => now.now(),
None => Instant::now(),
}
}
}
#[allow(deprecated)]
impl timer::Now for Clock {
fn now(&mut self) -> Instant {
Clock::now(self)
}
}
impl fmt::Debug for Clock {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("Clock")
.field("now", {
if self.now.is_some() {
&"Some(Arc<Now>)"
} else {
&"None"
}
})
.finish()
}
}
/// Set the default clock for the duration of the closure.
///
/// # Panics
///
/// This function panics if there already is a default clock set.
pub fn with_default<F, R>(clock: &Clock, enter: &mut Enter, f: F) -> R
where F: FnOnce(&mut Enter) -> R
{
CLOCK.with(|cell| {
assert!(cell.get().is_none(), "default clock already set for execution context");
// Ensure that the clock is removed from the thread-local context
// when leaving the scope. This handles cases that involve panicking.
struct Reset<'a>(&'a Cell<Option<*const Clock>>);
impl<'a> Drop for Reset<'a> {
fn drop(&mut self) {
self.0.set(None);
}
}
let _reset = Reset(cell);
cell.set(Some(clock as *const Clock));
f(enter)
})
}
+22
View File
@@ -0,0 +1,22 @@
//! A configurable source of time.
//!
//! This module provides an API to get the current instant in such a way that
//! the source of time may be configured. This allows mocking out the source of
//! time in tests.
//!
//! The [`now`][n] function returns the current `Instant`. By default, it delegates
//! to [`Instant::now`][std].
//!
//! The source of time used by [`now`] can be configured by implementing the
//! [`Now`] trait and passing an instance to [`with_default`].
//!
//! [n]: fn.now.html
//! [`Now`]: trait.Now.html
//! [std]: https://doc.rust-lang.org/std/time/struct.Instant.html
//! [`with_default`]: fn.with_default.html
mod clock;
mod now;
pub use self::clock::{Clock, now, with_default};
pub use self::now::Now;
+13
View File
@@ -0,0 +1,13 @@
use std::time::Instant;
/// Returns `Instant` values representing the current instant in time.
///
/// This allows customizing the source of time which is especially useful for
/// testing.
///
/// Implementations must ensure that calls to `now` return monotonically
/// increasing `Instant` values.
pub trait Now: Send + Sync + 'static {
/// Returns an instant corresponding to "now".
fn now(&self) -> Instant;
}