-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
9013ed9
commit db620b4
Showing
15 changed files
with
468 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
//! A configurable source of time. | ||
//! | ||
//! This module provides the [`now`][n] function, which returns an `Instant` | ||
//! representing "now". The source of time used by this function is configurable | ||
//! (via the [`tokio-timer`] crate) and allows mocking out the source of time in | ||
//! tests or performing caching operations to reduce the number of syscalls. | ||
//! | ||
//! Note that, because the source of time is configurable, it is possible to | ||
//! observe non-monotonic behavior when calling [`now`] from different | ||
//! executors. | ||
//! | ||
//! [n]: fn.now.html | ||
//! [`tokio-timer`]: https://docs.rs/tokio-timer/0.2/tokio_timer/clock/index.html | ||
pub use tokio_timer::clock::now; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
use executor::current_thread::CurrentThread; | ||
use runtime::current_thread::Runtime; | ||
|
||
use tokio_reactor::Reactor; | ||
use tokio_timer::clock::Clock; | ||
use tokio_timer::timer::Timer; | ||
|
||
use std::io; | ||
|
||
/// Builds a Single-threaded runtime with custom configuration values. | ||
/// | ||
/// Methods can be chained in order to set the configuration values. The | ||
/// Runtime is constructed by calling [`build`]. | ||
/// | ||
/// New instances of `Builder` are obtained via [`Builder::new`]. | ||
/// | ||
/// See function level documentation for details on the various configuration | ||
/// settings. | ||
/// | ||
/// [`build`]: #method.build | ||
/// [`Builder::new`]: #method.new | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// extern crate tokio; | ||
/// extern crate tokio_timer; | ||
/// | ||
/// use tokio::runtime::current_thread::Builder; | ||
/// use tokio_timer::clock::Clock; | ||
/// | ||
/// # pub fn main() { | ||
/// // build Runtime | ||
/// let runtime = Builder::new() | ||
/// .clock(Clock::new()) | ||
/// .build(); | ||
/// // ... call runtime.run(...) | ||
/// # let _ = runtime; | ||
/// # } | ||
/// ``` | ||
#[derive(Debug)] | ||
pub struct Builder { | ||
/// The clock to use | ||
clock: Clock, | ||
} | ||
|
||
impl Builder { | ||
/// Returns a new runtime builder initialized with default configuration | ||
/// values. | ||
/// | ||
/// Configuration methods can be chained on the return value. | ||
pub fn new() -> Builder { | ||
Builder { | ||
clock: Clock::new(), | ||
} | ||
} | ||
|
||
/// Set the `Clock` instance that will be used by the runtime. | ||
pub fn clock(&mut self, clock: Clock) -> &mut Self { | ||
self.clock = clock; | ||
self | ||
} | ||
|
||
/// Create the configured `Runtime`. | ||
pub fn build(&mut self) -> io::Result<Runtime> { | ||
// We need a reactor to receive events about IO objects from kernel | ||
let reactor = Reactor::new()?; | ||
let reactor_handle = reactor.handle(); | ||
|
||
// Place a timer wheel on top of the reactor. If there are no timeouts to fire, it'll let the | ||
// reactor pick up some new external events. | ||
let timer = Timer::new_with_now(reactor, self.clock.clone()); | ||
let timer_handle = timer.handle(); | ||
|
||
// And now put a single-threaded executor on top of the timer. When there are no futures ready | ||
// to do something, it'll let the timer or the reactor to generate some new stimuli for the | ||
// futures to continue in their life. | ||
let executor = CurrentThread::new_with_park(timer); | ||
|
||
let runtime = Runtime::new2( | ||
reactor_handle, | ||
timer_handle, | ||
self.clock.clone(), | ||
executor); | ||
|
||
Ok(runtime) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
extern crate futures; | ||
extern crate tokio; | ||
extern crate tokio_timer; | ||
extern crate env_logger; | ||
|
||
use tokio::prelude::*; | ||
use tokio::runtime::{self, current_thread}; | ||
use tokio::timer::*; | ||
use tokio_timer::clock::Clock; | ||
|
||
use std::sync::mpsc; | ||
use std::time::{Duration, Instant}; | ||
|
||
struct MockNow(Instant); | ||
|
||
impl tokio_timer::clock::Now for MockNow { | ||
fn now(&self) -> Instant { | ||
self.0 | ||
} | ||
} | ||
|
||
#[test] | ||
fn clock_and_timer_concurrent() { | ||
let _ = env_logger::init(); | ||
|
||
let when = Instant::now() + Duration::from_millis(5_000); | ||
let clock = Clock::new_with_now(MockNow(when)); | ||
|
||
let mut rt = runtime::Builder::new() | ||
.clock(clock) | ||
.build() | ||
.unwrap(); | ||
|
||
let (tx, rx) = mpsc::channel(); | ||
|
||
rt.spawn({ | ||
Delay::new(when) | ||
.map_err(|e| panic!("unexpected error; err={:?}", e)) | ||
.and_then(move |_| { | ||
assert!(Instant::now() < when); | ||
tx.send(()).unwrap(); | ||
Ok(()) | ||
}) | ||
}); | ||
|
||
rx.recv().unwrap(); | ||
} | ||
|
||
#[test] | ||
fn clock_and_timer_single_threaded() { | ||
let _ = env_logger::init(); | ||
|
||
let when = Instant::now() + Duration::from_millis(5_000); | ||
let clock = Clock::new_with_now(MockNow(when)); | ||
|
||
let mut rt = current_thread::Builder::new() | ||
.clock(clock) | ||
.build() | ||
.unwrap(); | ||
|
||
rt.block_on({ | ||
Delay::new(when) | ||
.map_err(|e| panic!("unexpected error; err={:?}", e)) | ||
.and_then(move |_| { | ||
assert!(Instant::now() < when); | ||
Ok(()) | ||
}) | ||
}).unwrap(); | ||
} |
Oops, something went wrong.