diff --git a/ci/azure-loom.yml b/ci/azure-loom.yml index 99e03da27..6bc305357 100644 --- a/ci/azure-loom.yml +++ b/ci/azure-loom.yml @@ -12,7 +12,7 @@ jobs: - ${{ each crate in parameters.crates }}: - script: RUSTFLAGS="--cfg loom" cargo test --lib --release -- --test-threads=1 --nocapture env: - LOOM_MAX_PREEMPTIONS: 2 + LOOM_MAX_PREEMPTIONS: 1 CI: 'True' displayName: test ${{ crate }} workingDirectory: $(Build.SourcesDirectory)/${{ crate }} diff --git a/tokio-test/Cargo.toml b/tokio-test/Cargo.toml index ad0a567a4..60cb3e50d 100644 --- a/tokio-test/Cargo.toml +++ b/tokio-test/Cargo.toml @@ -20,7 +20,7 @@ Testing utilities for Tokio- and futures-based code categories = ["asynchronous", "testing"] [dependencies] -tokio = { version = "=0.2.0-alpha.6", path = "../tokio" } +tokio = { version = "=0.2.0-alpha.6", path = "../tokio", features = ["test-util"] } bytes = "0.4" futures-core = "0.3.0" diff --git a/tokio-test/src/clock.rs b/tokio-test/src/clock.rs deleted file mode 100644 index d2f292491..000000000 --- a/tokio-test/src/clock.rs +++ /dev/null @@ -1,277 +0,0 @@ -//! A mocked clock for use with `tokio::time` based futures. -//! -//! # Example -//! -//! ``` -//! use tokio::time::{clock, delay}; -//! use tokio_test::{assert_ready, assert_pending, task}; -//! -//! use std::time::Duration; -//! -//! tokio_test::clock::mock(|handle| { -//! let mut task = task::spawn(async { -//! delay(clock::now() + Duration::from_secs(1)).await -//! }); -//! -//! assert_pending!(task.poll()); -//! -//! handle.advance(Duration::from_secs(1)); -//! -//! assert_ready!(task.poll()); -//! }); -//! ``` - -use tokio::runtime::{Park, Unpark}; -use tokio::time::clock::{Clock, Now}; -use tokio::time::Timer; - -use std::marker::PhantomData; -use std::rc::Rc; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; - -/// Run the provided closure with a `MockClock` that starts at the current time. -pub fn mock(f: F) -> R -where - F: FnOnce(&mut Handle) -> R, -{ - let mut mock = MockClock::new(); - mock.enter(f) -} - -/// Run the provided closure with a `MockClock` that starts at the provided `Instant`. -pub fn mock_at(instant: Instant, f: F) -> R -where - F: FnOnce(&mut Handle) -> R, -{ - let mut mock = MockClock::with_instant(instant); - mock.enter(f) -} - -/// Mock clock for use with `tokio-timer` futures. -/// -/// A mock timer that is able to advance and wake after a -/// certain duration. -#[derive(Debug)] -pub struct MockClock { - time: MockTime, - clock: Clock, -} - -/// A handle to the `MockClock`. -#[derive(Debug)] -pub struct Handle { - timer: Timer, - time: MockTime, -} - -type Inner = Arc>; - -#[derive(Debug, Clone)] -struct MockTime { - inner: Inner, - _pd: PhantomData>, -} - -#[derive(Debug)] -struct MockNow { - inner: Inner, -} - -#[derive(Debug)] -struct MockPark { - inner: Inner, - _pd: PhantomData>, -} - -#[derive(Debug)] -struct MockUnpark { - inner: Inner, -} - -#[derive(Debug)] -struct State { - base: Instant, - advance: Duration, - unparked: bool, - park_for: Option, -} - -impl MockClock { - /// Create a new `MockClock` with the current time. - pub fn new() -> Self { - MockClock::with_instant(Instant::now()) - } - - /// Create a `MockClock` with its current time at a duration from now - /// - /// This will create a clock with `Instant::now() + duration` as the current time. - pub fn with_duration(duration: Duration) -> Self { - let instant = Instant::now() + duration; - MockClock::with_instant(instant) - } - - /// Create a `MockClock` that sets its current time as the `Instant` provided. - pub fn with_instant(instant: Instant) -> Self { - let time = MockTime::new(instant); - let clock = Clock::new_with_now(time.mock_now()); - - MockClock { time, clock } - } - - /// Enter the `MockClock` context. - pub fn enter(&mut self, f: F) -> R - where - F: FnOnce(&mut Handle) -> R, - { - tokio::time::clock::with_default(&self.clock, || { - let park = self.time.mock_park(); - let timer = Timer::new(park); - let handle = timer.handle(); - let time = self.time.clone(); - - let _timer = tokio::time::set_default(&handle); - let mut handle = Handle::new(timer, time); - f(&mut handle) - // lazy(|| Ok::<_, ()>(f(&mut handle))).wait().unwrap() - }) - } -} - -impl Default for MockClock { - fn default() -> Self { - Self::new() - } -} - -impl Handle { - pub(self) fn new(timer: Timer, time: MockTime) -> Self { - Handle { timer, time } - } - - /// Turn the internal timer and mock park for the provided duration. - pub fn turn(&mut self) { - self.timer.turn(None).unwrap(); - } - - /// Turn the internal timer and mock park for the provided duration. - pub fn turn_for(&mut self, duration: Duration) { - self.timer.turn(Some(duration)).unwrap(); - } - - /// Advance the `MockClock` by the provided duration. - pub fn advance(&mut self, duration: Duration) { - let inner = self.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(); - self.turn_for(dur); - } - } - - /// Returns the total amount of time the time has been advanced. - pub fn advanced(&self) -> Duration { - self.time.inner.lock().unwrap().advance - } - - /// Get the currently mocked time - pub fn now(&mut self) -> Instant { - self.time.now() - } - - /// Turn the internal timer once, but force "parking" for `duration` regardless of any pending - /// timeouts - pub fn park_for(&mut self, duration: Duration) { - self.time.inner.lock().unwrap().park_for = Some(duration); - self.turn() - } -} - -impl MockTime { - pub(crate) 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)), - _pd: PhantomData, - } - } - - pub(crate) fn mock_now(&self) -> MockNow { - let inner = self.inner.clone(); - MockNow { inner } - } - - pub(crate) fn mock_park(&self) -> MockPark { - let inner = self.inner.clone(); - MockPark { - inner, - _pd: PhantomData, - } - } - - pub(crate) fn now(&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; - } -} - -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(&self) -> Instant { - self.inner.lock().unwrap().now() - } -} diff --git a/tokio-test/src/io.rs b/tokio-test/src/io.rs index a073193c4..1d42dd03f 100644 --- a/tokio-test/src/io.rs +++ b/tokio-test/src/io.rs @@ -18,7 +18,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tokio::sync::mpsc; -use tokio::time::{clock, timer, Delay}; +use tokio::time::{self, Delay, Duration, Instant}; use bytes::Buf; use futures_core::ready; @@ -26,7 +26,6 @@ use std::collections::VecDeque; use std::future::Future; use std::pin::Pin; use std::task::{self, Poll, Waker}; -use std::time::{Duration, Instant}; use std::{cmp, io}; /// An I/O object that follows a predefined script. @@ -62,8 +61,6 @@ enum Action { struct Inner { actions: VecDeque, waiting: Option, - - timer_handle: timer::Handle, sleep: Option, read_wait: Option, rx: mpsc::UnboundedReceiver, @@ -145,7 +142,6 @@ impl Inner { let inner = Inner { actions, - timer_handle: timer::Handle::default(), sleep: None, read_wait: None, rx, @@ -301,8 +297,8 @@ impl AsyncRead for Mock { match self.inner.read(buf) { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { if let Some(rem) = self.inner.remaining_wait() { - let until = clock::now() + rem; - self.inner.sleep = Some(self.inner.timer_handle.delay(until)); + let until = Instant::now() + rem; + self.inner.sleep = Some(time::delay(until)); } else { self.inner.read_wait = Some(cx.waker().clone()); return Poll::Pending; @@ -343,8 +339,8 @@ impl AsyncWrite for Mock { match self.inner.write(buf) { Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { if let Some(rem) = self.inner.remaining_wait() { - let until = clock::now() + rem; - self.inner.sleep = Some(self.inner.timer_handle.delay(until)); + let until = Instant::now() + rem; + self.inner.sleep = Some(time::delay(until)); } else { panic!("unexpected WouldBlock"); } diff --git a/tokio-test/src/lib.rs b/tokio-test/src/lib.rs index 749112d89..e6e9019ef 100644 --- a/tokio-test/src/lib.rs +++ b/tokio-test/src/lib.rs @@ -13,7 +13,6 @@ //! Tokio and Futures based testing utilites -pub mod clock; pub mod io; mod macros; pub mod task; diff --git a/tokio-test/tests/block_on.rs b/tokio-test/tests/block_on.rs index c361d5008..3c0fe32f9 100644 --- a/tokio-test/tests/block_on.rs +++ b/tokio-test/tests/block_on.rs @@ -1,10 +1,8 @@ #![warn(rust_2018_idioms)] -use tokio::time::delay; +use tokio::time::{delay, Duration, Instant}; use tokio_test::block_on; -use std::time::{Duration, Instant}; - #[test] fn async_block() { assert_eq!(4, block_on(async { 4 })); diff --git a/tokio-test/tests/clock.rs b/tokio-test/tests/clock.rs deleted file mode 100644 index d9d2fcfc2..000000000 --- a/tokio-test/tests/clock.rs +++ /dev/null @@ -1,25 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::time::delay; -use tokio_test::clock::MockClock; -use tokio_test::task; -use tokio_test::{assert_pending, assert_ready}; - -use std::time::{Duration, Instant}; - -#[test] -fn clock() { - let mut mock = MockClock::new(); - - mock.enter(|handle| { - let deadline = Instant::now() + Duration::from_secs(1); - let mut delay = task::spawn(delay(deadline)); - - assert_pending!(delay.poll()); - - handle.advance(Duration::from_secs(2)); - - assert!(delay.is_woken()); - assert_ready!(delay.poll()); - }); -} diff --git a/tokio-tls/Cargo.toml b/tokio-tls/Cargo.toml index bfd5b6860..022f6edae 100644 --- a/tokio-tls/Cargo.toml +++ b/tokio-tls/Cargo.toml @@ -26,11 +26,9 @@ travis-ci = { repository = "tokio-rs/tokio-tls" } [dependencies] native-tls = "0.2" -tokio = { version = "=0.2.0-alpha.6", path = "../tokio", features = ["io-traits"] } - -[dev-dependencies] tokio = { version = "=0.2.0-alpha.6", path = "../tokio" } +[dev-dependencies] cfg-if = "0.1" env_logger = { version = "0.6", default-features = false } futures = { version = "0.3.0", features = ["async-await"] } diff --git a/tokio/Cargo.toml b/tokio/Cargo.toml index df2841724..56ac0dc97 100644 --- a/tokio/Cargo.toml +++ b/tokio/Cargo.toml @@ -27,8 +27,8 @@ keywords = ["io", "async", "non-blocking", "futures"] default = [ "blocking", "fs", - "io", - "net-full", + "io-util", + "net", "process", "rt-full", "signal", @@ -36,46 +36,15 @@ default = [ "time", ] -executor-core = [] -blocking = ["executor-core", "sync"] -fs = ["blocking", "io-traits"] -io-traits = ["bytes", "iovec"] -io-util = ["io-traits", "pin-project", "memchr"] -io = ["io-traits", "io-util"] +blocking = ["rt-core"] +dns = ["blocking"] +fs = ["blocking"] +io-driver = ["mio", "lazy_static", "sync"] # TODO: get rid of sync +io-util = ["pin-project", "memchr"] macros = ["tokio-macros"] -net-full = ["tcp", "udp", "uds"] -net-driver = ["io-traits", "mio", "blocking", "lazy_static"] -rt-current-thread = [ - "executor-core", - "time", - "sync", - "net-driver", -] -rt-full = [ - "executor-core", - "macros", - "num_cpus", - "net-full", - "rt-current-thread", - "sync", - "time", -] -signal = [ - "lazy_static", - "libc", - "mio-uds", - "net-driver", - "signal-hook-registry", - "winapi/consoleapi", - "winapi/minwindef", -] -sync = ["fnv"] -tcp = ["io", "net-driver"] -time = ["executor-core", "sync", "slab"] -udp = ["io", "net-driver"] -uds = ["io", "net-driver", "mio-uds", "libc"] +net = ["dns", "tcp", "udp", "uds"] process = [ - "io", + "io-util", # TODO: Get rid of "libc", "mio-named-pipes", "signal", @@ -84,18 +53,44 @@ process = [ "winapi/threadpoollegacyapiset", "winapi/winerror", ] +# Includes basic task execution capabilities +rt-core = [] +rt-full = [ + "macros", + "num_cpus", + "net", + "rt-core", + "sync", + "time", +] +signal = [ + "io-driver", + "lazy_static", + "libc", + "mio-uds", + "signal-hook-registry", + "winapi/consoleapi", + "winapi/minwindef", +] +sync = ["fnv"] +test-util = [] +tcp = ["io-driver"] +time = ["rt-core", "sync", "slab"] +udp = ["io-driver"] +uds = ["io-driver", "mio-uds", "libc"] + [dependencies] tokio-macros = { version = "=0.2.0-alpha.6", optional = true, path = "../tokio-macros" } +bytes = "0.4" futures-core = "0.3.0" futures-sink = "0.3.0" futures-util = { version = "0.3.0", features = ["sink", "channel"] } +iovec = "0.1" # Everything else is optional... -bytes = { version = "0.4", optional = true } fnv = { version = "1.0.6", optional = true } -iovec = { version = "0.1", optional = true } lazy_static = { version = "1.0.2", optional = true } memchr = { version = "2.2", optional = true } mio = { version = "0.6.14", optional = true } diff --git a/tokio/src/blocking/mod.rs b/tokio/src/blocking/mod.rs new file mode 100644 index 000000000..1a2b4d11e --- /dev/null +++ b/tokio/src/blocking/mod.rs @@ -0,0 +1,65 @@ +//! Perform blocking operations from an asynchronous context. + +mod pool; +pub(crate) use self::pool::{BlockingPool, Spawner}; + +mod schedule; +mod task; + +use crate::task::JoinHandle; + +/// Run the provided blocking function without blocking the executor. +/// +/// In general, issuing a blocking call or performing a lot of compute in a +/// future without yielding is not okay, as it may prevent the executor from +/// driving other futures forward. If you run a closure through this method, +/// the current executor thread will relegate all its executor duties to another +/// (possibly new) thread, and only then poll the task. Note that this requires +/// additional synchronization. +/// +/// # Examples +/// +/// ``` +/// # async fn docs() { +/// tokio::blocking::in_place(move || { +/// // do some compute-heavy work or call synchronous code +/// }); +/// # } +/// ``` +#[cfg(feature = "rt-full")] +pub fn in_place(f: F) -> R +where + F: FnOnce() -> R, +{ + use crate::runtime::{enter, thread_pool}; + + enter::exit(|| thread_pool::block_in_place(f)) +} + +/// Run the provided closure on a thread where blocking is acceptable. +/// +/// In general, issuing a blocking call or performing a lot of compute in a future without +/// yielding is not okay, as it may prevent the executor from driving other futures forward. +/// A closure that is run through this method will instead be run on a dedicated thread pool for +/// such blocking tasks without holding up the main futures executor. +/// +/// # Examples +/// +/// ``` +/// # async fn docs() -> Result<(), Box>{ +/// let res = tokio::blocking::spawn_blocking(move || { +/// // do some compute-heavy work or call synchronous code +/// "done computing" +/// }).await?; +/// +/// assert_eq!(res, "done computing"); +/// # Ok(()) +/// # } +/// ``` +pub fn spawn_blocking(f: F) -> JoinHandle +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + pool::spawn(f) +} diff --git a/tokio/src/blocking/pool.rs b/tokio/src/blocking/pool.rs new file mode 100644 index 000000000..451611070 --- /dev/null +++ b/tokio/src/blocking/pool.rs @@ -0,0 +1,291 @@ +//! Thread pool for blocking operations + +use crate::blocking::schedule::NoopSchedule; +use crate::blocking::task::BlockingTask; +use crate::loom::sync::{Arc, Condvar, Mutex}; +use crate::loom::thread; +use crate::task::{self, JoinHandle}; + +use std::cell::Cell; +use std::collections::VecDeque; +use std::fmt; +use std::time::Duration; + +pub(crate) struct BlockingPool { + spawner: Spawner, +} + +#[derive(Clone)] +pub(crate) struct Spawner { + inner: Arc, +} + +struct Inner { + /// State shared between worker threads + shared: Mutex, + + /// Pool threads wait on this. + condvar: Condvar, + + /// Spawned threads use this name + thread_name: String, + + /// Spawned thread stack size + stack_size: Option, +} + +struct Shared { + queue: VecDeque, + num_th: u32, + num_idle: u32, + num_notify: u32, + shutdown: bool, +} + +type Task = task::Task; + +thread_local! { + /// Thread-local tracking the current executor + static BLOCKING: Cell> = Cell::new(None) +} + +const MAX_THREADS: u32 = 1_000; +const KEEP_ALIVE: Duration = Duration::from_secs(10); + +/// Run the provided function on an executor dedicated to blocking operations. +pub(super) fn spawn(func: F) -> JoinHandle +where + F: FnOnce() -> R + Send + 'static, +{ + BLOCKING.with(|cell| { + let schedule = match cell.get() { + Some(ptr) => unsafe { &*ptr }, + None => panic!("not currently running on the Tokio runtime."), + }; + + let (task, handle) = task::joinable(BlockingTask::new(func)); + schedule.schedule(task); + handle + }) +} + +// ===== impl BlockingPool ===== + +impl BlockingPool { + pub(crate) fn new(thread_name: String, stack_size: Option) -> BlockingPool { + BlockingPool { + spawner: Spawner { + inner: Arc::new(Inner { + shared: Mutex::new(Shared { + queue: VecDeque::new(), + num_th: 0, + num_idle: 0, + num_notify: 0, + shutdown: false, + }), + condvar: Condvar::new(), + thread_name, + stack_size, + }), + }, + } + } + + pub(crate) fn spawner(&self) -> &Spawner { + &self.spawner + } +} + +impl Drop for BlockingPool { + fn drop(&mut self) { + let mut shared = self.spawner.inner.shared.lock().unwrap(); + shared.shutdown = true; + self.spawner.inner.condvar.notify_all(); + + while shared.num_th > 0 { + shared = self.spawner.inner.condvar.wait(shared).unwrap(); + } + } +} + +impl fmt::Debug for BlockingPool { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("BlockingPool").finish() + } +} + +// ===== impl Spawner ===== + +impl Spawner { + #[cfg(feature = "rt-full")] + pub(crate) fn spawn_background(&self, func: F) + where + F: FnOnce() + Send + 'static, + { + let task = task::background(BlockingTask::new(func)); + self.schedule(task); + } + + /// Set the blocking pool for the duration of the closure + /// + /// If a blocking pool is already set, it will be restored when the closure + /// returns or if it panics. + pub(crate) fn enter(&self, f: F) -> R + where + F: FnOnce() -> R, + { + // While scary, this is safe. The function takes a `&BlockingPool`, + // which guarantees that the reference lives for the duration of + // `with_pool`. + // + // Because we are always clearing the TLS value at the end of the + // function, we can cast the reference to 'static which thread-local + // cells require. + BLOCKING.with(|cell| { + let was = cell.replace(None); + + // Ensure that the pool is removed from the thread-local context + // when leaving the scope. This handles cases that involve panicking. + struct Reset<'a>(&'a Cell>, Option<*const Spawner>); + + impl Drop for Reset<'_> { + fn drop(&mut self) { + self.0.set(self.1); + } + } + + let _reset = Reset(cell, was); + cell.set(Some(self as *const Spawner)); + f() + }) + } + + fn schedule(&self, task: Task) { + let should_spawn_thread = { + let mut shared = self.inner.shared.lock().unwrap(); + + if shared.shutdown { + // no need to even push this task; it would never get picked up + return; + } + + shared.queue.push_back(task); + + if shared.num_idle == 0 { + // No threads are able to process the task. + + if shared.num_th == MAX_THREADS { + // At max number of threads + false + } else { + shared.num_th += 1; + true + } + } else { + // Notify an idle worker thread. The notification counter + // is used to count the needed amount of notifications + // exactly. Thread libraries may generate spurious + // wakeups, this counter is used to keep us in a + // consistent state. + shared.num_idle -= 1; + shared.num_notify += 1; + self.inner.condvar.notify_one(); + false + } + }; + + if should_spawn_thread { + self.spawn_thread(); + } + } + + fn spawn_thread(&self) { + let mut builder = thread::Builder::new().name(self.inner.thread_name.clone()); + + if let Some(stack_size) = self.inner.stack_size { + builder = builder.stack_size(stack_size); + } + + let inner = self.inner.clone(); + + builder + .spawn(move || { + let mut shared = inner.shared.lock().unwrap(); + + 'main: loop { + // BUSY + while let Some(task) = shared.queue.pop_front() { + drop(shared); + run_task(task); + + shared = inner.shared.lock().unwrap(); + if shared.shutdown { + break; // Need to increment idle before we exit + } + } + + // IDLE + shared.num_idle += 1; + + while !shared.shutdown { + let lock_result = inner.condvar.wait_timeout(shared, KEEP_ALIVE).unwrap(); + + shared = lock_result.0; + let timeout_result = lock_result.1; + + if shared.num_notify != 0 { + // We have received a legitimate wakeup, + // acknowledge it by decrementing the counter + // and transition to the BUSY state. + shared.num_notify -= 1; + break; + } + + if timeout_result.timed_out() { + break 'main; + } + + // Spurious wakeup detected, go back to sleep. + } + + if shared.shutdown { + // Work was produced, and we "took" it (by decrementing num_notify). + // This means that num_idle was decremented once for our wakeup. + // But, since we are exiting, we need to "undo" that, as we'll stay idle. + shared.num_idle += 1; + // NOTE: Technically we should also do num_notify++ and notify again, + // but since we're shutting down anyway, that won't be necessary. + break; + } + } + + // Thread exit + shared.num_th -= 1; + + // num_idle should now be tracked exactly, panic + // with a descriptive message if it is not the + // case. + shared.num_idle = shared + .num_idle + .checked_sub(1) + .expect("num_idle underflowed on thread exit"); + + if shared.shutdown && shared.num_th == 0 { + inner.condvar.notify_one(); + } + }) + .unwrap(); + } +} + +impl fmt::Debug for Spawner { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("blocking::Spawner").finish() + } +} + +fn run_task(f: Task) { + let scheduler: &'static NoopSchedule = &NoopSchedule; + let res = f.run(|| Some(scheduler.into())); + assert!(res.is_none()); +} diff --git a/tokio/src/blocking/schedule.rs b/tokio/src/blocking/schedule.rs new file mode 100644 index 000000000..461b12c3f --- /dev/null +++ b/tokio/src/blocking/schedule.rs @@ -0,0 +1,18 @@ +use crate::task::{Schedule, Task}; + +/// `task::Schedule` implementation that does nothing. This is unique to the +/// blocking scheduler as tasks scheduled are not really futures but blocking +/// operations. +pub(super) struct NoopSchedule; + +impl Schedule for NoopSchedule { + fn bind(&self, _task: &Task) {} + + fn release(&self, _task: Task) {} + + fn release_local(&self, _task: &Task) {} + + fn schedule(&self, _task: Task) { + unreachable!(); + } +} diff --git a/tokio/src/blocking/task.rs b/tokio/src/blocking/task.rs new file mode 100644 index 000000000..8ea3bace9 --- /dev/null +++ b/tokio/src/blocking/task.rs @@ -0,0 +1,32 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// Converts a function to a future that completes on poll +pub(super) struct BlockingTask { + func: Option, +} + +impl BlockingTask { + /// Initialize a new blocking task from the given function + pub(super) fn new(func: T) -> BlockingTask { + BlockingTask { func: Some(func) } + } +} + +impl Future for BlockingTask +where + T: FnOnce() -> R, +{ + type Output = R; + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + let me = unsafe { self.get_unchecked_mut() }; + let func = me + .func + .take() + .expect("[internal exception] blocking task ran twice."); + + Poll::Ready(func()) + } +} diff --git a/tokio/src/fs/blocking.rs b/tokio/src/fs/blocking.rs index 3a9f754c9..695358a39 100644 --- a/tokio/src/fs/blocking.rs +++ b/tokio/src/fs/blocking.rs @@ -74,7 +74,7 @@ where })); } Busy(ref mut rx) => { - let (res, mut buf, inner) = ready!(Pin::new(rx).poll(cx)); + let (res, mut buf, inner) = ready!(Pin::new(rx).poll(cx))?; self.inner = Some(inner); match res { @@ -126,7 +126,7 @@ where return Ready(Ok(n)); } Busy(ref mut rx) => { - let (res, buf, inner) = ready!(Pin::new(rx).poll(cx)); + let (res, buf, inner) = ready!(Pin::new(rx).poll(cx))?; self.state = Idle(Some(buf)); self.inner = Some(inner); @@ -158,7 +158,7 @@ where } } Busy(ref mut rx) => { - let (res, buf, inner) = ready!(Pin::new(rx).poll(cx)); + let (res, buf, inner) = ready!(Pin::new(rx).poll(cx))?; self.state = Idle(Some(buf)); self.inner = Some(inner); diff --git a/tokio/src/fs/file.rs b/tokio/src/fs/file.rs index 9b81a2784..3f18831e5 100644 --- a/tokio/src/fs/file.rs +++ b/tokio/src/fs/file.rs @@ -223,7 +223,7 @@ impl File { let (op, buf) = match self.state { Idle(_) => unreachable!(), - Busy(ref mut rx) => rx.await, + Busy(ref mut rx) => rx.await.unwrap(), }; self.state = Idle(Some(buf)); @@ -343,7 +343,7 @@ impl File { let (op, buf) = match self.state { Idle(_) => unreachable!(), - Busy(ref mut rx) => rx.await, + Busy(ref mut rx) => rx.await?, }; self.state = Idle(Some(buf)); @@ -464,7 +464,7 @@ impl AsyncRead for File { })); } Busy(ref mut rx) => { - let (op, mut buf) = ready!(Pin::new(rx).poll(cx)); + let (op, mut buf) = ready!(Pin::new(rx).poll(cx))?; match op { Operation::Read(Ok(_)) => { @@ -537,7 +537,7 @@ impl AsyncWrite for File { return Ready(Ok(n)); } Busy(ref mut rx) => { - let (op, buf) = ready!(Pin::new(rx).poll(cx)); + let (op, buf) = ready!(Pin::new(rx).poll(cx))?; self.state = Idle(Some(buf)); match op { @@ -570,7 +570,7 @@ impl AsyncWrite for File { let (op, buf) = match self.state { Idle(_) => return Ready(Ok(())), - Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx)), + Busy(ref mut rx) => ready!(Pin::new(rx).poll(cx))?, }; // The buffer is not used here diff --git a/tokio/src/fs/mod.rs b/tokio/src/fs/mod.rs index ed3b4162d..9108116ab 100644 --- a/tokio/src/fs/mod.rs +++ b/tokio/src/fs/mod.rs @@ -84,12 +84,20 @@ where F: FnOnce() -> io::Result + Send + 'static, T: Send + 'static, { - sys::run(f).await + match sys::run(f).await { + Ok(res) => res, + Err(_) => Err(io::Error::new( + io::ErrorKind::Other, + "background task failed", + )), + } } /// Types in this module can be mocked out in tests. mod sys { pub(crate) use std::fs::File; - pub(crate) use crate::runtime::blocking::{run, Blocking}; + // TODO: don't rename + pub(crate) use crate::blocking::spawn_blocking as run; + pub(crate) use crate::task::JoinHandle as Blocking; } diff --git a/tokio/src/fs/read_dir.rs b/tokio/src/fs/read_dir.rs index 0fd5bf0b5..9492a2f4c 100644 --- a/tokio/src/fs/read_dir.rs +++ b/tokio/src/fs/read_dir.rs @@ -65,7 +65,7 @@ impl Stream for ReadDir { })); } State::Pending(ref mut rx) => { - let (ret, std) = ready!(Pin::new(rx).poll(cx)); + let (ret, std) = ready!(Pin::new(rx).poll(cx))?; self.0 = State::Idle(Some(std)); let ret = ret.map(|res| res.map(|std| DirEntry(Arc::new(std)))); diff --git a/tokio/src/lib.rs b/tokio/src/lib.rs index 8f9736ea0..3614bb223 100644 --- a/tokio/src/lib.rs +++ b/tokio/src/lib.rs @@ -69,38 +69,36 @@ //! } //! } //! ``` -macro_rules! if_runtime { - ($($i:item)*) => ($( - #[cfg(any( - feature = "blocking", - feature = "rt-full", - feature = "rt-current-thread", - ))] - $i - )*) -} #[cfg(all(loom, test))] macro_rules! thread_local { ($($tts:tt)+) => { loom::thread_local!{ $($tts)+ } } } +// At the top due to macros +#[cfg(test)] +#[macro_use] +mod tests; + +#[cfg(feature = "blocking")] +pub mod blocking; + #[cfg(feature = "fs")] pub mod fs; pub mod future; -#[cfg(feature = "io-traits")] pub mod io; -#[cfg(feature = "net-driver")] +#[cfg(feature = "io-driver")] pub mod net; mod loom; pub mod prelude; -#[cfg(all(feature = "process", not(loom)))] +#[cfg(feature = "process")] +#[cfg(not(loom))] pub mod process; pub mod runtime; @@ -114,26 +112,27 @@ pub mod stream; #[cfg(feature = "sync")] pub mod sync; +#[cfg(feature = "rt-core")] +pub mod task; + #[cfg(feature = "time")] pub mod time; #[cfg(feature = "rt-full")] mod util; -if_runtime! { +#[doc(inline)] +#[cfg(feature = "rt-core")] +pub use crate::runtime::spawn; - #[doc(inline)] - pub use crate::runtime::spawn; +#[cfg(not(test))] // Work around for rust-lang/rust#62127 +#[cfg(feature = "macros")] +#[doc(inline)] +pub use tokio_macros::main; - #[cfg(not(test))] // Work around for rust-lang/rust#62127 - #[cfg(feature = "macros")] - #[doc(inline)] - pub use tokio_macros::main; - - #[cfg(feature = "macros")] - #[doc(inline)] - pub use tokio_macros::test; -} +#[cfg(feature = "macros")] +#[doc(inline)] +pub use tokio_macros::test; #[cfg(feature = "io-util")] #[cfg(test)] diff --git a/tokio/src/time/atomic.rs b/tokio/src/loom/std/atomic_u64.rs similarity index 100% rename from tokio/src/time/atomic.rs rename to tokio/src/loom/std/atomic_u64.rs diff --git a/tokio/src/loom/std/mod.rs b/tokio/src/loom/std/mod.rs index e0aafa83a..c5bd6039f 100644 --- a/tokio/src/loom/std/mod.rs +++ b/tokio/src/loom/std/mod.rs @@ -1,8 +1,7 @@ -// rt-full implies rt-current-thread - #![cfg_attr(not(feature = "rt-full"), allow(unused_imports, dead_code))] mod atomic_u32; +mod atomic_u64; mod atomic_usize; mod causal_cell; @@ -43,6 +42,7 @@ pub(crate) mod sync { pub(crate) mod atomic { pub(crate) use crate::loom::std::atomic_u32::AtomicU32; + pub(crate) use crate::loom::std::atomic_u64::AtomicU64; pub(crate) use crate::loom::std::atomic_usize::AtomicUsize; pub(crate) use std::sync::atomic::spin_loop_hint; diff --git a/tokio/src/net/addr.rs b/tokio/src/net/addr.rs index 8fe02b4ea..9b3cc2bcb 100644 --- a/tokio/src/net/addr.rs +++ b/tokio/src/net/addr.rs @@ -1,8 +1,8 @@ -use crate::runtime::blocking; - use futures_util::future; use std::io; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; +use std::net::{IpAddr, SocketAddr}; +#[cfg(feature = "dns")] +use std::net::{Ipv4Addr, Ipv6Addr}; /// Convert or resolve without blocking to one or more `SocketAddr` values. /// @@ -33,13 +33,16 @@ impl sealed::ToSocketAddrsPriv for SocketAddr { // ===== impl str ===== +#[cfg(feature = "dns")] impl ToSocketAddrs for str {} +#[cfg(feature = "dns")] impl sealed::ToSocketAddrsPriv for str { type Iter = sealed::OneOrMore; type Future = sealed::MaybeReady; fn to_socket_addrs(&self) -> Self::Future { + use crate::blocking; use sealed::MaybeReady; // First check if the input parses as a socket address @@ -52,7 +55,7 @@ impl sealed::ToSocketAddrsPriv for str { // Run DNS lookup on the blocking pool let s = self.to_owned(); - MaybeReady::Blocking(blocking::run(move || { + MaybeReady::Blocking(blocking::spawn_blocking(move || { std::net::ToSocketAddrs::to_socket_addrs(&s) })) } @@ -60,13 +63,16 @@ impl sealed::ToSocketAddrsPriv for str { // ===== impl (&str, u16) ===== +#[cfg(feature = "dns")] impl ToSocketAddrs for (&'_ str, u16) {} +#[cfg(feature = "dns")] impl sealed::ToSocketAddrsPriv for (&'_ str, u16) { type Iter = sealed::OneOrMore; type Future = sealed::MaybeReady; fn to_socket_addrs(&self) -> Self::Future { + use crate::blocking; use sealed::MaybeReady; use std::net::{SocketAddrV4, SocketAddrV6}; @@ -89,7 +95,7 @@ impl sealed::ToSocketAddrsPriv for (&'_ str, u16) { let host = host.to_owned(); - MaybeReady::Blocking(blocking::run(move || { + MaybeReady::Blocking(blocking::spawn_blocking(move || { std::net::ToSocketAddrs::to_socket_addrs(&(&host[..], port)) })) } @@ -111,8 +117,10 @@ impl sealed::ToSocketAddrsPriv for (IpAddr, u16) { // ===== impl String ===== +#[cfg(feature = "dns")] impl ToSocketAddrs for String {} +#[cfg(feature = "dns")] impl sealed::ToSocketAddrsPriv for String { type Iter = ::Iter; type Future = ::Future; @@ -143,15 +151,19 @@ pub(crate) mod sealed { //! part of the `ToSocketAddrs` public API. The details will change over //! time. - use crate::runtime::blocking::Blocking; + #[cfg(feature = "dns")] + use crate::task::JoinHandle; - use futures_core::ready; use std::future::Future; use std::io; use std::net::SocketAddr; + #[cfg(feature = "dns")] use std::option; + #[cfg(feature = "dns")] use std::pin::Pin; + #[cfg(feature = "dns")] use std::task::{Context, Poll}; + #[cfg(feature = "dns")] use std::vec; #[doc(hidden)] @@ -164,29 +176,34 @@ pub(crate) mod sealed { #[doc(hidden)] #[derive(Debug)] + #[cfg(feature = "dns")] pub enum MaybeReady { Ready(Option), - Blocking(Blocking>>), + Blocking(JoinHandle>>), } #[doc(hidden)] #[derive(Debug)] + #[cfg(feature = "dns")] pub enum OneOrMore { One(option::IntoIter), More(vec::IntoIter), } + #[cfg(feature = "dns")] impl Future for MaybeReady { type Output = io::Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + use futures_core::ready; + match *self { MaybeReady::Ready(ref mut i) => { let iter = OneOrMore::One(i.take().into_iter()); Poll::Ready(Ok(iter)) } MaybeReady::Blocking(ref mut rx) => { - let res = ready!(Pin::new(rx).poll(cx)).map(OneOrMore::More); + let res = ready!(Pin::new(rx).poll(cx))?.map(OneOrMore::More); Poll::Ready(res) } @@ -194,6 +211,7 @@ pub(crate) mod sealed { } } + #[cfg(feature = "dns")] impl Iterator for OneOrMore { type Item = SocketAddr; diff --git a/tokio/src/prelude.rs b/tokio/src/prelude.rs index 26144c4d1..956003ec6 100644 --- a/tokio/src/prelude.rs +++ b/tokio/src/prelude.rs @@ -26,8 +26,7 @@ pub use futures_util::sink::SinkExt as _; #[doc(no_inline)] pub use futures_util::stream::StreamExt as _; -#[cfg(feature = "io")] pub use crate::io::{AsyncBufRead, AsyncRead, AsyncWrite}; -#[cfg(feature = "io")] +#[cfg(feature = "io-util")] #[doc(no_inline)] pub use crate::io::{AsyncBufReadExt as _, AsyncReadExt as _, AsyncWriteExt as _}; diff --git a/tokio/src/runtime/blocking.rs b/tokio/src/runtime/blocking.rs new file mode 100644 index 000000000..39d103bd5 --- /dev/null +++ b/tokio/src/runtime/blocking.rs @@ -0,0 +1,45 @@ +//! Abstracts out the APIs necessary to `Runtime` for integrating the blocking +//! pool. When the `blocking` feature flag is **not** enabled. These APIs are +//! shells. This isolates the complexity of dealing with conditional +//! compilation. + +pub(crate) use self::variant::*; + +#[cfg(feature = "blocking")] +mod variant { + pub(crate) use crate::blocking::BlockingPool; + pub(crate) use crate::blocking::Spawner; + + use crate::runtime::Builder; + + pub(crate) fn create_blocking_pool(builder: &Builder) -> BlockingPool { + BlockingPool::new(builder.thread_name.clone(), builder.thread_stack_size) + } +} + +#[cfg(not(feature = "blocking"))] +mod variant { + use crate::runtime::Builder; + + #[derive(Debug, Clone)] + pub(crate) struct BlockingPool {} + + pub(crate) use BlockingPool as Spawner; + + pub(crate) fn create_blocking_pool(_builder: &Builder) -> BlockingPool { + BlockingPool {} + } + + impl BlockingPool { + pub(crate) fn spawner(&self) -> &BlockingPool { + self + } + + pub(crate) fn enter(&self, f: F) -> R + where + F: FnOnce() -> R, + { + f() + } + } +} diff --git a/tokio/src/runtime/blocking/mod.rs b/tokio/src/runtime/blocking/mod.rs deleted file mode 100644 index 941babaad..000000000 --- a/tokio/src/runtime/blocking/mod.rs +++ /dev/null @@ -1,366 +0,0 @@ -//! Thread pool for blocking operations - -use crate::loom::sync::{Arc, Condvar, Mutex}; -use crate::loom::thread; -#[cfg(feature = "blocking")] -use crate::sync::oneshot; - -use std::cell::Cell; -use std::collections::VecDeque; -use std::fmt; -#[cfg(feature = "blocking")] -use std::future::Future; -use std::ops::Deref; -#[cfg(feature = "blocking")] -use std::pin::Pin; -#[cfg(feature = "blocking")] -use std::task::{Context, Poll}; -use std::time::Duration; - -#[derive(Clone, Copy)] -enum State { - Empty, - Ready(*const Arc), -} - -thread_local! { - /// Thread-local tracking the current executor - static BLOCKING: Cell = Cell::new(State::Empty) -} - -/// Set the blocking pool for the duration of the closure -/// -/// If a blocking pool is already set, it will be restored when the closure returns or if it -/// panics. -#[allow(dead_code)] // we allow dead code since this won't be called if no executors are enabled -pub(crate) fn with_pool(pool: &Arc, f: F) -> R -where - F: FnOnce() -> R, -{ - // While scary, this is safe. The function takes a `&Pool`, which guarantees - // that the reference lives for the duration of `with_pool`. - // - // Because we are always clearing the TLS value at the end of the - // function, we can cast the reference to 'static which thread-local - // cells require. - BLOCKING.with(|cell| { - let was = cell.replace(State::Empty); - - // Ensure that the pool is removed from the thread-local context - // when leaving the scope. This handles cases that involve panicking. - struct Reset<'a>(&'a Cell, State); - - impl Drop for Reset<'_> { - fn drop(&mut self) { - self.0.set(self.1); - } - } - - let _reset = Reset(cell, was); - cell.set(State::Ready(pool as *const _)); - f() - }) -} - -pub(crate) struct Pool { - /// State shared between worker threads - shared: Mutex, - - /// Pool threads wait on this. - condvar: Condvar, - - /// Spawned threads use this name - thread_name: String, - - /// Spawned thread stack size - stack_size: Option, -} - -#[derive(Debug)] -pub(crate) struct PoolWaiter(Arc); - -impl fmt::Debug for Pool { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Pool").finish() - } -} - -struct Shared { - queue: VecDeque>, - num_th: u32, - num_idle: u32, - num_notify: u32, - shutdown: bool, -} - -const MAX_THREADS: u32 = 1_000; -const KEEP_ALIVE: Duration = Duration::from_secs(10); - -/// Result of a blocking operation running on the blocking thread pool. -#[cfg(feature = "blocking")] -#[derive(Debug)] -pub struct Blocking { - rx: oneshot::Receiver, -} - -impl Pool { - pub(crate) fn new(thread_name: String, stack_size: Option) -> Arc { - Arc::new(Pool { - shared: Mutex::new(Shared { - queue: VecDeque::new(), - num_th: 0, - num_idle: 0, - num_notify: 0, - shutdown: false, - }), - condvar: Condvar::new(), - thread_name, - stack_size, - }) - } - - /// Run the provided function on an executor dedicated to blocking operations. - pub(crate) fn spawn(this: &Arc, f: Box) { - let should_spawn = { - let mut shared = this.shared.lock().unwrap(); - - if shared.shutdown { - // no need to even push this task; it would never get picked up - return; - } - - shared.queue.push_back(f); - - if shared.num_idle == 0 { - // No threads are able to process the task. - - if shared.num_th == MAX_THREADS { - // At max number of threads - false - } else { - shared.num_th += 1; - true - } - } else { - // Notify an idle worker thread. The notification counter - // is used to count the needed amount of notifications - // exactly. Thread libraries may generate spurious - // wakeups, this counter is used to keep us in a - // consistent state. - shared.num_idle -= 1; - shared.num_notify += 1; - this.condvar.notify_one(); - false - } - }; - - if should_spawn { - Pool::spawn_thread(Arc::clone(this)); - } - } - - // NOTE: we cannot use self here w/o arbitrary_self_types since Arc is loom::Arc - fn spawn_thread(this: Arc) { - let mut builder = thread::Builder::new().name(this.thread_name.clone()); - - if let Some(stack_size) = this.stack_size { - builder = builder.stack_size(stack_size); - } - - builder - .spawn(move || { - let mut shared = this.shared.lock().unwrap(); - 'main: loop { - // BUSY - while let Some(task) = shared.queue.pop_front() { - drop(shared); - run_task(task); - shared = this.shared.lock().unwrap(); - if shared.shutdown { - break; // Need to increment idle before we exit - } - } - - // IDLE - shared.num_idle += 1; - - while !shared.shutdown { - let lock_result = this.condvar.wait_timeout(shared, KEEP_ALIVE).unwrap(); - shared = lock_result.0; - let timeout_result = lock_result.1; - - if shared.num_notify != 0 { - // We have received a legitimate wakeup, - // acknowledge it by decrementing the counter - // and transition to the BUSY state. - shared.num_notify -= 1; - break; - } - - if timeout_result.timed_out() { - break 'main; - } - - // Spurious wakeup detected, go back to sleep. - } - - if shared.shutdown { - // Work was produced, and we "took" it (by decrementing num_notify). - // This means that num_idle was decremented once for our wakeup. - // But, since we are exiting, we need to "undo" that, as we'll stay idle. - shared.num_idle += 1; - // NOTE: Technically we should also do num_notify++ and notify again, - // but since we're shutting down anyway, that won't be necessary. - break; - } - } - - // Thread exit - shared.num_th -= 1; - - // num_idle should now be tracked exactly, panic - // with a descriptive message if it is not the - // case. - shared.num_idle = shared - .num_idle - .checked_sub(1) - .expect("num_idle underflowed on thread exit"); - - if shared.shutdown && shared.num_th == 0 { - this.condvar.notify_one(); - } - }) - .unwrap(); - } - - /// Shut down all workers in the pool the next time they are idle. - /// - /// Blocks until all threads have exited. - pub(crate) fn shutdown(&self) { - let mut shared = self.shared.lock().unwrap(); - shared.shutdown = true; - self.condvar.notify_all(); - - while shared.num_th > 0 { - shared = self.condvar.wait(shared).unwrap(); - } - } -} - -impl From for PoolWaiter { - fn from(p: Pool) -> Self { - Self::from(Arc::new(p)) - } -} - -impl From> for PoolWaiter { - fn from(p: Arc) -> Self { - Self(p) - } -} - -impl Deref for PoolWaiter { - type Target = Arc; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl Drop for PoolWaiter { - fn drop(&mut self) { - self.0.shutdown(); - } -} - -/// Run the provided blocking function without blocking the executor. -/// -/// In general, issuing a blocking call or performing a lot of compute in a -/// future without yielding is not okay, as it may prevent the executor from -/// driving other futures forward. If you run a closure through this method, -/// the current executor thread will relegate all its executor duties to another -/// (possibly new) thread, and only then poll the task. Note that this requires -/// additional synchronization. -/// -/// # Examples -/// -/// ``` -/// # async fn docs() { -/// tokio::runtime::blocking::in_place(move || { -/// // do some compute-heavy work or call synchronous code -/// }); -/// # } -/// ``` -#[cfg(feature = "rt-full")] -pub fn in_place(f: F) -> R -where - F: FnOnce() -> R, -{ - use crate::runtime::{enter, thread_pool}; - - enter::exit(|| thread_pool::blocking(f)) -} - -/// Run the provided closure on a thread where blocking is acceptable. -/// -/// In general, issuing a blocking call or performing a lot of compute in a future without -/// yielding is not okay, as it may prevent the executor from driving other futures forward. -/// A closure that is run through this method will instead be run on a dedicated thread pool for -/// such blocking tasks without holding up the main futures executor. -/// -/// # Examples -/// -/// ``` -/// # async fn docs() { -/// tokio::runtime::blocking::run(move || { -/// // do some compute-heavy work or call synchronous code -/// }).await; -/// # } -/// ``` -#[cfg(feature = "blocking")] -pub fn run(f: F) -> Blocking -where - F: FnOnce() -> R + Send + 'static, - R: Send + 'static, -{ - let (tx, rx) = oneshot::channel(); - - BLOCKING.with(|current_pool| match current_pool.get() { - State::Ready(pool) => { - let pool = unsafe { &*pool }; - Pool::spawn( - pool, - Box::new(move || { - // receiver may have gone away - let _ = tx.send(f()); - }), - ); - } - State::Empty => panic!("must be called from the context of Tokio runtime"), - }); - - Blocking { rx } -} - -#[cfg(feature = "blocking")] -impl Future for Blocking { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - use std::task::Poll::*; - - match Pin::new(&mut self.rx).poll(cx) { - Ready(Ok(v)) => Ready(v), - Ready(Err(_)) => panic!( - "the blocking operation has been dropped before completing. \ - This should not happen and is a bug." - ), - Pending => Pending, - } - } -} - -fn run_task(f: Box) { - use std::panic::{catch_unwind, AssertUnwindSafe}; - - let _ = catch_unwind(AssertUnwindSafe(|| f())); -} diff --git a/tokio/src/runtime/builder.rs b/tokio/src/runtime/builder.rs index 3a81af3e5..66c9e166c 100644 --- a/tokio/src/runtime/builder.rs +++ b/tokio/src/runtime/builder.rs @@ -1,7 +1,7 @@ use crate::loom::sync::Arc; -#[cfg(feature = "blocking")] -use crate::runtime::blocking; -use crate::runtime::{io, timer, Runtime}; +use crate::runtime::handle::{self, Handle}; +use crate::runtime::shell::Shell; +use crate::runtime::{blocking, io, time, Runtime}; use std::fmt; @@ -22,12 +22,10 @@ use std::fmt; /// /// ``` /// use tokio::runtime::Builder; -/// use tokio::time::clock::Clock; /// /// fn main() { /// // build Runtime /// let runtime = Builder::new() -/// .clock(Clock::system()) /// .num_threads(4) /// .thread_name("my-custom-name") /// .thread_stack_size(3 * 1024 * 1024) @@ -47,25 +45,22 @@ pub struct Builder { num_threads: usize, /// Name used for threads spawned by the runtime. - thread_name: String, + pub(super) thread_name: String, /// Stack size used for threads spawned by the runtime. - thread_stack_size: Option, + pub(super) thread_stack_size: Option, /// Callback to run after each thread starts. after_start: Option, /// To run before each worker thread stops before_stop: Option, - - /// The clock to use - clock: timer::Clock, } #[derive(Debug)] enum Kind { Shell, - #[cfg(feature = "rt-current-thread")] + #[cfg(feature = "rt-core")] CurrentThread, #[cfg(feature = "rt-full")] ThreadPool, @@ -99,9 +94,6 @@ impl Builder { // No worker thread callbacks after_start: None, before_stop: None, - - // Default clock - clock: timer::Clock::default(), } } @@ -131,9 +123,9 @@ impl Builder { /// Use only the current thread for executing tasks. /// - /// The network driver, timer, and executor will all be run on the current + /// The executor and all necessary drivers will all be run on the current /// thread during `block_on` calls. - #[cfg(feature = "rt-current-thread")] + #[cfg(feature = "rt-core")] pub fn current_thread(&mut self) -> &mut Self { self.kind = Kind::CurrentThread; self @@ -243,12 +235,6 @@ impl Builder { self } - /// Set the `Clock` instance that will be used by the runtime. - pub fn clock(&mut self, clock: timer::Clock) -> &mut Self { - self.clock = clock; - self - } - /// Create the configured `Runtime`. /// /// The returned `ThreadPool` instance is ready to spawn tasks. @@ -267,7 +253,7 @@ impl Builder { pub fn build(&mut self) -> io::Result { match self.kind { Kind::Shell => self.build_shell(), - #[cfg(feature = "rt-current-thread")] + #[cfg(feature = "rt-core")] Kind::CurrentThread => self.build_current_thread(), #[cfg(feature = "rt-full")] Kind::ThreadPool => self.build_threadpool(), @@ -277,93 +263,108 @@ impl Builder { fn build_shell(&mut self) -> io::Result { use crate::runtime::Kind; - // Create network driver - let (net, handle) = io::create()?; - let net_handles = vec![handle]; + let clock = time::create_clock(); - let (_timer, handle) = timer::create(net, self.clock.clone()); - let timer_handles = vec![handle]; + // Create I/O driver + let (io_driver, handle) = io::create_driver()?; + let io_handles = vec![handle]; + + let (driver, handle) = time::create_driver(io_driver, clock.clone()); + let time_handles = vec![handle]; + + let blocking_pool = blocking::create_blocking_pool(self); + let blocking_spawner = blocking_pool.spawner().clone(); Ok(Runtime { - kind: Kind::Shell, - net_handles, - timer_handles, - #[cfg(feature = "blocking")] - blocking_pool: self.build_blocking_pool().into(), + kind: Kind::Shell(Shell::new(driver)), + handle: Handle { + kind: handle::Kind::Shell, + io_handles, + time_handles, + clock, + blocking_spawner, + }, + blocking_pool, }) } - #[cfg(feature = "rt-current-thread")] + #[cfg(feature = "rt-core")] fn build_current_thread(&mut self) -> io::Result { use crate::runtime::{CurrentThread, Kind}; - // Create network driver - let (net, handle) = io::create()?; - let net_handles = vec![handle]; + let clock = time::create_clock(); - let (timer, handle) = timer::create(net, self.clock.clone()); - let timer_handles = vec![handle]; + // Create I/O driver + let (io_driver, handle) = io::create_driver()?; + let io_handles = vec![handle]; - // And now put a single-threaded executor on top of the timer. When + let (driver, handle) = time::create_driver(io_driver, clock.clone()); + let time_handles = vec![handle]; + + // And now put a single-threaded scheduler 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(timer); + let scheduler = CurrentThread::new(driver); + let spawner = scheduler.spawner(); // Blocking pool - let blocking_pool = self.build_blocking_pool(); + let blocking_pool = blocking::create_blocking_pool(self); + let blocking_spawner = blocking_pool.spawner().clone(); Ok(Runtime { - kind: Kind::CurrentThread(executor), - net_handles, - timer_handles, - blocking_pool: blocking_pool.into(), + kind: Kind::CurrentThread(scheduler), + handle: Handle { + kind: handle::Kind::CurrentThread(spawner), + io_handles, + time_handles, + clock, + blocking_spawner, + }, + blocking_pool, }) } #[cfg(feature = "rt-full")] fn build_threadpool(&mut self) -> io::Result { use crate::runtime::{Kind, ThreadPool}; - use crate::time::clock; use std::sync::Mutex; - let mut net_handles = Vec::new(); - let mut timer_handles = Vec::new(); - let mut timers = Vec::new(); + let clock = time::create_clock(); + + let mut io_handles = Vec::new(); + let mut time_handles = Vec::new(); + let mut drivers = Vec::new(); for _ in 0..self.num_threads { - // Create network driver and handle - let (net, handle) = io::create()?; - net_handles.push(handle); + // Create I/O driver and handle + let (io_driver, handle) = io::create_driver()?; + io_handles.push(handle); // Create a new timer. - let (timer, handle) = timer::create(net, self.clock.clone()); - timer_handles.push(handle); - timers.push(Mutex::new(Some(timer))); + let (time_driver, handle) = time::create_driver(io_driver, clock.clone()); + time_handles.push(handle); + drivers.push(Mutex::new(Some(time_driver))); } - // Get a handle to the clock for the runtime. - let clock = self.clock.clone(); - // Create the blocking pool - let blocking_pool = self.build_blocking_pool(); + let blocking_pool = blocking::create_blocking_pool(self); + let blocking_spawner = blocking_pool.spawner().clone(); - let pool = { - let net_handles = net_handles.clone(); - let timer_handles = timer_handles.clone(); + let scheduler = { + let clock = clock.clone(); + let io_handles = io_handles.clone(); + let time_handles = time_handles.clone(); let after_start = self.after_start.clone(); let before_stop = self.before_stop.clone(); let around_worker = Arc::new(Box::new(move |index, next: &mut dyn FnMut()| { - // Configure the network driver - let _net = io::set_default(&net_handles[index]); - - // Configure the clock - clock::with_default(&clock, || { - // Configure the timer - let _timer = timer::set_default(&timer_handles[index]); + // Configure the I/O driver + let _io = io::set_default(&io_handles[index]); + // Configure time + time::with_default(&time_handles[index], &clock, || { // Call the start callback if let Some(after_start) = after_start.as_ref() { after_start(); @@ -382,24 +383,25 @@ impl Builder { ThreadPool::new( self.num_threads, - blocking_pool.clone(), + blocking_pool.spawner().clone(), around_worker, - move |index| timers[index].lock().unwrap().take().unwrap(), + move |index| drivers[index].lock().unwrap().take().unwrap(), ) }; - Ok(Runtime { - kind: Kind::ThreadPool(pool), - net_handles, - timer_handles, - blocking_pool: blocking_pool.into(), - }) - } + let spawner = scheduler.spawner().clone(); - #[cfg(feature = "blocking")] - fn build_blocking_pool(&self) -> Arc { - // Create the blocking pool - blocking::Pool::new(self.thread_name.clone(), self.thread_stack_size) + Ok(Runtime { + kind: Kind::ThreadPool(scheduler), + handle: Handle { + kind: handle::Kind::ThreadPool(spawner), + io_handles, + time_handles, + clock, + blocking_spawner, + }, + blocking_pool, + }) } } @@ -418,7 +420,6 @@ impl fmt::Debug for Builder { .field("thread_stack_size", &self.thread_stack_size) .field("after_start", &self.after_start.as_ref().map(|_| "...")) .field("before_stop", &self.after_start.as_ref().map(|_| "...")) - .field("clock", &self.clock) .finish() } } diff --git a/tokio/src/runtime/current_thread/mod.rs b/tokio/src/runtime/current_thread/mod.rs index d2cf4581f..be233e9b8 100644 --- a/tokio/src/runtime/current_thread/mod.rs +++ b/tokio/src/runtime/current_thread/mod.rs @@ -1,5 +1,5 @@ use crate::runtime::park::{Park, Unpark}; -use crate::runtime::task::{self, JoinHandle, Schedule, Task}; +use crate::task::{self, JoinHandle, Schedule, Task}; use std::cell::UnsafeCell; use std::collections::VecDeque; diff --git a/tokio/src/runtime/enter.rs b/tokio/src/runtime/enter.rs index 5206c7e3e..9caea0cf9 100644 --- a/tokio/src/runtime/enter.rs +++ b/tokio/src/runtime/enter.rs @@ -1,5 +1,6 @@ use std::cell::{Cell, RefCell}; use std::fmt; +#[cfg(feature = "rt-full")] use std::future::Future; use std::marker::PhantomData; @@ -79,6 +80,7 @@ pub(crate) fn exit R, R>(f: F) -> R { impl Enter { /// Blocks the thread on the specified future, returning the value with /// which that future completes. + #[cfg(feature = "rt-full")] pub(crate) fn block_on(&mut self, mut f: F) -> F::Output { use crate::runtime::park::{CachedParkThread, Park}; use std::pin::Pin; diff --git a/tokio/src/runtime/global.rs b/tokio/src/runtime/global.rs index a1b1bed0d..f1cb8d1b8 100644 --- a/tokio/src/runtime/global.rs +++ b/tokio/src/runtime/global.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "rt-current-thread")] use crate::runtime::current_thread; #[cfg(feature = "rt-full")] @@ -12,13 +11,12 @@ enum State { // default executor not defined Empty, + // Current-thread executor + CurrentThread(*const current_thread::Scheduler), + // default executor is a thread pool instance. #[cfg(feature = "rt-full")] ThreadPool(*const thread_pool::Spawner), - - // Current-thread executor - #[cfg(feature = "rt-current-thread")] - CurrentThread(*const current_thread::Scheduler), } thread_local! { @@ -79,7 +77,6 @@ where let thread_pool = unsafe { &*threadpool_ptr }; thread_pool.spawn_background(future); } - #[cfg(feature = "rt-current-thread")] State::CurrentThread(current_thread_ptr) => { let current_thread = unsafe { &*current_thread_ptr }; @@ -98,7 +95,6 @@ where }) } -#[cfg(feature = "rt-current-thread")] pub(super) fn with_current_thread(current_thread: ¤t_thread::Scheduler, f: F) -> R where F: FnOnce() -> R, @@ -109,7 +105,6 @@ where ) } -#[cfg(feature = "rt-current-thread")] pub(super) fn current_thread_is_current(current_thread: ¤t_thread::Scheduler) -> bool { EXECUTOR.with(|current_executor| match current_executor.get() { State::CurrentThread(ptr) => ptr == current_thread as *const _, @@ -125,7 +120,6 @@ where with_state(State::ThreadPool(thread_pool as *const _), f) } -#[cfg(feature = "rt-current-thread")] fn with_state(state: State, f: F) -> R where F: FnOnce() -> R, diff --git a/tokio/src/runtime/spawner.rs b/tokio/src/runtime/handle.rs similarity index 60% rename from tokio/src/runtime/spawner.rs rename to tokio/src/runtime/handle.rs index 678f09912..c5f2b6715 100644 --- a/tokio/src/runtime/spawner.rs +++ b/tokio/src/runtime/handle.rs @@ -1,47 +1,41 @@ +#[cfg(feature = "rt-core")] use crate::runtime::current_thread; #[cfg(feature = "rt-full")] use crate::runtime::thread_pool; -use crate::runtime::JoinHandle; +use crate::runtime::{blocking, io, time}; +#[cfg(feature = "rt-core")] +use crate::task::JoinHandle; +#[cfg(feature = "rt-core")] use std::future::Future; -/// Spawns futures on the runtime -/// -/// All futures spawned using this executor will be submitted to the associated -/// Runtime's executor. This executor is usually a thread pool. -/// -/// For more details, see the [module level](index.html) documentation. +/// Handle to the runtime #[derive(Debug, Clone)] -pub struct Spawner { - kind: Kind, +pub struct Handle { + pub(super) kind: Kind, + + /// Handles to the I/O drivers + pub(super) io_handles: Vec, + + /// Handles to the time drivers + pub(super) time_handles: Vec, + + pub(super) clock: time::Clock, + + /// Blocking pool spawner + pub(super) blocking_spawner: blocking::Spawner, } #[derive(Debug, Clone)] -enum Kind { +pub(super) enum Kind { Shell, + #[cfg(feature = "rt-core")] + CurrentThread(current_thread::Spawner), #[cfg(feature = "rt-full")] ThreadPool(thread_pool::Spawner), - CurrentThread(current_thread::Spawner), } -impl Spawner { - pub(super) fn shell() -> Spawner { - Spawner { kind: Kind::Shell } - } - - #[cfg(feature = "rt-full")] - pub(super) fn thread_pool(spawner: thread_pool::Spawner) -> Spawner { - Spawner { - kind: Kind::ThreadPool(spawner), - } - } - - pub(super) fn current_thread(spawner: current_thread::Spawner) -> Spawner { - Spawner { - kind: Kind::CurrentThread(spawner), - } - } - +impl Handle { /// Spawn a future onto the Tokio runtime. /// /// This spawns the given future onto the runtime's executor, usually a @@ -60,10 +54,10 @@ impl Spawner { /// # fn dox() { /// // Create the runtime /// let rt = Runtime::new().unwrap(); - /// let spawner = rt.spawner(); + /// let handle = rt.handle(); /// /// // Spawn a future onto the runtime - /// spawner.spawn(async { + /// handle.spawn(async { /// println!("now running on a worker thread"); /// }); /// # } @@ -73,15 +67,29 @@ impl Spawner { /// /// This function panics if the spawn fails. Failure occurs if the executor /// is currently at capacity and is unable to spawn a new future. + #[cfg(feature = "rt-core")] pub fn spawn(&self, future: F) -> JoinHandle where F: Future + Send + 'static, { match &self.kind { Kind::Shell => panic!("spawning not enabled for runtime"), + #[cfg(feature = "rt-core")] + Kind::CurrentThread(spawner) => spawner.spawn(future), #[cfg(feature = "rt-full")] Kind::ThreadPool(spawner) => spawner.spawn(future), - Kind::CurrentThread(spawner) => spawner.spawn(future), } } + + /// Enter the runtime context + pub fn enter(&self, f: F) -> R + where + F: FnOnce() -> R, + { + self.blocking_spawner.enter(|| { + let _io = io::set_default(&self.io_handles[0]); + + time::with_default(&self.time_handles[0], &self.clock, f) + }) + } } diff --git a/tokio/src/runtime/io.rs b/tokio/src/runtime/io.rs index a1ede23a5..35009a67f 100644 --- a/tokio/src/runtime/io.rs +++ b/tokio/src/runtime/io.rs @@ -1,9 +1,14 @@ +//! Abstracts out the APIs necessary to `Runtime` for integrating the I/O +//! driver. When the `time` feature flag is **not** enabled. These APIs are +//! shells. This isolates the complexity of dealing with conditional +//! compilation. + pub(crate) use self::variant::*; /// Re-exported for convenience. pub(crate) use std::io::Result; -#[cfg(feature = "net-driver")] +#[cfg(feature = "io-driver")] mod variant { use crate::net::driver; @@ -21,7 +26,7 @@ mod variant { /// When the `io-driver` feature is **not** enabled, this is `()`. pub(crate) type Handle = driver::Handle; - pub(crate) fn create() -> io::Result<(Driver, Handle)> { + pub(crate) fn create_driver() -> io::Result<(Driver, Handle)> { let driver = driver::Reactor::new()?; let handle = driver.handle(); @@ -33,7 +38,7 @@ mod variant { } } -#[cfg(not(feature = "net-driver"))] +#[cfg(not(feature = "io-driver"))] mod variant { use crate::runtime::park::ParkThread; @@ -45,12 +50,11 @@ mod variant { /// There is no handle pub(crate) type Handle = (); - pub(crate) fn create() -> io::Result<(Driver, Handle)> { + pub(crate) fn create_driver() -> io::Result<(Driver, Handle)> { let driver = ParkThread::new(); Ok((driver, ())) } - #[cfg(feature = "blocking")] pub(crate) fn set_default(_handle: &Handle) {} } diff --git a/tokio/src/runtime/mod.rs b/tokio/src/runtime/mod.rs index c8ff71c83..615c70749 100644 --- a/tokio/src/runtime/mod.rs +++ b/tokio/src/runtime/mod.rs @@ -4,7 +4,7 @@ //! //! * A [driver] to drive I/O resources. //! * An [executor] to execute tasks that use these I/O resources. -//! * A [timer] for scheduling work to run after a set period of time. +//! * A timer for scheduling work to run after a set period of time. //! //! While it is possible to setup each component manually, this involves a bunch //! of boilerplate. @@ -121,7 +121,6 @@ //! //! [driver]: tokio::net::driver //! [executor]: https://tokio.rs/docs/internals/runtime-model/#executors -//! [timer]: ../timer/index.html //! [`Runtime`]: struct.Runtime.html //! [`Reactor`]: ../reactor/struct.Reactor.html //! [`run`]: fn.run.html @@ -133,52 +132,46 @@ #[macro_use] mod tests; -#[cfg(all(not(feature = "blocking"), feature = "rt-full"))] mod blocking; -#[cfg(feature = "blocking")] -pub mod blocking; -#[cfg(feature = "blocking")] -use crate::runtime::blocking::PoolWaiter; +use blocking::BlockingPool; mod builder; pub use self::builder::Builder; -#[cfg(feature = "rt-current-thread")] +#[cfg(feature = "rt-core")] mod current_thread; -#[cfg(feature = "rt-current-thread")] +#[cfg(feature = "rt-core")] use self::current_thread::CurrentThread; -#[cfg(feature = "blocking")] -mod enter; -#[cfg(feature = "blocking")] +pub(crate) mod enter; use self::enter::enter; +#[cfg(feature = "rt-core")] mod global; +#[cfg(feature = "rt-core")] pub use self::global::spawn; +mod handle; +pub use self::handle::Handle; + mod io; mod park; pub use self::park::{Park, Unpark}; -#[cfg(feature = "rt-current-thread")] -mod spawner; -#[cfg(feature = "rt-current-thread")] -pub use self::spawner::Spawner; +mod shell; +use self::shell::Shell; -#[cfg(feature = "rt-current-thread")] -mod task; -#[cfg(feature = "rt-current-thread")] -pub use self::task::{JoinError, JoinHandle}; - -mod timer; +mod time; #[cfg(feature = "rt-full")] pub(crate) mod thread_pool; #[cfg(feature = "rt-full")] use self::thread_pool::ThreadPool; -#[cfg(feature = "blocking")] +#[cfg(feature = "rt-core")] +use crate::task::JoinHandle; + use std::future::Future; /// The Tokio runtime, includes a reactor as well as an executor for running @@ -211,15 +204,11 @@ pub struct Runtime { /// Task executor kind: Kind, - /// Handles to the network drivers - net_handles: Vec, + /// Handle to runtime, also contains driver handles + handle: Handle, - /// Timer handles - timer_handles: Vec, - - /// Blocking pool handle - #[cfg(feature = "blocking")] - blocking_pool: PoolWaiter, + /// Blocking pool handle, used to signal shutdown + blocking_pool: BlockingPool, } /// The runtime executor is either a thread-pool or a current-thread executor. @@ -227,11 +216,11 @@ pub struct Runtime { enum Kind { /// Not able to execute concurrent tasks. This variant is mostly used to get /// access to the driver handles. - Shell, + Shell(Shell), /// Execute all tasks on the current-thread. - #[cfg(feature = "rt-current-thread")] - CurrentThread(CurrentThread), + #[cfg(feature = "rt-core")] + CurrentThread(CurrentThread), /// Execute tasks across multiple threads. #[cfg(feature = "rt-full")] @@ -241,9 +230,9 @@ enum Kind { impl Runtime { /// Create a new runtime instance with default configuration values. /// - /// This results in a reactor, thread pool, and timer being initialized. The - /// thread pool will not spawn any worker threads until it needs to, i.e. - /// tasks are scheduled to run. + /// This results in a thread pool, I/O driver, and time driver being + /// initialized. The thread pool will not spawn any worker threads until it + /// needs to, i.e. tasks are scheduled to run. /// /// Most users will not need to call this function directly, instead they /// will use [`tokio::run`](fn.run.html). @@ -268,10 +257,10 @@ impl Runtime { #[cfg(feature = "rt-full")] let ret = Builder::new().thread_pool().build(); - #[cfg(all(not(feature = "rt-full"), feature = "rt-current-thread"))] + #[cfg(all(not(feature = "rt-full"), feature = "rt-core"))] let ret = Builder::new().current_thread().build(); - #[cfg(not(feature = "rt-current-thread"))] + #[cfg(not(feature = "rt-core"))] let ret = Builder::new().build(); ret @@ -307,13 +296,13 @@ impl Runtime { /// /// This function panics if the spawn fails. Failure occurs if the executor /// is currently at capacity and is unable to spawn a new future. - #[cfg(feature = "rt-current-thread")] + #[cfg(feature = "rt-core")] pub fn spawn(&self, future: F) -> JoinHandle where F: Future + Send + 'static, { match &self.kind { - Kind::Shell => panic!("task execution disabled"), + Kind::Shell(_) => panic!("task execution disabled"), #[cfg(feature = "rt-full")] Kind::ThreadPool(exec) => exec.spawn(future), Kind::CurrentThread(exec) => exec.spawn(future), @@ -333,16 +322,12 @@ impl Runtime { /// /// This function panics if the executor is at capacity, if the provided /// future panics, or if called within an asynchronous execution context. - #[cfg(feature = "blocking")] // TODO: remove this pub fn block_on(&mut self, future: F) -> F::Output { - let _net = io::set_default(&self.net_handles[0]); - let _timer = timer::set_default(&self.timer_handles[0]); - let kind = &mut self.kind; - blocking::with_pool(&self.blocking_pool, || match kind { - Kind::Shell => enter().block_on(future), - #[cfg(feature = "rt-current-thread")] + self.handle.enter(|| match kind { + Kind::Shell(exec) => exec.block_on(future), + #[cfg(feature = "rt-core")] Kind::CurrentThread(exec) => exec.block_on(future), #[cfg(feature = "rt-full")] Kind::ThreadPool(exec) => exec.block_on(future), @@ -361,18 +346,11 @@ impl Runtime { /// let rt = Runtime::new() /// .unwrap(); /// - /// let spawner = rt.spawner(); + /// let handle = rt.handle(); /// - /// spawner.spawn(async { println!("hello"); }); + /// handle.spawn(async { println!("hello"); }); /// ``` - #[cfg(feature = "rt-current-thread")] - pub fn spawner(&self) -> Spawner { - match &self.kind { - Kind::Shell => Spawner::shell(), - #[cfg(feature = "rt-current-thread")] - Kind::CurrentThread(exec) => Spawner::current_thread(exec.spawner()), - #[cfg(feature = "rt-full")] - Kind::ThreadPool(exec) => Spawner::thread_pool(exec.spawner().clone()), - } + pub fn handle(&self) -> &Handle { + &self.handle } } diff --git a/tokio/src/runtime/park/mod.rs b/tokio/src/runtime/park/mod.rs index b8fa81aba..122128323 100644 --- a/tokio/src/runtime/park/mod.rs +++ b/tokio/src/runtime/park/mod.rs @@ -45,9 +45,9 @@ //! [mio]: https://docs.rs/mio/0.6/mio/struct.Poll.html mod thread; -#[cfg(feature = "blocking")] +#[cfg(feature = "rt-full")] pub(crate) use self::thread::CachedParkThread; -#[cfg(not(feature = "net-driver"))] +#[cfg(not(feature = "io-driver"))] pub(crate) use self::thread::ParkThread; use std::sync::Arc; diff --git a/tokio/src/runtime/park/thread.rs b/tokio/src/runtime/park/thread.rs index beebe2a4b..7d9bf4651 100644 --- a/tokio/src/runtime/park/thread.rs +++ b/tokio/src/runtime/park/thread.rs @@ -22,6 +22,7 @@ pub(crate) struct CachedParkThread { _anchor: PhantomData>, } +#[derive(Debug)] pub(crate) struct ParkThread { inner: Arc, } @@ -167,7 +168,7 @@ impl CachedParkThread { /// /// This type cannot be moved to other threads, so it should be created on /// the thread that the caller intends to park. - #[cfg(feature = "blocking")] + #[cfg(feature = "rt-full")] pub(crate) fn new() -> CachedParkThread { CachedParkThread { _anchor: PhantomData, @@ -216,7 +217,7 @@ impl Unpark for UnparkThread { } } -#[cfg(feature = "blocking")] +#[cfg(feature = "rt-full")] mod waker { use super::{Inner, UnparkThread}; use crate::loom::sync::Arc; diff --git a/tokio/src/runtime/shell.rs b/tokio/src/runtime/shell.rs new file mode 100644 index 000000000..552786677 --- /dev/null +++ b/tokio/src/runtime/shell.rs @@ -0,0 +1,79 @@ +use crate::runtime::time; +use crate::runtime::{enter, io, Park}; + +use std::future::Future; +use std::mem::ManuallyDrop; +use std::pin::Pin; +use std::sync::Arc; +use std::task::Poll::Ready; +use std::task::{Context, RawWaker, RawWakerVTable, Waker}; + +#[derive(Debug)] +pub(super) struct Shell { + driver: time::Driver, + + /// TODO: don't store this + waker: Waker, +} + +type Handle = ::Unpark; + +impl Shell { + pub(super) fn new(driver: time::Driver) -> Shell { + let unpark = Arc::new(driver.unpark()); + + let raw_waker = RawWaker::new( + Arc::into_raw(unpark) as *const Handle as *const (), + &RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker), + ); + + let waker = unsafe { Waker::from_raw(raw_waker) }; + + Shell { driver, waker } + } + + pub(super) fn block_on(&mut self, mut f: F) -> F::Output + where + F: Future, + { + let _e = enter(); + + let mut f = unsafe { Pin::new_unchecked(&mut f) }; + let mut cx = Context::from_waker(&self.waker); + + loop { + if let Ready(v) = f.as_mut().poll(&mut cx) { + return v; + } + + self.driver.park().unwrap(); + } + } +} + +fn clone_waker(ptr: *const ()) -> RawWaker { + let w1 = unsafe { ManuallyDrop::new(Arc::from_raw(ptr as *const Handle)) }; + let _w2 = ManuallyDrop::new(w1.clone()); + + RawWaker::new( + ptr, + &RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker), + ) +} + +fn wake(ptr: *const ()) { + use crate::runtime::park::Unpark; + let unpark = unsafe { Arc::from_raw(ptr as *const Handle) }; + (unpark).unpark() +} + +fn wake_by_ref(ptr: *const ()) { + use crate::runtime::park::Unpark; + + let unpark = ptr as *const Handle; + unsafe { (*unpark).unpark() } +} + +fn drop_waker(ptr: *const ()) { + let _ = unsafe { Arc::from_raw(ptr as *const Handle) }; +} diff --git a/tokio/src/runtime/tests/mod.rs b/tokio/src/runtime/tests/mod.rs index b287bcf29..99ed8cd81 100644 --- a/tokio/src/runtime/tests/mod.rs +++ b/tokio/src/runtime/tests/mod.rs @@ -1,40 +1,7 @@ //! Testing utilities -#[cfg(not(loom))] -pub(crate) mod backoff; - #[cfg(loom)] pub(crate) mod loom_oneshot; -#[cfg(loom)] -pub(crate) mod loom_schedule; - #[cfg(not(loom))] pub(crate) mod mock_park; - -pub(crate) mod mock_schedule; - -#[cfg(not(loom))] -pub(crate) mod track_drop; - -/// Panic if expression results in `None`. -#[macro_export] -macro_rules! assert_some { - ($e:expr) => {{ - match $e { - Some(v) => v, - _ => panic!("expected some, was none"), - } - }}; -} - -/// Panic if expression results in `Some`. -#[macro_export] -macro_rules! assert_none { - ($e:expr) => {{ - match $e { - Some(v) => panic!("expected none, was {:?}", v), - _ => {} - } - }}; -} diff --git a/tokio/src/runtime/thread_pool/current.rs b/tokio/src/runtime/thread_pool/current.rs index 1a8113e37..1ab83c54f 100644 --- a/tokio/src/runtime/thread_pool/current.rs +++ b/tokio/src/runtime/thread_pool/current.rs @@ -1,6 +1,6 @@ use crate::loom::sync::Arc; use crate::runtime::park::Unpark; -use crate::runtime::thread_pool::{worker, Owned}; +use crate::runtime::thread_pool::{slice, Owned}; use std::cell::Cell; use std::ptr; @@ -23,7 +23,7 @@ struct Inner { // Pointer to the current worker info thread_local!(static CURRENT_WORKER: Cell = Cell::new(Inner::new())); -pub(super) fn set(pool: &Arc>, index: usize, f: F) -> R +pub(super) fn set(pool: &Arc>, index: usize, f: F) -> R where F: FnOnce() -> R, P: Unpark, @@ -65,7 +65,7 @@ where } impl Current { - pub(super) fn as_member<'a, P>(&self, set: &'a worker::Set

) -> Option<&'a Owned

> + pub(super) fn as_member<'a, P>(&self, set: &'a slice::Set

) -> Option<&'a Owned

> where P: Unpark, { diff --git a/tokio/src/runtime/thread_pool/mod.rs b/tokio/src/runtime/thread_pool/mod.rs index 314942d31..599ce5480 100644 --- a/tokio/src/runtime/thread_pool/mod.rs +++ b/tokio/src/runtime/thread_pool/mod.rs @@ -13,7 +13,7 @@ mod queue; mod spawner; pub(crate) use self::spawner::Spawner; -mod set; +mod slice; mod shared; use self::shared::Shared; @@ -21,10 +21,8 @@ use self::shared::Shared; mod shutdown; mod worker; -use self::worker::Worker; -#[cfg(feature = "blocking")] -pub(crate) use worker::blocking; +pub(crate) use worker::block_in_place; /// Unit tests #[cfg(test)] @@ -39,10 +37,10 @@ const LOCAL_QUEUE_CAPACITY: usize = 256; #[cfg(loom)] const LOCAL_QUEUE_CAPACITY: usize = 2; +use crate::blocking; use crate::loom::sync::Arc; -use crate::runtime::blocking::{self, PoolWaiter}; -use crate::runtime::task::JoinHandle; use crate::runtime::Park; +use crate::task::JoinHandle; use std::fmt; use std::future::Future; @@ -53,9 +51,6 @@ pub(crate) struct ThreadPool { /// Shutdown waiter shutdown_rx: shutdown::Receiver, - - /// Shutdown valve for Pool - blocking: PoolWaiter, } // The Arc> is needed because loom doesn't support Arc where T: !Sized @@ -66,7 +61,7 @@ type Callback = Arc>; impl ThreadPool { pub(crate) fn new( pool_size: usize, - blocking_pool: Arc, + blocking_pool: blocking::Spawner, around_worker: Callback, mut build_park: F, ) -> ThreadPool @@ -76,65 +71,24 @@ impl ThreadPool { { let (shutdown_tx, shutdown_rx) = shutdown::channel(); - let launch_worker = Arc::new(Box::new(move |worker: Worker>| { - // NOTE: It might seem like the shutdown_tx that's moved into this Arc is never - // dropped, and that shutdown_rx will therefore never see EOF, but that is not actually - // the case. Only `build_with_park` and each worker hold onto a copy of this Arc. - // `build_with_park` drops it immediately, and the workers drop theirs when their `run` - // method returns (and their copy of the Arc are dropped). In fact, we don't actually - // _need_ a copy of `shutdown_tx` for each worker thread; having them all hold onto - // this Arc, which in turn holds the last `shutdown_tx` would have been sufficient. - let shutdown_tx = shutdown_tx.clone(); - let around_worker = around_worker.clone(); - - Box::new(move || { - struct AbortOnPanic; - - impl Drop for AbortOnPanic { - fn drop(&mut self) { - if std::thread::panicking() { - eprintln!("[ERROR] unhandled panic in Tokio scheduler. This is a bug and should be reported."); - std::process::abort(); - } - } - } - - let _abort_on_panic = AbortOnPanic; - - let idx = worker.id(); - let mut f = Some(move || worker.run()); - around_worker(idx, &mut || { - (f.take() - .expect("around_thread callback called closure twice"))( - ) - }); - - // Dropping the handle must happen __after__ the callback - drop(shutdown_tx); - }) as Box - }) - as Box>) -> Box + Send + Sync>); - let (pool, workers) = worker::create_set::<_, BoxedPark

>( pool_size, - |i| Box::new(BoxedPark::new(build_park(i))), - Arc::clone(&launch_worker), + |i| BoxedPark::new(build_park(i)), blocking_pool.clone(), + around_worker, + shutdown_tx, ); // Spawn threads for each worker for worker in workers { - crate::runtime::blocking::Pool::spawn(&blocking_pool, launch_worker(worker)) + blocking_pool.spawn_background(|| worker.run()); } let spawner = Spawner::new(pool); - let blocking = crate::runtime::blocking::PoolWaiter::from(blocking_pool); - // ThreadPool::from_parts(spawner, shutdown_rx, blocking) ThreadPool { spawner, shutdown_rx, - blocking, } } @@ -165,9 +119,7 @@ impl ThreadPool { { crate::runtime::global::with_thread_pool(self.spawner(), || { let mut enter = crate::runtime::enter(); - crate::runtime::blocking::with_pool(self.spawner.blocking_pool(), || { - enter.block_on(future) - }) + enter.block_on(future) }) } @@ -176,7 +128,6 @@ impl ThreadPool { if self.spawner.workers().close() { self.shutdown_rx.wait(); } - self.blocking.shutdown(); } } diff --git a/tokio/src/runtime/thread_pool/owned.rs b/tokio/src/runtime/thread_pool/owned.rs index 04a2f9316..88284d5ef 100644 --- a/tokio/src/runtime/thread_pool/owned.rs +++ b/tokio/src/runtime/thread_pool/owned.rs @@ -1,5 +1,6 @@ -use crate::runtime::task::{self, Task}; +use crate::loom::sync::atomic::AtomicUsize; use crate::runtime::thread_pool::{queue, Shared}; +use crate::task::{self, Task}; use crate::util::FastRand; use std::cell::Cell; @@ -7,6 +8,13 @@ use std::cell::Cell; /// Per-worker data accessible only by the thread driving the worker. #[derive(Debug)] pub(super) struct Owned { + /// Worker generation. This guards concurrent access to the `Owned` struct. + /// When a worker starts running, it checks that the generation it has + /// assigned matches the current generation. When it does, the worker has + /// obtained unique access to the struct. When it fails, another thread has + /// gained unique access. + pub(super) generation: AtomicUsize, + /// Worker tick number. Used to schedule bookkeeping tasks every so often. pub(super) tick: Cell, @@ -40,6 +48,7 @@ where { pub(super) fn new(work_queue: queue::Worker>, rand: FastRand) -> Owned

{ Owned { + generation: AtomicUsize::new(0), tick: Cell::new(1), is_running: Cell::new(true), is_searching: Cell::new(false), diff --git a/tokio/src/runtime/thread_pool/queue/global.rs b/tokio/src/runtime/thread_pool/queue/global.rs index edac6edea..931b76a60 100644 --- a/tokio/src/runtime/thread_pool/queue/global.rs +++ b/tokio/src/runtime/thread_pool/queue/global.rs @@ -1,6 +1,6 @@ use crate::loom::sync::atomic::AtomicUsize; use crate::loom::sync::Mutex; -use crate::runtime::task::{Header, Task}; +use crate::task::{Header, Task}; use std::marker::PhantomData; use std::ptr::{self, NonNull}; diff --git a/tokio/src/runtime/thread_pool/queue/inject.rs b/tokio/src/runtime/thread_pool/queue/inject.rs index f0f92fb2c..1a2d047c9 100644 --- a/tokio/src/runtime/thread_pool/queue/inject.rs +++ b/tokio/src/runtime/thread_pool/queue/inject.rs @@ -1,6 +1,6 @@ use crate::loom::sync::Arc; -use crate::runtime::task::Task; use crate::runtime::thread_pool::queue::Cluster; +use crate::task::Task; pub(crate) struct Inject { cluster: Arc>, diff --git a/tokio/src/runtime/thread_pool/queue/local.rs b/tokio/src/runtime/thread_pool/queue/local.rs index 14f34832f..78b26dac6 100644 --- a/tokio/src/runtime/thread_pool/queue/local.rs +++ b/tokio/src/runtime/thread_pool/queue/local.rs @@ -1,8 +1,8 @@ use crate::loom::cell::{CausalCell, CausalCheck}; use crate::loom::sync::atomic::{self, AtomicU32}; -use crate::runtime::task::Task; use crate::runtime::thread_pool::queue::global; use crate::runtime::thread_pool::LOCAL_QUEUE_CAPACITY; +use crate::task::Task; use std::fmt; use std::mem::MaybeUninit; diff --git a/tokio/src/runtime/thread_pool/queue/worker.rs b/tokio/src/runtime/thread_pool/queue/worker.rs index 67a2a1b85..f9415669c 100644 --- a/tokio/src/runtime/thread_pool/queue/worker.rs +++ b/tokio/src/runtime/thread_pool/queue/worker.rs @@ -1,6 +1,6 @@ use crate::loom::sync::Arc; -use crate::runtime::task::Task; use crate::runtime::thread_pool::queue::{local, Cluster, Inject}; +use crate::task::Task; use std::cell::Cell; use std::fmt; diff --git a/tokio/src/runtime/thread_pool/shared.rs b/tokio/src/runtime/thread_pool/shared.rs index e0a1987e2..99981151d 100644 --- a/tokio/src/runtime/thread_pool/shared.rs +++ b/tokio/src/runtime/thread_pool/shared.rs @@ -1,6 +1,6 @@ use crate::runtime::park::Unpark; -use crate::runtime::task::{self, Schedule, Task}; -use crate::runtime::thread_pool::worker; +use crate::runtime::thread_pool::slice; +use crate::task::{self, Schedule, Task}; use std::ptr; @@ -24,13 +24,9 @@ where /// Untracked pointer to the pool. /// - /// The pool itself is tracked by an `Arc`, but this pointer is not included - /// in the ref count. - /// - /// # Safety - /// - /// `Worker` instances are stored in the `Pool` and are never removed. - set: *const worker::Set

, + /// The slice::Set itself is tracked by an `Arc`, but this pointer is not + /// included in the ref count. + slices: *const slice::Set

, } unsafe impl Send for Shared

{} @@ -44,24 +40,24 @@ where Shared { unpark, pending_drop: task::TransferStack::new(), - set: ptr::null(), + slices: ptr::null(), } } pub(crate) fn schedule(&self, task: Task) { - self.set().schedule(task); + self.slices().schedule(task); } pub(super) fn unpark(&self) { self.unpark.unpark(); } - pub(super) fn set_container_ptr(&mut self, set: *const worker::Set

) { - self.set = set; + fn slices(&self) -> &slice::Set

{ + unsafe { &*self.slices } } - fn set(&self) -> &worker::Set

{ - unsafe { &*self.set } + pub(super) fn set_slices_ptr(&mut self, slices: *const slice::Set

) { + self.slices = slices; } } @@ -73,8 +69,8 @@ where // Get access to the Owned component. This function can only be called // when on the worker. unsafe { - let index = self.set().index_of(self); - let owned = &mut *self.set().owned()[index].get(); + let index = self.slices().index_of(self); + let owned = &mut *self.slices().owned()[index].get(); owned.bind_task(task); } @@ -91,8 +87,8 @@ where // Get access to the Owned component. This function can only be called // when on the worker. unsafe { - let index = self.set().index_of(self); - let owned = &mut *self.set().owned()[index].get(); + let index = self.slices().index_of(self); + let owned = &mut *self.slices().owned()[index].get(); owned.release_task(task); } diff --git a/tokio/src/runtime/thread_pool/set.rs b/tokio/src/runtime/thread_pool/slice.rs similarity index 88% rename from tokio/src/runtime/thread_pool/set.rs rename to tokio/src/runtime/thread_pool/slice.rs index 73555f821..1a0bd3817 100644 --- a/tokio/src/runtime/thread_pool/set.rs +++ b/tokio/src/runtime/thread_pool/slice.rs @@ -1,12 +1,11 @@ -//! Putting a worker to sleep. -//! -//! - Attempt to spin. +//! The scheduler is divided into multiple slices. Each slice is fairly +//! isolated, having its own queue. A worker is dedicated to processing a single +//! slice. use crate::loom::rand::seed; -use crate::loom::sync::Arc; use crate::runtime::park::Unpark; -use crate::runtime::task::{self, JoinHandle, Task}; use crate::runtime::thread_pool::{current, queue, Idle, Owned, Shared}; +use crate::task::{self, JoinHandle, Task}; use crate::util::{CachePadded, FastRand}; use std::cell::UnsafeCell; @@ -27,9 +26,6 @@ where /// Coordinates idle workers idle: Idle, - - /// Pool where blocking tasks should be spawned. - pub(crate) blocking: Arc, } unsafe impl Send for Set

{} @@ -40,11 +36,7 @@ where P: Unpark, { /// Create a new worker set using the provided queues. - pub(crate) fn new( - num_workers: usize, - mut mk_unpark: F, - blocking: Arc, - ) -> Self + pub(crate) fn new(num_workers: usize, mut mk_unpark: F) -> Self where F: FnMut(usize) -> P, { @@ -69,7 +61,7 @@ where owned: owned.into_boxed_slice(), inject, idle: Idle::new(num_workers), - blocking, + // blocking, } } @@ -112,10 +104,6 @@ where self.schedule(task); } - pub(super) fn blocking_pool(&self) -> &Arc { - &self.blocking - } - pub(crate) fn schedule(&self, task: Task>) { current::get(|current_worker| match current_worker.as_member(self) { Some(worker) => { @@ -129,10 +117,10 @@ where }) } - pub(crate) fn set_container_ptr(&mut self) { + pub(crate) fn set_ptr(&mut self) { let ptr = self as *const _; for shared in &mut self.shared[..] { - shared.set_container_ptr(ptr); + shared.set_slices_ptr(ptr); } } diff --git a/tokio/src/runtime/thread_pool/spawner.rs b/tokio/src/runtime/thread_pool/spawner.rs index dd80f4a67..b7031c430 100644 --- a/tokio/src/runtime/thread_pool/spawner.rs +++ b/tokio/src/runtime/thread_pool/spawner.rs @@ -1,7 +1,7 @@ use crate::loom::sync::Arc; use crate::runtime::park::Unpark; -use crate::runtime::task::JoinHandle; -use crate::runtime::thread_pool::worker; +use crate::runtime::thread_pool::slice; +use crate::task::JoinHandle; use std::fmt; use std::future::Future; @@ -20,11 +20,11 @@ use std::future::Future; /// [`ThreadPool::spawner`]: struct.ThreadPool.html#method.spawner #[derive(Clone)] pub(crate) struct Spawner { - workers: Arc>>, + workers: Arc>>, } impl Spawner { - pub(super) fn new(workers: Arc>>) -> Spawner { + pub(super) fn new(workers: Arc>>) -> Spawner { Spawner { workers } } @@ -45,12 +45,8 @@ impl Spawner { self.workers.spawn_background(future); } - pub(super) fn blocking_pool(&self) -> &Arc { - self.workers.blocking_pool() - } - /// Reference to the worker set. Used by `ThreadPool` to initiate shutdown. - pub(super) fn workers(&self) -> &worker::Set> { + pub(super) fn workers(&self) -> &slice::Set> { &*self.workers } } diff --git a/tokio/src/runtime/thread_pool/tests/loom_pool.rs b/tokio/src/runtime/thread_pool/tests/loom_pool.rs index 5eb166ce9..065d515e5 100644 --- a/tokio/src/runtime/thread_pool/tests/loom_pool.rs +++ b/tokio/src/runtime/thread_pool/tests/loom_pool.rs @@ -1,5 +1,5 @@ use crate::runtime::tests::loom_oneshot as oneshot; -use crate::runtime::thread_pool::{self, ThreadPool}; +use crate::runtime::thread_pool::ThreadPool; use crate::runtime::{Park, Unpark}; use crate::spawn; @@ -50,7 +50,7 @@ fn only_blocking() { let (block_tx, block_rx) = oneshot::channel(); pool.spawn(async move { - thread_pool::blocking(move || { + crate::blocking::in_place(move || { block_tx.send(()); }) }); @@ -72,7 +72,7 @@ fn blocking_and_regular() { let done_tx = Arc::new(Mutex::new(Some(done_tx))); pool.spawn(async move { - thread_pool::blocking(move || { + crate::blocking::in_place(move || { block_tx.send(()); }) }); @@ -166,15 +166,21 @@ fn complete_block_on_under_load() { }); } -fn mk_pool(num_threads: usize) -> ThreadPool { - use crate::runtime::blocking; +fn mk_pool(num_threads: usize) -> Runtime { + use crate::blocking::BlockingPool; - ThreadPool::new( + let blocking_pool = BlockingPool::new("test".into(), None); + let executor = ThreadPool::new( num_threads, - blocking::Pool::new("test".into(), None), + blocking_pool.spawner().clone(), Arc::new(Box::new(|_, next| next())), move |_| LoomPark::new(), - ) + ); + + Runtime { + executor, + blocking_pool, + } } use futures::future::poll_fn; @@ -235,6 +241,29 @@ fn gated2(thread: bool) -> impl Future { }) } +/// Fake runtime +struct Runtime { + executor: ThreadPool, + #[allow(dead_code)] + blocking_pool: crate::blocking::BlockingPool, +} + +use std::ops; + +impl ops::Deref for Runtime { + type Target = ThreadPool; + + fn deref(&self) -> &ThreadPool { + &self.executor + } +} + +impl ops::DerefMut for Runtime { + fn deref_mut(&mut self) -> &mut ThreadPool { + &mut self.executor + } +} + struct LoomPark { notify: Arc, } diff --git a/tokio/src/runtime/thread_pool/tests/loom_queue.rs b/tokio/src/runtime/thread_pool/tests/loom_queue.rs index b7c86f6c9..d0598c3eb 100644 --- a/tokio/src/runtime/thread_pool/tests/loom_queue.rs +++ b/tokio/src/runtime/thread_pool/tests/loom_queue.rs @@ -1,6 +1,6 @@ -use crate::runtime::task::{self, Task}; -use crate::runtime::tests::mock_schedule::{Noop, NOOP_SCHEDULE}; use crate::runtime::thread_pool::queue; +use crate::task::{self, Task}; +use crate::tests::mock_schedule::{Noop, NOOP_SCHEDULE}; use loom::thread; diff --git a/tokio/src/runtime/thread_pool/tests/mod.rs b/tokio/src/runtime/thread_pool/tests/mod.rs index 24578e2f2..dc1d31585 100644 --- a/tokio/src/runtime/thread_pool/tests/mod.rs +++ b/tokio/src/runtime/thread_pool/tests/mod.rs @@ -9,6 +9,3 @@ mod pool; #[cfg(not(loom))] mod queue; - -#[cfg(not(loom))] -mod worker; diff --git a/tokio/src/runtime/thread_pool/tests/pool.rs b/tokio/src/runtime/thread_pool/tests/pool.rs index 6e753b35e..c11281f0a 100644 --- a/tokio/src/runtime/thread_pool/tests/pool.rs +++ b/tokio/src/runtime/thread_pool/tests/pool.rs @@ -1,7 +1,8 @@ #![warn(rust_2018_idioms)] +use crate::blocking; use crate::runtime::thread_pool::ThreadPool; -use crate::runtime::{blocking, Park, Unpark}; +use crate::runtime::{Park, Unpark}; use futures_util::future::poll_fn; use std::future::Future; @@ -63,9 +64,11 @@ fn eagerly_drops_futures() { let (park_tx, park_rx) = mpsc::sync_channel(0); let (unpark_tx, unpark_rx) = mpsc::sync_channel(0); + let blocking_pool = blocking::BlockingPool::new("test".into(), None); + let pool = ThreadPool::new( 4, - blocking::Pool::new("test".into(), None), + blocking_pool.spawner().clone(), Arc::new(Box::new(|_, next| next())), move |_| { let (tx, rx) = mpsc::channel(); @@ -166,9 +169,11 @@ fn park_called_at_interval() { let (done_tx, done_rx) = mpsc::channel(); + let blocking_pool = blocking::BlockingPool::new("test".into(), None); + let pool = ThreadPool::new( 1, - blocking::Pool::new("test".into(), None), + blocking_pool.spawner().clone(), Arc::new(Box::new(|_, next| next())), move |idx| { assert_eq!(idx, 0); diff --git a/tokio/src/runtime/thread_pool/tests/queue.rs b/tokio/src/runtime/thread_pool/tests/queue.rs index 86f32ed29..7c0a65d5a 100644 --- a/tokio/src/runtime/thread_pool/tests/queue.rs +++ b/tokio/src/runtime/thread_pool/tests/queue.rs @@ -1,6 +1,6 @@ -use crate::runtime::task::{self, Task}; -use crate::runtime::tests::mock_schedule::{Noop, NOOP_SCHEDULE}; use crate::runtime::thread_pool::{queue, LOCAL_QUEUE_CAPACITY}; +use crate::task::{self, Task}; +use crate::tests::mock_schedule::{Noop, NOOP_SCHEDULE}; macro_rules! assert_pop { ($q:expr, $expect:expr) => { diff --git a/tokio/src/runtime/thread_pool/tests/worker.rs b/tokio/src/runtime/thread_pool/tests/worker.rs deleted file mode 100644 index 91ec5804d..000000000 --- a/tokio/src/runtime/thread_pool/tests/worker.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::runtime::blocking; -use crate::runtime::tests::track_drop::track_drop; -use crate::runtime::thread_pool; - -use tokio_test::assert_ok; - -use std::sync::Arc; - -macro_rules! pool { - (2) => {{ - let (pool, mut w, mock_park) = pool!(!2); - (pool, w.remove(0), w.remove(0), mock_park) - }}; - (! $n:expr) => {{ - let mut mock_park = crate::runtime::tests::mock_park::MockPark::new(); - let blocking = blocking::Pool::new("test".into(), None); - let (pool, workers) = thread_pool::worker::create_set( - $n, - |index| Box::new(mock_park.mk_park(index)), - Arc::new(Box::new(|_| { - unreachable!("attempted to move worker during non-blocking test") - })), - blocking, - ); - (pool, workers, mock_park) - }}; -} - -macro_rules! enter { - ($w:expr, $expr:expr) => {{ - $w.enter(move || $expr); - }}; -} - -#[test] -fn execute_single_task() { - use std::sync::mpsc; - - let (p, mut w0, _w1, ..) = pool!(2); - let (tx, rx) = mpsc::channel(); - - enter!(w0, p.spawn_background(async move { tx.send(1).unwrap() })); - - w0.tick(); - - assert_ok!(rx.try_recv()); -} - -#[test] -fn task_migrates() { - use crate::sync::oneshot; - use std::sync::mpsc; - - let (p, mut w0, mut w1, ..) = pool!(2); - let (tx1, rx1) = oneshot::channel(); - let (tx2, rx2) = mpsc::channel(); - - let (task, did_drop) = track_drop(async move { - let msg = rx1.await.unwrap(); - tx2.send(msg).unwrap(); - }); - - enter!(w0, p.spawn_background(task)); - - w0.tick(); - w1.enter(|| tx1.send("hello").unwrap()); - - w1.tick(); - assert_ok!(rx2.try_recv()); - - // Future drops immediately even though the underlying task is not freed - assert!(did_drop.did_drop_future()); - assert!(did_drop.did_drop_output()); - - // Tick the spawning worker in order to free memory - w0.tick(); -} diff --git a/tokio/src/runtime/thread_pool/worker.rs b/tokio/src/runtime/thread_pool/worker.rs index 5abdba24c..2de2101e2 100644 --- a/tokio/src/runtime/thread_pool/worker.rs +++ b/tokio/src/runtime/thread_pool/worker.rs @@ -1,23 +1,21 @@ +use crate::blocking; +use crate::loom::cell::CausalCell; use crate::loom::sync::Arc; use crate::runtime::park::{Park, Unpark}; -use crate::runtime::task::Task; -use crate::runtime::thread_pool::{current, Owned, Shared, Spawner}; +use crate::runtime::thread_pool::{current, shutdown, slice, Callback, Owned, Shared, Spawner}; +use crate::task::Task; use std::cell::Cell; -use std::ops::{Deref, DerefMut}; +use std::marker::PhantomData; +use std::sync::atomic::Ordering::Relaxed; use std::time::Duration; -// The Arc> is needed because loom doesn't support Arc where T: !Sized -// loom doesn't support that because it requires CoerceUnsized, which is unstable -type LaunchWorker

= Arc) -> Box + Send + Sync>>; - thread_local! { - /// Thread-local tracking the current executor - static ON_BLOCK: Cell> = Cell::new(None) + /// Used to handle block_in_place + static ON_BLOCK: Cell> = Cell::new(None) } -#[cfg(feature = "blocking")] -pub(crate) fn blocking(f: F) -> R +pub(crate) fn block_in_place(f: F) -> R where F: FnOnce() -> R, { @@ -30,60 +28,100 @@ where // This is safe, because ON_BLOCK was set from an &mut dyn FnMut in the worker that wraps // the worker's operation, and is unset just prior to when the FnMut is dropped. - let allow_blocking = unsafe { &mut *allow_blocking }; + let allow_blocking = unsafe { &*allow_blocking }; allow_blocking(); f() }) } -// TODO: remove this re-export -pub(super) use crate::runtime::thread_pool::set::Set; - pub(crate) struct Worker { - /// Entry in the set of workers. - entry: Entry, + /// Parks the thread. Requires the calling worker to have obtained unique + /// access via the generation synchronization action. + inner: Arc>, - /// Park the thread - park: Box

, + /// Scheduler slices + slices: Arc>, - /// Fn for launching another Worker should we need it - launch_worker: LaunchWorker

, + /// Slice assigned to this worker + index: usize, + + /// Handle to the blocking pool + blocking_pool: blocking::Spawner, + + /// Run before calling worker logic + around_worker: Callback, + + /// Worker generation. This is used to synchronize access to the internal + /// data. + generation: usize, /// To indicate that the Worker has been given away and should no longer be used gone: Cell, } +/// Internal worker state. This may be referenced from multiple threads, but the +/// generation guard protects unsafe access +struct Inner { + /// Used to park the thread + park: CausalCell

, + + /// Only held so that the scheduler can be signaled on shutdown. + shutdown_tx: shutdown::Sender, +} + +// TODO: clean up +unsafe impl Send for Worker

{} + +/// Used to ensure the invariants are respected +struct GenerationGuard<'a, P: Park + 'static> { + /// Worker reference + worker: &'a Worker

, + + /// Prevent `Sync` access + _p: PhantomData>, +} + +struct WorkerGone; + +// TODO: Move into slices pub(super) fn create_set( pool_size: usize, mk_park: F, - launch_worker: LaunchWorker

, - blocking: Arc, -) -> (Arc>, Vec>) + blocking_pool: blocking::Spawner, + around_worker: Callback, + shutdown_tx: shutdown::Sender, +) -> (Arc>, Vec>) where P: Send + Park, - F: FnMut(usize) -> Box

, + F: FnMut(usize) -> P, { // Create the parks... let parks: Vec<_> = (0..pool_size).map(mk_park).collect(); - let mut pool = Arc::new(Set::new(pool_size, |i| parks[i].unpark(), blocking)); + let mut slices = Arc::new(slice::Set::new(pool_size, |i| parks[i].unpark())); // Establish the circular link between the individual worker state // structure and the container. - Arc::get_mut(&mut pool).unwrap().set_container_ptr(); + Arc::get_mut(&mut slices).unwrap().set_ptr(); // This will contain each worker. let workers = parks .into_iter() .enumerate() .map(|(index, park)| { - // unsafe is safe because we call Worker::new only once with each index in the pool - unsafe { Worker::new(pool.clone(), index, park, Arc::clone(&launch_worker)) } + Worker::new( + slices.clone(), + index, + park, + blocking_pool.clone(), + around_worker.clone(), + shutdown_tx.clone(), + ) }) .collect(); - (pool, workers) + (slices, workers) } /// After how many ticks is the global queue polled. This helps to ensure @@ -96,298 +134,286 @@ impl

Worker

where P: Send + Park, { - // unsafe because new may only be called once for each index in pool's set - pub(super) unsafe fn new( - pool: Arc>, + // Safe as aquiring a lock is required before doing anything potentially + // dangerous. + pub(super) fn new( + slices: Arc>, index: usize, - park: Box

, - launch_worker: LaunchWorker

, + park: P, + blocking_pool: blocking::Spawner, + around_worker: Callback, + shutdown_tx: shutdown::Sender, ) -> Self { Worker { - entry: Entry::new(pool, index), - park, - launch_worker, + inner: Arc::new(Inner { + park: CausalCell::new(park), + shutdown_tx, + }), + slices, + index, + blocking_pool, + around_worker, + generation: 0, gone: Cell::new(false), } } - pub(super) fn run(mut self) + pub(super) fn run(self) where P: Park>, { - let pool = Arc::clone(&self.entry.pool); - let pool = &pool; - let index = self.entry.index; + (self.around_worker)(self.index, &mut || { + // First, acquire a lock on the worker. + let guard = match self.acquire_lock() { + Some(guard) => guard, + None => return, + }; - let executor = &**pool; - let spawner = Spawner::new(pool.clone()); - let entry = &mut self.entry; - let launch_worker = &self.launch_worker; + let spawner = Spawner::new(self.slices.clone()); - let blocking = &executor.blocking; - let gone = &self.gone; + // Track the current worker + current::set(&self.slices, self.index, || { + // Enter a runtime context + let _enter = crate::runtime::enter(); - let mut park = DropNotGone::new(self.park, gone); + crate::runtime::global::with_thread_pool(&spawner, || { + self.blocking_pool.enter(|| { + ON_BLOCK.with(|ob| { + // Ensure that the ON_BLOCK is removed from the thread-local context + // when leaving the scope. This handles cases that involve panicking. + struct Reset<'a>(&'a Cell>); - // Track the current worker - current::set(&pool, index, || { - let _enter = crate::runtime::enter(); - - crate::runtime::global::with_thread_pool(&spawner, || { - crate::runtime::blocking::with_pool(blocking, || { - ON_BLOCK.with(|ob| { - // Ensure that the ON_BLOCK is removed from the thread-local context - // when leaving the scope. This handles cases that involve panicking. - struct Reset<'a>(&'a Cell>); - - impl<'a> Drop for Reset<'a> { - fn drop(&mut self) { - self.0.set(None); - } - } - - let _reset = Reset(ob); - - let park_ptr = &mut **park as *mut _; - let mut allow_blocking = move || { - // If our Worker has already been given away, then blocking is fine! - if gone.get() { - return; + impl<'a> Drop for Reset<'a> { + fn drop(&mut self) { + self.0.set(None); + } } - // If this method is called, we need to move the entire worker onto a - // separate (blocking) thread before returning. Once we return, the - // caller is going to execute some blocking code which would otherwise - // block our reactor from making progress. Since we are _in the middle_ - // of running a task, this isn't trivial, as the Worker is "active". - // We do have the luxury of knowing that we are on the worker thread, - // so we can assert exclusive access to any Worker-specific state. - // - // More specifically, the caller is _currently_ "stuck" in - // Entry::run_task at: - // - // if let Some(task) = task.run(self.shared().into()) { - // - // And _we_ get to decide when it continues (specifically, by choosing - // when we return from the second callback (i.e., after the FnOnce - // passed to blocking has returned). - // - // Here's what we'll have to do: - // - // - Reconstruct our `Worker` struct - // - Notably, this includes `park`, which we're passing in below. - // - Spawn the reconstructed `Worker` on another blocking thread - // - Clear any state indicating what worker we are on, since at this - // point we are effectively no longer "on" that worker. - // - Allow the caller of `blocking` to continue. - // - // TODO: should we also undo the enter()? - // - // Once the caller completes the blocking operations, we need to ensure - // that async code can continue running in that context. Luckily, since - // `Arc` has a fallback for when current::get() is None, we can - // just let the task run until it yields, and then put it back into the - // pool. + let _reset = Reset(ob); - // We know that the code we're about to execute (inside - // Entry::run_task) has no way to reach the park passed to entry.run. - // therefore, it's fine for us to take ownership of it here _as long as - // we don't drop `park` later_! The DropNotGone wrapper around `park` - // takes care of that. - let park = unsafe { Box::from_raw(park_ptr) }; - let worker = Worker { - entry: unsafe { - // The same argument applies here. Since we unset `current`, - // the task's execution won't assume that it owns a worker any - // more. When the task yields, entry will use its `Arc` - // (which is fine and safe), and then immediately return, - // without calling any code that assumes there is only one - // Entry with the given index (namely it won't call - // Entry::owned). - Entry::new(Arc::clone(&pool), index) - }, - park, - launch_worker: Arc::clone(launch_worker), - gone: Cell::new(false), - }; + let allow_blocking: &dyn Fn() = &|| self.block_in_place(); - // Give away the worker - // - // TODO: it would be _really_ nice if we had a way to _not_ spawn a - // thread and hand off the worker if the blocking routine ran only for - // a short amount of time. maybe push the Worker onto a "stealing - // queue" somehow? or maybe keep a shared "active" AtomicBool in both - // instances of the Worker, and compare_exchange it to true afterwards - // in an attempt to take it back. if it succeeds, we just resume where - // we were. if it fails, another thread has already stolen the Worker. - crate::runtime::blocking::Pool::spawn( - &pool.blocking, - launch_worker(worker), - ); + ob.set(Some(unsafe { + // NOTE: We cannot use a safe cast to raw pointer here, since we are + // _also_ erasing the lifetime of these pointers. That is safe here, + // because we know that ob will set back to None before allow_blocking + // is dropped. + #[allow(clippy::useless_transmute)] + std::mem::transmute::<_, *const dyn Fn()>(allow_blocking) + })); - // make sure no subsequent code thinks that it is on a worker - current::clear(); + let _ = guard.run(); - // and make sure that when Entry finishes running the current task, - // it immediately returns all the way up to the worker. - gone.set(true); - }; - let allow_blocking: &mut dyn FnMut() = &mut allow_blocking; - - ob.set(Some(unsafe { - // NOTE: We cannot use a safe cast to raw pointer here, since we are - // _also_ erasing the lifetime of these pointers. That is safe here, - // because we know that ob will set back to None before allow_blocking - // is dropped. - #[allow(clippy::useless_transmute)] - std::mem::transmute::<_, *mut dyn FnMut()>(allow_blocking) - })); - - let _ = entry.run(&mut **park, gone); - - // Ensure that we reset ob before allow_blocking is dropped. - drop(_reset); - }); + // Ensure that we reset ob before allow_blocking is dropped. + drop(_reset); + }); + }) }) - }) + }); + + if self.gone.get() { + // Synchronize with the pool for load(Acquire) in is_closed to get + // up-to-date value. + self.slices.wait_for_unlocked(); + + if self.slices.is_closed() { + // If the pool is shutting down, some other thread may be + // waiting to clean up after the task that we were holding on + // to. If we completed that task, we did nothing (because + // task.run() returned None), and so crucially we did not wait + // up any such thread. + // + // So, we have to do that here. + self.slices.notify_all(); + } + } }); - if gone.get() { - // Synchronize with the pool for load(Acquire) in is_closed to get up-to-date value. - pool.wait_for_unlocked(); - if pool.is_closed() { - // If the pool is shutting down, some other thread may be waiting to clean up after - // the task that we were holding on to. If we completed that task, we did nothing - // (because task.run() returned None), and so crucially we did not wait up any such - // thread. - // - // So, we have to do that here. - pool.notify_all(); - } + // We have to drop the `shutdown_tx` handle last to ensure expected + // ordering. + let shutdown_tx = self.inner.shutdown_tx.clone(); + drop(self); + drop(shutdown_tx); + } + + /// Acquire the lock + fn acquire_lock(&self) -> Option> { + // Safety: Only getting `&self` access to access atomic field + let owned = unsafe { &*self.slices.owned()[self.index].get() }; + + // The lock is only to establish mutual exclusion. Other synchronization + // handles memory orderings + let prev = owned.generation.compare_and_swap( + self.generation, + self.generation.wrapping_add(1), + Relaxed, + ); + + if prev == self.generation { + Some(GenerationGuard { + worker: self, + _p: PhantomData, + }) + } else { + None } } - pub(super) fn id(&self) -> usize { - self.entry.index - } - - #[cfg(test)] - #[allow(warnings)] - pub(crate) fn enter(&self, f: F) -> R + /// Enter an in-place blocking section + fn block_in_place(&self) where - F: FnOnce() -> R, + P: Park>, { - current::set(&self.entry.pool, self.entry.index, f) - } + // If our Worker has already been given away, then blocking is fine! + if self.gone.get() { + return; + } - #[cfg(test)] - #[allow(warnings)] - pub(crate) fn tick(&mut self) { - self.entry.tick(&mut *self.park, &self.gone); + // make sure no subsequent code thinks that it is on a worker + current::clear(); + + // Track that the worker is gone + self.gone.set(true); + + // If this method is called, we need to move the entire worker onto a + // separate (blocking) thread before returning. Once we return, the + // caller is going to execute some blocking code which would otherwise + // block our reactor from making progress. Since we are _in the middle_ + // of running a task, this isn't trivial, as the Worker is "active". + // We do have the luxury of knowing that we are on the worker thread, + // so we can assert exclusive access to any Worker-specific state. + // + // More specifically, the caller is _currently_ "stuck" in + // Entry::run_task at: + // + // if let Some(task) = task.run(self.shared().into()) { + // + // And _we_ get to decide when it continues (specifically, by choosing + // when we return from the second callback (i.e., after the FnOnce + // passed to blocking has returned). + // + // Here's what we'll have to do: + // + // - Reconstruct our `Worker` struct + // - Spawn the reconstructed `Worker` on another blocking thread + // - Clear any state indicating what worker we are on, since at this + // point we are effectively no longer "on" that worker. + // - Allow the caller of `blocking` to continue. + // + // Once the caller completes the blocking operations, we need to ensure + // that async code can continue running in that context. Luckily, since + // `Arc` has a fallback for when + // current::get() is None, we can just let the task + // run until it yields, and then put it back into + // the pool. + + let worker = Worker { + inner: self.inner.clone(), + slices: self.slices.clone(), + index: self.index, + blocking_pool: self.blocking_pool.clone(), + around_worker: self.around_worker.clone(), + generation: self.generation + 1, + gone: Cell::new(false), + }; + + // Give away the worker + self.blocking_pool.spawn_background(move || worker.run()); } } -struct WorkerGone; - -struct Entry { - pool: Arc>, - index: usize, -} - -impl

Entry

+impl

GenerationGuard<'_, P> where - P: Unpark, + P: Park + 'static, { - // unsafe because Entry::owned assumes there is only one instance of the Entry - unsafe fn new(pool: Arc>, index: usize) -> Self { - Entry { pool, index } - } + fn run(self) -> Result<(), WorkerGone> { + let mut me = self; - fn run( - &mut self, - park: &mut impl Park, - gone: &Cell, - ) -> Result<(), WorkerGone> { - while self.is_running() { - if self.tick(park, gone)? { - self.park(park); + while me.is_running() { + me = me.process_available_work()?; + + if me.is_running() { + me.park(); } } - self.shutdown(park); + me.shutdown(); Ok(()) } - fn is_running(&mut self) -> bool { + fn is_running(&self) -> bool { self.owned().is_running.get() } /// Returns `true` if the worker needs to park - fn tick( - &mut self, - park: &mut impl Park, - gone: &Cell, - ) -> Result { - // Process all pending tasks in the local queue. - if !self.process_local_queue(park, gone)? { - return Ok(false); - } + fn process_available_work(self) -> Result { + let mut me = self; - // No more **local** work to process, try transitioning to searching - // in order to attempt to steal work from other workers. - // - // On `false`, the worker has entered the parked state - if self.transition_to_searching() { - // If `true` then work was found - if self.search_for_work(gone)? { - return Ok(false); - } - } - - Ok(true) - } - - /// Process all pending tasks in the local queue, occasionally checking the - /// global queue, but never other worker local queues. - /// - /// Returns `false` if processing was interrupted due to the pool shutting - /// down. - fn process_local_queue( - &mut self, - park: &mut impl Park, - gone: &Cell, - ) -> Result { loop { - let tick = self.tick_fetch_inc(); + // Local queue loop + loop { + let task = match me.find_local_work() { + Some(task) => task, + None => { + if !me.is_running() { + // The scheduler is in the process of shutting down. + return Ok(me); + } - let task = if tick % GLOBAL_POLL_INTERVAL == 0 { - // Sleep light... - self.park_light(park); + // Break out of the local task loop and try to steal + break; + } + }; - // Perform regularly scheduled maintenance work. - self.maintenance(); - - if !self.is_running() { - return Ok(false); - } - - // Check the global queue - self.owned().work_queue.pop_global_first() - } else { - self.owned().work_queue.pop_local_first() - }; - - if let Some(task) = task { - self.run_task(task, gone)?; - } else { - return Ok(true); + me = me.run_task(task)?; } + + // No more **local** work to process, try transitioning to searching + // in order to attempt to steal work from other workers. + // + // On `false`, the worker has entered the parked state + if me.transition_to_searching() { + // Try to steal tasks from other workers + if let Some(task) = me.steal_work() { + me = me.run_task(task)?; + } else { + // No work to steal, perform some routine work + me.drain_tasks_pending_drop(); + return Ok(me); + } + } else { + return Ok(me); + } + + // Start checking the local queue again } } - fn steal_work(&mut self) -> Option>> { - let num_workers = self.pool.len(); - let start = self.owned().rand.fastrand_n(num_workers as u32); + /// Find local work + fn find_local_work(&mut self) -> Option>> { + let tick = self.tick_fetch_inc(); + + if tick % GLOBAL_POLL_INTERVAL == 0 { + // Sleep light... + self.park_light(); + + // Perform regularly scheduled maintenance work. + self.maintenance(); + + if !self.is_running() { + return None; + } + + // Check the global queue + self.owned().work_queue.pop_global_first() + } else { + self.owned().work_queue.pop_local_first() + } + } + + fn steal_work(&mut self) -> Option>> { + let num_slices = self.worker.slices.len(); + let start = self.owned().rand.fastrand_n(num_slices as u32); self.owned() .work_queue @@ -408,23 +434,12 @@ where self.owned().is_running.set(!closed) } - fn search_for_work(&mut self, gone: &Cell) -> Result { - if let Some(task) = self.steal_work() { - self.run_task(task, gone)?; - Ok(true) - } else { - // Perform some routine work - self.drain_tasks_pending_drop(); - Ok(false) - } - } - fn transition_to_searching(&mut self) -> bool { if self.is_searching() { return true; } - let ret = self.set().idle().transition_worker_to_searching(); + let ret = self.slices().idle().transition_worker_to_searching(); self.owned().is_searching.set(ret); ret } @@ -432,19 +447,19 @@ where fn transition_from_searching(&mut self) { self.owned().is_searching.set(false); - if self.set().idle().transition_worker_from_searching() { + if self.slices().idle().transition_worker_from_searching() { // We are the final searching worker. Because work was found, we // need to notify another worker. - self.set().notify_work(); + self.slices().notify_work(); } } /// Returns `true` if the worker must check for any work. fn transition_to_parked(&mut self) -> bool { - let idx = self.index; + let idx = self.index(); let is_searching = self.is_searching(); let ret = self - .set() + .slices() .idle() .transition_worker_to_parked(idx, is_searching); @@ -463,14 +478,14 @@ where fn transition_from_parked(&mut self) -> bool { if self.owned().did_submit_task.get() || !self.is_running() { // Remove the worker from the sleep set. - self.set().idle().unpark_worker_by_id(self.index); + self.slices().idle().unpark_worker_by_id(self.index()); self.owned().is_searching.set(true); self.owned().defer_notification.set(false); true } else { - let ret = !self.set().idle().is_parked(self.index); + let ret = !self.slices().idle().is_parked(self.index()); if ret { self.owned().is_searching.set(true); @@ -481,12 +496,17 @@ where } } - fn run_task(&mut self, task: Task>, gone: &Cell) -> Result<(), WorkerGone> { + /// Runs the task. During the task execution, it is possible for worker to + /// transition to a new thread. In this case, the caller loses the guard to + /// access the generation and must stop processing. + fn run_task(mut self, task: Task>) -> Result { if self.is_searching() { self.transition_from_searching(); } + let gone = &self.worker.gone; let executor = self.shared(); + let task = task.run(&mut || { if gone.get() { None @@ -494,30 +514,33 @@ where Some(executor.into()) } }); + if gone.get() { // The Worker disappeared from under us. // We need to return, because we no longer own all of our state! // Make sure the task gets picked up again eventually. if let Some(task) = task { - self.pool.schedule(task); + self.worker.slices.schedule(task); } - return Err(WorkerGone); - } - if let Some(task) = task { - self.owned().submit_local_yield(task); - self.set().notify_work(); + Err(WorkerGone) + } else { + if let Some(task) = task { + self.owned().submit_local_yield(task); + self.slices().notify_work(); + } + + Ok(self) } - Ok(()) } fn final_work_sweep(&mut self) { if !self.owned().work_queue.is_empty() { - self.set().notify_work(); + self.slices().notify_work(); } } - fn park(&mut self, park: &mut impl Park) { + fn park(&mut self) { if self.transition_to_parked() { // We are the final searching worker, check if any work arrived // before parking @@ -528,7 +551,7 @@ where // calling the parker. This is done in a loop as spurious wakeups are // permitted. loop { - park.park().ok().expect("park failed"); + self.park_mut().park().ok().expect("park failed"); // We might have been woken to clean up a dropped task self.maintenance(); @@ -539,19 +562,20 @@ where } } - fn park_light(&mut self, park: &mut impl Park) { + fn park_light(&mut self) { // When tasks are submitted locally (from the parker), defer any // notifications in hopes that the curent worker will grab those tasks. self.owned().defer_notification.set(true); - park.park_timeout(Duration::from_millis(0)) + self.park_mut() + .park_timeout(Duration::from_millis(0)) .ok() .expect("park failed"); self.owned().defer_notification.set(false); if self.owned().did_submit_task.get() { - self.set().notify_work(); + self.slices().notify_work(); self.owned().did_submit_task.set(false) } } @@ -559,7 +583,7 @@ where fn drain_tasks_pending_drop(&mut self) { for task in self.shared().pending_drop.drain() { unsafe { - let owned = &mut *self.set().owned()[self.index].get(); + let owned = &mut *self.slices().owned()[self.index()].get(); owned.release_task(&task); } drop(task); @@ -570,7 +594,7 @@ where /// /// Once the shutdown flag has been observed, it is guaranteed that no /// further tasks may be pushed into the global queue. - fn shutdown(&mut self, park: &mut impl Park) { + fn shutdown(&mut self) { // Transition all tasks owned by the worker to canceled. self.owned().owned_tasks.shutdown(); @@ -582,7 +606,7 @@ where // Notify all workers in case they have pending tasks to drop // // Not super efficient, but we are also shutting down. - self.pool.notify_all(); + self.worker.slices.notify_all(); // The worker can only shutdown once there are no further owned tasks. while !self.owned().owned_tasks.is_empty() { @@ -591,7 +615,7 @@ where // `transition_to_parked` is not called as we are not working // anymore. When a task is released, the owning worker is unparked // directly. - park.park().ok().expect("park failed"); + self.park_mut().park().ok().expect("park failed"); // Try draining more tasks self.drain_tasks_pending_drop(); @@ -605,56 +629,31 @@ where tick } - fn is_searching(&mut self) -> bool { + fn is_searching(&self) -> bool { self.owned().is_searching.get() } - fn set(&self) -> &Set

{ - &self.pool + fn index(&self) -> usize { + self.worker.index } - fn shared(&self) -> &Shared

{ - &self.set().shared()[self.index] + fn slices(&self) -> &slice::Set { + &self.worker.slices } - fn owned(&mut self) -> &Owned

{ + fn shared(&self) -> &Shared { + &self.slices().shared()[self.index()] + } + + fn owned(&self) -> &Owned { + let index = self.index(); // safety: we own the slot - unsafe { &*self.set().owned()[self.index].get() } - } -} - -struct DropNotGone<'a, T> { - gone: &'a Cell, - inner: Option, -} - -impl<'a, T> DropNotGone<'a, T> { - fn new(inner: T, gone: &'a Cell) -> Self { - DropNotGone { - gone, - inner: Some(inner), - } - } -} - -impl<'a, T> Drop for DropNotGone<'a, T> { - fn drop(&mut self) { - if self.gone.get() { - let inner = self.inner.take().unwrap(); - std::mem::forget(inner); - } - } -} - -impl<'a, T> Deref for DropNotGone<'a, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - self.inner.as_ref().unwrap() - } -} - -impl<'a, T> DerefMut for DropNotGone<'a, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.as_mut().unwrap() + unsafe { &*self.slices().owned()[index].get() } + } + + fn park_mut(&mut self) -> &mut P { + // Safety: `&mut self` on `GenerationGuard` implies it is safe to + // perform the action. + unsafe { self.worker.inner.park.with_mut(|ptr| &mut *ptr) } } } diff --git a/tokio/src/runtime/time.rs b/tokio/src/runtime/time.rs new file mode 100644 index 000000000..c04c2c966 --- /dev/null +++ b/tokio/src/runtime/time.rs @@ -0,0 +1,61 @@ +//! Abstracts out the APIs necessary to `Runtime` for integrating the time +//! driver. When the `time` feature flag is **not** enabled. These APIs are +//! shells. This isolates the complexity of dealing with conditional +//! compilation. + +pub(crate) use self::variant::*; + +#[cfg(feature = "time")] +mod variant { + use crate::runtime::io; + use crate::time::{self, driver}; + + pub(crate) type Clock = time::Clock; + pub(crate) type Driver = driver::Driver; + pub(crate) type Handle = driver::Handle; + + pub(crate) fn create_clock() -> Clock { + Clock::new() + } + + /// Create a new timer driver / handle pair + pub(crate) fn create_driver(io_driver: io::Driver, clock: Clock) -> (Driver, Handle) { + let driver = driver::Driver::new(io_driver, clock); + let handle = driver.handle(); + + (driver, handle) + } + + pub(crate) fn with_default(handle: &Handle, clock: &Clock, f: F) -> R + where + F: FnOnce() -> R, + { + let _time = driver::set_default(handle); + clock.enter(f) + } +} + +#[cfg(not(feature = "time"))] +mod variant { + use crate::runtime::io; + + pub(crate) type Clock = (); + pub(crate) type Driver = io::Driver; + pub(crate) type Handle = (); + + pub(crate) fn create_clock() -> Clock { + () + } + + /// Create a new timer driver / handle pair + pub(crate) fn create_driver(io_driver: io::Driver, _clock: Clock) -> (Driver, Handle) { + (io_driver, ()) + } + + pub(crate) fn with_default(_handler: &Handle, _clock: &Clock, f: F) -> R + where + F: FnOnce() -> R, + { + f() + } +} diff --git a/tokio/src/runtime/timer.rs b/tokio/src/runtime/timer.rs deleted file mode 100644 index 03e501ce5..000000000 --- a/tokio/src/runtime/timer.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub(crate) use self::variant::*; - -#[cfg(feature = "time")] -mod variant { - use crate::runtime::io; - use crate::time::{clock, timer}; - - pub(crate) type Clock = clock::Clock; - pub(crate) type Driver = timer::Timer; - pub(crate) type Handle = timer::Handle; - - /// Create a new timer driver / handle pair - pub(crate) fn create(io_driver: io::Driver, clock: Clock) -> (Driver, Handle) { - let driver = timer::Timer::new_with_clock(io_driver, clock); - let handle = driver.handle(); - - (driver, handle) - } - - #[cfg(feature = "blocking")] - pub(crate) fn set_default(handle: &Handle) -> timer::DefaultGuard<'_> { - timer::set_default(handle) - } -} - -#[cfg(not(feature = "time"))] -mod variant { - use crate::runtime::io; - - pub(crate) type Clock = (); - pub(crate) type Driver = io::Driver; - pub(crate) type Handle = (); - - /// Create a new timer driver / handle pair - pub(crate) fn create(io_driver: io::Driver, _clock: Clock) -> (Driver, Handle) { - (io_driver, ()) - } - - #[cfg(feature = "blocking")] - pub(crate) fn set_default(_handle: &Handle) {} -} diff --git a/tokio/src/runtime/task/core.rs b/tokio/src/task/core.rs similarity index 96% rename from tokio/src/runtime/task/core.rs rename to tokio/src/task/core.rs index 957a4a836..67b9bed6e 100644 --- a/tokio/src/runtime/task/core.rs +++ b/tokio/src/task/core.rs @@ -1,9 +1,9 @@ use crate::loom::alloc::Track; use crate::loom::cell::CausalCell; -use crate::runtime::task::raw::{self, Vtable}; -use crate::runtime::task::state::State; -use crate::runtime::task::waker::waker_ref; -use crate::runtime::task::Schedule; +use crate::task::raw::{self, Vtable}; +use crate::task::state::State; +use crate::task::waker::waker_ref; +use crate::task::Schedule; use std::cell::UnsafeCell; use std::future::Future; diff --git a/tokio/src/runtime/task/error.rs b/tokio/src/task/error.rs similarity index 77% rename from tokio/src/runtime/task/error.rs rename to tokio/src/task/error.rs index c87b98bb6..e5eea4654 100644 --- a/tokio/src/runtime/task/error.rs +++ b/tokio/src/task/error.rs @@ -1,5 +1,6 @@ use std::any::Any; use std::fmt; +use std::io; /// Task failed to execute to completion. pub struct JoinError { @@ -46,3 +47,15 @@ impl fmt::Debug for JoinError { } impl std::error::Error for JoinError {} + +impl From for io::Error { + fn from(src: JoinError) -> io::Error { + io::Error::new( + io::ErrorKind::Other, + match src.repr { + Repr::Cancelled => "task was cancelled", + Repr::Panic(_) => "task panicked", + }, + ) + } +} diff --git a/tokio/src/runtime/task/harness.rs b/tokio/src/task/harness.rs similarity index 99% rename from tokio/src/runtime/task/harness.rs rename to tokio/src/task/harness.rs index 39cda5dd0..39ea2b5da 100644 --- a/tokio/src/runtime/task/harness.rs +++ b/tokio/src/task/harness.rs @@ -1,8 +1,8 @@ use crate::loom::alloc::Track; use crate::loom::cell::CausalCheck; -use crate::runtime::task::core::{Cell, Core, Header, Trailer}; -use crate::runtime::task::state::Snapshot; -use crate::runtime::task::{JoinError, Schedule, Task}; +use crate::task::core::{Cell, Core, Header, Trailer}; +use crate::task::state::Snapshot; +use crate::task::{JoinError, Schedule, Task}; use std::future::Future; use std::marker::PhantomData; diff --git a/tokio/src/runtime/task/join.rs b/tokio/src/task/join.rs similarity index 94% rename from tokio/src/runtime/task/join.rs rename to tokio/src/task/join.rs index a59cfde84..c2c59a2c1 100644 --- a/tokio/src/runtime/task/join.rs +++ b/tokio/src/task/join.rs @@ -1,5 +1,5 @@ use crate::loom::alloc::Track; -use crate::runtime::task::RawTask; +use crate::task::RawTask; use std::fmt; use std::future::Future; @@ -13,6 +13,9 @@ pub struct JoinHandle { _p: PhantomData, } +unsafe impl Send for JoinHandle {} +unsafe impl Sync for JoinHandle {} + impl JoinHandle { pub(super) fn new(raw: RawTask) -> JoinHandle { JoinHandle { diff --git a/tokio/src/runtime/task/list.rs b/tokio/src/task/list.rs similarity index 98% rename from tokio/src/runtime/task/list.rs rename to tokio/src/task/list.rs index 7ccc57950..85ff3dc22 100644 --- a/tokio/src/runtime/task/list.rs +++ b/tokio/src/task/list.rs @@ -1,4 +1,4 @@ -use crate::runtime::task::{Header, Task}; +use crate::task::{Header, Task}; use std::fmt; use std::marker::PhantomData; diff --git a/tokio/src/runtime/task/mod.rs b/tokio/src/task/mod.rs similarity index 96% rename from tokio/src/runtime/task/mod.rs rename to tokio/src/task/mod.rs index 7e361e4fb..a0175456c 100644 --- a/tokio/src/runtime/task/mod.rs +++ b/tokio/src/task/mod.rs @@ -1,16 +1,17 @@ +//! Asynchronous green-threads. + mod core; use self::core::Cell; pub(crate) use self::core::Header; mod error; -#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::error::JoinError; mod harness; use self::harness::Harness; mod join; -#[cfg(any(feature = "rt-current-thread", feature = "rt-full"))] +#[cfg(feature = "rt-core")] #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::join::JoinHandle; @@ -28,6 +29,9 @@ use self::state::{Snapshot, State}; mod waker; +mod yield_now; +pub use yield_now::yield_now; + /// Unit tests #[cfg(test)] mod tests; diff --git a/tokio/src/runtime/task/raw.rs b/tokio/src/task/raw.rs similarity index 97% rename from tokio/src/runtime/task/raw.rs rename to tokio/src/task/raw.rs index 51d9d41c2..d6542a162 100644 --- a/tokio/src/runtime/task/raw.rs +++ b/tokio/src/task/raw.rs @@ -1,8 +1,8 @@ use crate::loom::alloc::Track; -use crate::runtime::task::Cell; -use crate::runtime::task::Harness; -use crate::runtime::task::{Header, Schedule}; -use crate::runtime::task::{Snapshot, State}; +use crate::task::Cell; +use crate::task::Harness; +use crate::task::{Header, Schedule}; +use crate::task::{Snapshot, State}; use std::future::Future; use std::ptr::NonNull; diff --git a/tokio/src/runtime/task/stack.rs b/tokio/src/task/stack.rs similarity index 98% rename from tokio/src/runtime/task/stack.rs rename to tokio/src/task/stack.rs index 7e13d7d57..e2dd3838c 100644 --- a/tokio/src/runtime/task/stack.rs +++ b/tokio/src/task/stack.rs @@ -1,5 +1,5 @@ use crate::loom::sync::atomic::AtomicPtr; -use crate::runtime::task::{Header, Task}; +use crate::task::{Header, Task}; use std::marker::PhantomData; use std::ptr::{self, NonNull}; diff --git a/tokio/src/runtime/task/state.rs b/tokio/src/task/state.rs similarity index 100% rename from tokio/src/runtime/task/state.rs rename to tokio/src/task/state.rs diff --git a/tokio/src/runtime/task/tests/loom.rs b/tokio/src/task/tests/loom.rs similarity index 98% rename from tokio/src/runtime/task/tests/loom.rs rename to tokio/src/task/tests/loom.rs index b62e0f36e..dd6fbbefc 100644 --- a/tokio/src/runtime/task/tests/loom.rs +++ b/tokio/src/task/tests/loom.rs @@ -1,5 +1,5 @@ -use crate::runtime::task; -use crate::runtime::tests::loom_schedule::LoomSchedule; +use crate::task; +use crate::tests::loom_schedule::LoomSchedule; use tokio_test::{assert_err, assert_ok}; diff --git a/tokio/src/runtime/task/tests/mod.rs b/tokio/src/task/tests/mod.rs similarity index 100% rename from tokio/src/runtime/task/tests/mod.rs rename to tokio/src/task/tests/mod.rs diff --git a/tokio/src/runtime/task/tests/task.rs b/tokio/src/task/tests/task.rs similarity index 98% rename from tokio/src/runtime/task/tests/task.rs rename to tokio/src/task/tests/task.rs index 4b2dd2d89..95b1451cc 100644 --- a/tokio/src/runtime/task/tests/task.rs +++ b/tokio/src/task/tests/task.rs @@ -1,8 +1,8 @@ -use crate::runtime::task::{self, Header}; -use crate::runtime::tests::backoff::*; -use crate::runtime::tests::mock_schedule::{mock, Mock}; -use crate::runtime::tests::track_drop::track_drop; use crate::sync::oneshot; +use crate::task::{self, Header}; +use crate::tests::backoff::*; +use crate::tests::mock_schedule::{mock, Mock}; +use crate::tests::track_drop::track_drop; use tokio_test::task::spawn; use tokio_test::{assert_pending, assert_ready_err, assert_ready_ok}; diff --git a/tokio/src/runtime/task/waker.rs b/tokio/src/task/waker.rs similarity index 96% rename from tokio/src/runtime/task/waker.rs rename to tokio/src/task/waker.rs index 00c1e6d46..e0e1f36ce 100644 --- a/tokio/src/runtime/task/waker.rs +++ b/tokio/src/task/waker.rs @@ -1,5 +1,5 @@ -use crate::runtime::task::harness::Harness; -use crate::runtime::task::{Header, Schedule}; +use crate::task::harness::Harness; +use crate::task::{Header, Schedule}; use std::future::Future; use std::marker::PhantomData; diff --git a/tokio/src/task/yield_now.rs b/tokio/src/task/yield_now.rs new file mode 100644 index 000000000..aabd6b392 --- /dev/null +++ b/tokio/src/task/yield_now.rs @@ -0,0 +1,27 @@ +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +/// Yield execution back to the Tokio runtime. +pub async fn yield_now() { + /// Yield implementation + struct YieldNow { + yielded: bool, + } + + impl Future for YieldNow { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + if self.yielded { + return Poll::Ready(()); + } + + self.yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + } + + YieldNow { yielded: false }.await +} diff --git a/tokio/src/runtime/tests/backoff.rs b/tokio/src/tests/backoff.rs similarity index 100% rename from tokio/src/runtime/tests/backoff.rs rename to tokio/src/tests/backoff.rs diff --git a/tokio/src/runtime/tests/loom_schedule.rs b/tokio/src/tests/loom_schedule.rs similarity index 96% rename from tokio/src/runtime/tests/loom_schedule.rs rename to tokio/src/tests/loom_schedule.rs index 1dad60621..d3f932284 100644 --- a/tokio/src/runtime/tests/loom_schedule.rs +++ b/tokio/src/tests/loom_schedule.rs @@ -1,4 +1,4 @@ -use crate::runtime::task::{Schedule, Task}; +use crate::task::{Schedule, Task}; use loom::sync::Notify; use std::collections::VecDeque; diff --git a/tokio/src/runtime/tests/mock_schedule.rs b/tokio/src/tests/mock_schedule.rs similarity index 98% rename from tokio/src/runtime/tests/mock_schedule.rs rename to tokio/src/tests/mock_schedule.rs index ab15c54e2..8c20d361b 100644 --- a/tokio/src/runtime/tests/mock_schedule.rs +++ b/tokio/src/tests/mock_schedule.rs @@ -1,6 +1,6 @@ #![allow(warnings)] -use crate::runtime::task::{Header, Schedule, Task}; +use crate::task::{Header, Schedule, Task}; use std::collections::VecDeque; use std::sync::Mutex; diff --git a/tokio/src/tests/mod.rs b/tokio/src/tests/mod.rs new file mode 100644 index 000000000..0f934f57d --- /dev/null +++ b/tokio/src/tests/mod.rs @@ -0,0 +1,32 @@ +#[macro_export] +/// Assert option is some +macro_rules! assert_some { + ($e:expr) => {{ + match $e { + Some(v) => v, + _ => panic!("expected some, was none"), + } + }}; +} + +#[macro_export] +/// Assert option is none +macro_rules! assert_none { + ($e:expr) => {{ + match $e { + Some(v) => panic!("expected none, was {:?}", v), + _ => {} + } + }}; +} + +#[cfg(not(loom))] +pub(crate) mod backoff; + +#[cfg(loom)] +pub(crate) mod loom_schedule; + +pub(crate) mod mock_schedule; + +#[cfg(not(loom))] +pub(crate) mod track_drop; diff --git a/tokio/src/runtime/tests/track_drop.rs b/tokio/src/tests/track_drop.rs similarity index 100% rename from tokio/src/runtime/tests/track_drop.rs rename to tokio/src/tests/track_drop.rs diff --git a/tokio/src/time/clock.rs b/tokio/src/time/clock.rs new file mode 100644 index 000000000..982c79aae --- /dev/null +++ b/tokio/src/time/clock.rs @@ -0,0 +1,243 @@ +//! Source of time abstraction. +//! +//! By default, `std::time::Instant::now()` is used. However, when the +//! `test-util` feature flag is enabled, the values returned for `now()` are +//! configurable. + +#[cfg(feature = "test-util")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::variant::{advance, pause, resume}; +pub(crate) use self::variant::{now, Clock}; + +#[cfg(not(feature = "test-util"))] +mod variant { + use crate::time::Instant; + + #[derive(Debug, Clone)] + pub(crate) struct Clock {} + + pub(crate) fn now() -> Instant { + Instant::from_std(std::time::Instant::now()) + } + + impl Clock { + pub(crate) fn new() -> Clock { + Clock {} + } + + pub(crate) fn now(&self) -> Instant { + now() + } + + pub(crate) fn enter(&self, f: F) -> R + where + F: FnOnce() -> R, + { + f() + } + } +} + +#[cfg(feature = "test-util")] +mod variant { + use crate::time::{Duration, Instant}; + + use std::cell::Cell; + use std::sync::{Arc, Mutex}; + + /// A handle to a source of time. + #[derive(Debug, Clone)] + pub(crate) struct Clock { + inner: Arc, + } + + #[derive(Debug)] + struct Inner { + /// Instant at which the clock was created + start: std::time::Instant, + + /// Current, "frozen" time as an offset from `start`. + frozen: Mutex>, + } + + thread_local! { + /// Thread-local tracking the current clock + static CLOCK: Cell> = Cell::new(None) + } + + /// Pause time + /// + /// The current value of `Instant::now()` is saved and all subsequent calls + /// to `Instant::now()` will return the saved value. This is useful for + /// running tests that are dependent on time. + /// + /// # Panics + /// + /// Panics if time is already frozen or if called from outside of the Tokio + /// runtime. + pub fn pause() { + CLOCK.with(|cell| { + let ptr = match cell.get() { + Some(ptr) => ptr, + None => panic!("time cannot be frozen from outside the Tokio runtime"), + }; + + let clock = unsafe { &*ptr }; + let mut frozen = clock.inner.frozen.lock().unwrap(); + + if frozen.is_some() { + panic!("time is already frozen"); + } + + *frozen = Some(clock.inner.start.elapsed()); + }) + } + + /// Resume time + /// + /// Clears the saved `Instant::now()` value. Subsequent calls to + /// `Instant::now()` will return the value returned by the system call. + /// + /// # Panics + /// + /// Panics if time is not frozen or if called from outside of the Tokio + /// runtime. + pub fn resume() { + CLOCK.with(|cell| { + let ptr = match cell.get() { + Some(ptr) => ptr, + None => panic!("time cannot be frozen from outside the Tokio runtime"), + }; + + let clock = unsafe { &*ptr }; + let mut frozen = clock.inner.frozen.lock().unwrap(); + + if frozen.is_none() { + panic!("time is not frozen"); + } + + *frozen = None; + }) + } + + /// Advance time + /// + /// Increments the saved `Instant::now()` value by `duration`. Subsequent + /// calls to `Instant::now()` will return the result of the increment. + /// + /// # Panics + /// + /// Panics if time is not frozen or if called from outside of the Tokio + /// runtime. + pub async fn advance(duration: Duration) { + CLOCK.with(|cell| { + let ptr = match cell.get() { + Some(ptr) => ptr, + None => panic!("time cannot be frozen from outside the Tokio runtime"), + }; + + let clock = unsafe { &*ptr }; + clock.advance(duration); + }); + + crate::task::yield_now().await; + } + + /// Return the current instant, factoring in frozen time. + pub(crate) fn now() -> Instant { + CLOCK.with(|cell| { + Instant::from_std(match cell.get() { + Some(ptr) => { + let clock = unsafe { &*ptr }; + + if let Some(frozen) = *clock.inner.frozen.lock().unwrap() { + clock.inner.start + frozen + } else { + std::time::Instant::now() + } + } + None => std::time::Instant::now(), + }) + }) + } + + impl Clock { + /// Return a new `Clock` instance that uses the current execution context's + /// source of time. + pub(crate) fn new() -> Clock { + Clock { + inner: Arc::new(Inner { + start: std::time::Instant::now(), + frozen: Mutex::new(None), + }), + } + } + + // TODO: delete this. Some tests rely on this + #[cfg(all(test, not(loom)))] + /// Return a new `Clock` instance that uses the current execution context's + /// source of time. + pub(crate) fn new_frozen() -> Clock { + Clock { + inner: Arc::new(Inner { + start: std::time::Instant::now(), + frozen: Mutex::new(Some(Duration::from_millis(0))), + }), + } + } + + pub(crate) fn advance(&self, duration: Duration) { + let mut frozen = self.inner.frozen.lock().unwrap(); + + if let Some(ref mut elapsed) = *frozen { + *elapsed += duration; + } else { + panic!("time is not frozen"); + } + } + + // TODO: delete this as well + #[cfg(all(test, not(loom)))] + pub(crate) fn advanced(&self) -> Duration { + self.inner.frozen.lock().unwrap().unwrap() + } + + pub(crate) fn now(&self) -> Instant { + Instant::from_std(if let Some(frozen) = *self.inner.frozen.lock().unwrap() { + self.inner.start + frozen + } else { + std::time::Instant::now() + }) + } + + /// Set the clock as the default source of time for the duration of the + /// closure + pub(crate) fn enter(&self, f: F) -> R + where + F: FnOnce() -> 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>); + + impl Drop for Reset<'_> { + fn drop(&mut self) { + self.0.set(None); + } + } + + let _reset = Reset(cell); + + cell.set(Some(self as *const Clock)); + + f() + }) + } + } +} diff --git a/tokio/src/time/clock/mod.rs b/tokio/src/time/clock/mod.rs deleted file mode 100644 index 17cbe2f9b..000000000 --- a/tokio/src/time/clock/mod.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! 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`]. -//! -//! The source of time used by [`now`][n] can be configured by implementing the -//! [`Now`] trait and passing an instance to [`with_default`]. -//! -//! [n]: fn.now.html -//! [`Now`]: trait.Now.html -//! [`Instant`]: std::time::Instant -//! [`Instant::now`]: std::time::Instant::now -//! [`with_default`]: fn.with_default.html - -mod now; - -pub use self::now::Now; - -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`]. -/// -/// [`Instant`]: std::time::Instant -/// [`Instant::now`]: std::time::Instant::now -#[derive(Default, Clone)] -pub struct Clock { - now: Option>, -} - -thread_local! { - /// Thread-local tracking the current clock - static CLOCK: Cell> = 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::time::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(now: impl Now) -> Clock { - Clock { - now: Some(Arc::new(now)), - } - } - - /// Return a new `Clock` instance that uses [`Instant::now`] as the source - /// of time. - /// - /// [`Instant::now`]: std::time::Instant::now - 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(), - } - } -} - -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)" - } 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(clock: &Clock, f: F) -> R -where - F: FnOnce() -> 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>); - - impl Drop for Reset<'_> { - fn drop(&mut self) { - self.0.set(None); - } - } - - let _reset = Reset(cell); - - cell.set(Some(clock as *const Clock)); - - f() - }) -} diff --git a/tokio/src/time/clock/now.rs b/tokio/src/time/clock/now.rs deleted file mode 100644 index f6b11b706..000000000 --- a/tokio/src/time/clock/now.rs +++ /dev/null @@ -1,15 +0,0 @@ -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. -/// -/// [`Instant`]: std::time::Instant -pub trait Now: Send + Sync + 'static { - /// Returns an instant corresponding to "now". - fn now(&self) -> Instant; -} diff --git a/tokio/src/time/deadline.rs b/tokio/src/time/deadline.rs deleted file mode 100644 index 9df67da6f..000000000 --- a/tokio/src/time/deadline.rs +++ /dev/null @@ -1,162 +0,0 @@ -#![allow(deprecated)] - -use crate::Delay; -use futures::{Async, Future, Poll}; -use std::error; -use std::fmt; -use std::time::Instant; - -#[deprecated(since = "0.2.6", note = "use Timeout instead")] -#[doc(hidden)] -#[derive(Debug)] -pub struct Deadline { - future: T, - delay: Delay, -} - -#[deprecated(since = "0.2.6", note = "use Timeout instead")] -#[doc(hidden)] -#[derive(Debug)] -pub struct DeadlineError(Kind); - -/// Deadline error variants -#[derive(Debug)] -enum Kind { - /// Inner future returned an error - Inner(T), - - /// The deadline elapsed. - Elapsed, - - /// Timer returned an error. - Timer(crate::Error), -} - -impl Deadline { - /// Create a new `Deadline` that completes when `future` completes or when - /// `deadline` is reached. - pub fn new(future: T, deadline: Instant) -> Deadline { - Deadline::new_with_delay(future, Delay::new(deadline)) - } - - pub(crate) fn new_with_delay(future: T, delay: Delay) -> Deadline { - Deadline { future, delay } - } - - /// Gets a reference to the underlying future in this deadline. - pub fn get_ref(&self) -> &T { - &self.future - } - - /// Gets a mutable reference to the underlying future in this deadline. - pub fn get_mut(&mut self) -> &mut T { - &mut self.future - } - - /// Consumes this deadline, returning the underlying future. - pub fn into_inner(self) -> T { - self.future - } -} - -impl Future for Deadline -where - T: Future, -{ - type Item = T::Item; - type Error = DeadlineError; - - fn poll(&mut self) -> Poll { - // First, try polling the future - match self.future.poll() { - Ok(Async::Ready(v)) => return Ok(Async::Ready(v)), - Ok(Async::NotReady) => {} - Err(e) => return Err(DeadlineError::inner(e)), - } - - // Now check the timer - match self.delay.poll() { - Ok(Async::NotReady) => Ok(Async::NotReady), - Ok(Async::Ready(_)) => Err(DeadlineError::elapsed()), - Err(e) => Err(DeadlineError::timer(e)), - } - } -} - -// ===== impl DeadlineError ===== - -impl DeadlineError { - /// Create a new `DeadlineError` representing the inner future completing - /// with `Err`. - pub fn inner(err: T) -> DeadlineError { - DeadlineError(Kind::Inner(err)) - } - - /// Returns `true` if the error was caused by the inner future completing - /// with `Err`. - pub fn is_inner(&self) -> bool { - match self.0 { - Kind::Inner(_) => true, - _ => false, - } - } - - /// Consumes `self`, returning the inner future error. - pub fn into_inner(self) -> Option { - match self.0 { - Kind::Inner(err) => Some(err), - _ => None, - } - } - - /// Create a new `DeadlineError` representing the inner future not - /// completing before the deadline is reached. - pub fn elapsed() -> DeadlineError { - DeadlineError(Kind::Elapsed) - } - - /// Returns `true` if the error was caused by the inner future not - /// completing before the deadline is reached. - pub fn is_elapsed(&self) -> bool { - match self.0 { - Kind::Elapsed => true, - _ => false, - } - } - - /// Creates a new `DeadlineError` representing an error encountered by the - /// timer implementation - pub fn timer(err: crate::Error) -> DeadlineError { - DeadlineError(Kind::Timer(err)) - } - - /// Returns `true` if the error was caused by the timer. - pub fn is_timer(&self) -> bool { - match self.0 { - Kind::Timer(_) => true, - _ => false, - } - } - - /// Consumes `self`, returning the error raised by the timer implementation. - pub fn into_timer(self) -> Option { - match self.0 { - Kind::Timer(err) => Some(err), - _ => None, - } - } -} - -impl error::Error for DeadlineError {} - -impl fmt::Display for DeadlineError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - use self::Kind::*; - - match self.0 { - Inner(ref e) => e.fmt(fmt), - Elapsed => "deadline has elapsed".fmt(fmt), - Timer(ref e) => e.fmt(fmt), - } - } -} diff --git a/tokio/src/time/delay.rs b/tokio/src/time/delay.rs index e79f9c341..83ab3c8fe 100644 --- a/tokio/src/time/delay.rs +++ b/tokio/src/time/delay.rs @@ -1,10 +1,10 @@ -use crate::time::timer::{HandlePriv, Registration}; +use crate::time::driver::Registration; +use crate::time::{Duration, Instant}; use futures_core::ready; use std::future::Future; use std::pin::Pin; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; /// A future that completes at a specified instant in time. /// @@ -46,17 +46,6 @@ impl Delay { Delay { registration } } - pub(crate) fn new_with_handle( - deadline: Instant, - duration: Duration, - handle: HandlePriv, - ) -> Delay { - let mut registration = Registration::new(deadline, duration); - registration.register_with(handle); - - Delay { registration } - } - /// Returns the instant at which the future will complete. pub fn deadline(&self) -> Instant { self.registration.deadline() diff --git a/tokio/src/time/delay_queue.rs b/tokio/src/time/delay_queue.rs index 38d049403..6fa455a67 100644 --- a/tokio/src/time/delay_queue.rs +++ b/tokio/src/time/delay_queue.rs @@ -4,10 +4,8 @@ //! //! [`DelayQueue`]: struct.DelayQueue.html -use crate::time::clock::now; -use crate::time::timer::Handle; use crate::time::wheel::{self, Wheel}; -use crate::time::{Delay, Error}; +use crate::time::{Delay, Duration, Error, Instant}; use futures_core::ready; use slab::Slab; @@ -16,7 +14,6 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; /// A queue of delayed elements. /// @@ -128,9 +125,6 @@ use std::time::{Duration, Instant}; /// [`reserve`]: #method.reserve #[derive(Debug)] pub struct DelayQueue { - /// Handle to the timer driving the `DelayQueue` - handle: Handle, - /// Stores data associated with entries slab: Slab>, @@ -224,31 +218,6 @@ impl DelayQueue { DelayQueue::with_capacity(0) } - /// Create a new, empty, `DelayQueue` backed by the specified timer. - /// - /// The queue will not allocate storage until items are inserted into it. - /// - /// # Examples - /// - /// ```rust,no_run - /// # use tokio::time::DelayQueue; - /// use tokio::time::timer::Handle; - /// - /// let handle = Handle::default(); - /// let delay_queue: DelayQueue = DelayQueue::with_capacity_and_handle(0, &handle); - /// ``` - pub fn with_capacity_and_handle(capacity: usize, handle: &Handle) -> DelayQueue { - DelayQueue { - handle: handle.clone(), - wheel: Wheel::new(), - slab: Slab::with_capacity(capacity), - expired: Stack::default(), - delay: None, - poll: wheel::Poll::new(0), - start: now(), - } - } - /// Create a new, empty, `DelayQueue` with the specified capacity. /// /// The queue will be able to hold at least `capacity` elements without @@ -271,7 +240,14 @@ impl DelayQueue { /// delay_queue.insert(11, Duration::from_secs(11)); /// ``` pub fn with_capacity(capacity: usize) -> DelayQueue { - DelayQueue::with_capacity_and_handle(capacity, &Handle::default()) + DelayQueue { + wheel: Wheel::new(), + slab: Slab::with_capacity(capacity), + expired: Stack::default(), + delay: None, + poll: wheel::Poll::new(0), + start: Instant::now(), + } } /// Insert `value` into the queue set to expire at a specific instant in @@ -302,8 +278,7 @@ impl DelayQueue { /// Basic usage /// /// ```rust - /// use tokio::time::DelayQueue; - /// use std::time::{Instant, Duration}; + /// use tokio::time::{DelayQueue, Duration, Instant}; /// /// let mut delay_queue = DelayQueue::new(); /// let key = delay_queue.insert_at( @@ -345,7 +320,7 @@ impl DelayQueue { }; if should_set_delay { - self.delay = Some(self.handle.delay(self.start + Duration::from_millis(when))); + self.delay = Some(Delay::new(self.start + Duration::from_millis(when))); } Key::new(key) @@ -420,7 +395,7 @@ impl DelayQueue { /// [`Key`]: struct.Key.html /// [type]: # pub fn insert(&mut self, value: T, timeout: Duration) -> Key { - self.insert_at(value, now() + timeout) + self.insert_at(value, Instant::now() + timeout) } fn insert_idx(&mut self, when: u64, key: usize) { @@ -501,8 +476,7 @@ impl DelayQueue { /// Basic usage /// /// ```rust - /// use tokio::time::DelayQueue; - /// use std::time::{Duration, Instant}; + /// use tokio::time::{DelayQueue, Duration, Instant}; /// /// let mut delay_queue = DelayQueue::new(); /// let key = delay_queue.insert("foo", Duration::from_secs(5)); @@ -568,7 +542,7 @@ impl DelayQueue { /// // "foo"is now scheduled to be returned in 10 seconds /// ``` pub fn reset(&mut self, key: &Key, timeout: Duration) { - self.reset_at(key, now() + timeout); + self.reset_at(key, Instant::now() + timeout); } /// Clears the queue, removing all items. @@ -702,7 +676,7 @@ impl DelayQueue { } if let Some(deadline) = self.next_deadline() { - self.delay = Some(self.handle.delay(deadline)); + self.delay = Some(Delay::new(deadline)); } else { return Poll::Ready(None); } diff --git a/tokio/src/time/timer/atomic_stack.rs b/tokio/src/time/driver/atomic_stack.rs similarity index 99% rename from tokio/src/time/timer/atomic_stack.rs rename to tokio/src/time/driver/atomic_stack.rs index fc73943ba..036d283df 100644 --- a/tokio/src/time/timer/atomic_stack.rs +++ b/tokio/src/time/driver/atomic_stack.rs @@ -1,4 +1,4 @@ -use crate::time::timer::Entry; +use crate::time::driver::Entry; use crate::time::Error; use std::ptr; diff --git a/tokio/src/time/timer/entry.rs b/tokio/src/time/driver/entry.rs similarity index 98% rename from tokio/src/time/timer/entry.rs rename to tokio/src/time/driver/entry.rs index b189b2f6b..97ce34de3 100644 --- a/tokio/src/time/timer/entry.rs +++ b/tokio/src/time/driver/entry.rs @@ -1,7 +1,7 @@ +use crate::loom::sync::atomic::AtomicU64; use crate::sync::AtomicWaker; -use crate::time::atomic::AtomicU64; -use crate::time::timer::{HandlePriv, Inner}; -use crate::time::Error; +use crate::time::driver::{HandlePriv, Inner}; +use crate::time::{Duration, Error, Instant}; use std::cell::UnsafeCell; use std::ptr; @@ -9,7 +9,6 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering::{Relaxed, SeqCst}; use std::sync::{Arc, Weak}; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; use std::u64; /// Internal state shared between a `Delay` instance and the timer. diff --git a/tokio/src/time/driver/handle.rs b/tokio/src/time/driver/handle.rs new file mode 100644 index 000000000..5d2e6b9b2 --- /dev/null +++ b/tokio/src/time/driver/handle.rs @@ -0,0 +1,108 @@ +use crate::time::driver::Inner; +use crate::time::Error; + +use std::cell::RefCell; +use std::fmt; +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; + +/// Handle to time driver instance. +#[derive(Debug, Clone)] +pub(crate) struct Handle { + inner: Option, +} + +/// Like `Handle` but never `None`. +#[derive(Clone)] +pub(crate) struct HandlePriv { + inner: Weak, +} + +thread_local! { + /// Tracks the timer for the current execution context. + static CURRENT_TIMER: RefCell> = RefCell::new(None) +} + +#[derive(Debug)] +///Unsets default timer handler on drop. +pub(crate) struct DefaultGuard<'a> { + prev: Option, + _lifetime: PhantomData<&'a u8>, +} + +impl Drop for DefaultGuard<'_> { + fn drop(&mut self) { + CURRENT_TIMER.with(|current| { + let mut current = current.borrow_mut(); + *current = self.prev.take(); + }) + } +} + +///Sets handle to default timer, returning guard that unsets it on drop. +/// +/// # Panics +/// +/// This function panics if there already is a default timer set. +pub(crate) fn set_default(handle: &Handle) -> DefaultGuard<'_> { + CURRENT_TIMER.with(|current| { + let mut current = current.borrow_mut(); + let prev = current.take(); + + let handle = handle + .as_priv() + .unwrap_or_else(|| panic!("`handle` does not reference a timer")); + + *current = Some(handle.clone()); + + DefaultGuard { + prev, + _lifetime: PhantomData, + } + }) +} + +impl Handle { + pub(crate) fn new(inner: Weak) -> Handle { + let inner = HandlePriv { inner }; + Handle { inner: Some(inner) } + } + + fn as_priv(&self) -> Option<&HandlePriv> { + self.inner.as_ref() + } +} + +impl Default for Handle { + fn default() -> Handle { + Handle { inner: None } + } +} + +impl HandlePriv { + /// Try to get a handle to the current timer. + /// + /// Returns `Err` if no handle is found. + pub(crate) fn try_current() -> Result { + CURRENT_TIMER.with(|current| match *current.borrow() { + Some(ref handle) => Ok(handle.clone()), + None => Err(Error::shutdown()), + }) + } + + /// Try to return a strong ref to the inner + pub(crate) fn inner(&self) -> Option> { + self.inner.upgrade() + } + + /// Consume the handle, returning the weak Inner ref. + pub(crate) fn into_inner(self) -> Weak { + self.inner + } +} + +impl fmt::Debug for HandlePriv { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "HandlePriv") + } +} diff --git a/tokio/src/time/timer/mod.rs b/tokio/src/time/driver/mod.rs similarity index 63% rename from tokio/src/time/timer/mod.rs rename to tokio/src/time/driver/mod.rs index e79f1f378..74309524c 100644 --- a/tokio/src/time/timer/mod.rs +++ b/tokio/src/time/driver/mod.rs @@ -1,29 +1,4 @@ -//! Timer implementation. -//! -//! This module contains the types needed to run a timer. -//! -//! The [`Timer`] type runs the timer logic. It holds all the necessary state -//! to track all associated [`Delay`] instances and delivering notifications -//! once the deadlines are reached. -//! -//! The [`Handle`] type is a reference to a [`Timer`] instance. This type is -//! `Clone`, `Send`, and `Sync`. This type is used to create instances of -//! [`Delay`]. -//! -//! [`Timer`] is generic over [`Now`]. This allows the source of time to be -//! customized. This ability is especially useful in tests and any environment -//! where determinism is necessary. -//! -//! Note, when using the Tokio runtime, the [`Timer`] does not need to be manually -//! setup as the runtime comes pre-configured with a [`Timer`] instance. -//! -//! [`Timer`]: struct.Timer.html -//! [`Handle`]: struct.Handle.html -//! [`Delay`]: Delay -//! [`Now`]: clock::Now -//! [`Now::now`]: clock::Now::now -//! [`Instant`]: std::time::Instant -//! [`Instant::now`]: std::time::Instant::now +//! Time driver mod atomic_stack; use self::atomic_stack::AtomicStack; @@ -32,8 +7,7 @@ mod entry; use self::entry::Entry; mod handle; -pub(crate) use self::handle::HandlePriv; -pub use self::handle::{set_default, DefaultGuard, Handle}; +pub(crate) use self::handle::{set_default, Handle, HandlePriv}; mod registration; pub(crate) use self::registration::Registration; @@ -41,58 +15,50 @@ pub(crate) use self::registration::Registration; mod stack; use self::stack::Stack; +use crate::loom::sync::atomic::{AtomicU64, AtomicUsize}; use crate::runtime::{Park, Unpark}; -use crate::time::atomic::AtomicU64; -use crate::time::clock::Clock; -use crate::time::wheel; -use crate::time::Error; +use crate::time::{wheel, Error}; +use crate::time::{Clock, Duration, Instant}; -use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::SeqCst; use std::sync::Arc; -use std::time::{Duration, Instant}; use std::usize; use std::{cmp, fmt}; -/// Timer implementation that drives [`Delay`], [`Interval`], and [`Timeout`]. +/// Time implementation that drives [`Delay`], [`Interval`], and [`Timeout`]. /// -/// A `Timer` instance tracks the state necessary for managing time and +/// A `Driver` instance tracks the state necessary for managing time and /// notifying the [`Delay`] instances once their deadlines are reached. /// -/// It is expected that a single `Timer` instance manages many individual -/// [`Delay`] instances. The `Timer` implementation is thread-safe and, as such, -/// is able to handle callers from across threads. +/// It is expected that a single instance manages many individual [`Delay`] +/// instances. The `Driver` implementation is thread-safe and, as such, is able +/// to handle callers from across threads. /// -/// Callers do not use `Timer` directly to create [`Delay`] instances. Instead, -/// [`Handle`][Handle.struct] is used. A handle for the timer instance is obtained by calling -/// [`handle`]. [`Handle`][Handle.struct] is the type that implements `Clone` and is `Send + -/// Sync`. -/// -/// After creating the `Timer` instance, the caller must repeatedly call -/// [`turn`]. The timer will perform no work unless [`turn`] is called +/// After creating the `Driver` instance, the caller must repeatedly call +/// [`turn`]. The time driver will perform no work unless [`turn`] is called /// repeatedly. /// -/// The `Timer` has a resolution of one millisecond. Any unit of time that falls +/// The driver has a resolution of one millisecond. Any unit of time that falls /// between milliseconds are rounded up to the next millisecond. /// -/// When the `Timer` instance is dropped, any outstanding [`Delay`] instance that -/// has not elapsed will be notified with an error. At this point, calling -/// `poll` on the [`Delay`] instance will result in `Err` being returned. +/// When an instance is dropped, any outstanding [`Delay`] instance that has not +/// elapsed will be notified with an error. At this point, calling `poll` on the +/// [`Delay`] instance will result in `Err` being returned. /// /// # Implementation /// -/// `Timer` is based on the [paper by Varghese and Lauck][paper]. +/// THe time driver is based on the [paper by Varghese and Lauck][paper]. /// /// A hashed timing wheel is a vector of slots, where each slot handles a time /// slice. As time progresses, the timer walks over the slot for the current /// instant, and processes each entry for that slot. When the timer reaches the /// end of the wheel, it starts again at the beginning. /// -/// The `Timer` implementation maintains six wheels arranged in a set of levels. -/// As the levels go up, the slots of the associated wheel represent larger -/// intervals of time. At each level, the wheel has 64 slots. Each slot covers a -/// range of time equal to the wheel at the lower level. At level zero, each -/// slot represents one millisecond of time. +/// The implementation maintains six wheels arranged in a set of levels. As the +/// levels go up, the slots of the associated wheel represent larger intervals +/// of time. At each level, the wheel has 64 slots. Each slot covers a range of +/// time equal to the wheel at the lower level. At level zero, each slot +/// represents one millisecond of time. /// /// The wheels are: /// @@ -118,28 +84,21 @@ use std::{cmp, fmt}; /// [`turn`]: #method.turn /// [Handle.struct]: struct.Handle.html #[derive(Debug)] -pub struct Timer { +pub(crate) struct Driver { /// Shared state inner: Arc, /// Timer wheel wheel: wheel::Wheel, - /// Thread parker. The `Timer` park implementation delegates to this. + /// Thread parker. The `Driver` park implementation delegates to this. park: T, /// Source of "now" instances clock: Clock, } -/// Return value from the `turn` method on `Timer`. -/// -/// Currently this value doesn't actually provide any functionality, but it may -/// in the future give insight into what happened during `turn`. -#[derive(Debug)] -pub struct Turn(()); - -/// Timer state shared between `Timer`, `Handle`, and `Registration`. +/// Timer state shared between `Driver`, `Handle`, and `Registration`. pub(crate) struct Inner { /// The instant at which the timer started running. start: Instant, @@ -160,51 +119,20 @@ pub(crate) struct Inner { /// Maximum number of timeouts the system can handle concurrently. const MAX_TIMEOUTS: usize = usize::MAX >> 1; -// ===== impl Timer ===== +// ===== impl Driver ===== -impl Timer +impl Driver where T: Park, { - /// Create a new `Timer` instance that uses `park` to block the current - /// thread. - /// - /// Once the timer has been created, a handle can be obtained using - /// [`handle`]. The handle is used to create `Delay` instances. - /// - /// Use `default` when constructing a `Timer` using the default `park` - /// instance. - /// - /// [`handle`]: #method.handle - pub fn new(park: T) -> Self { - Timer::new_with_clock(park, Clock::new()) - } -} - -impl Timer { - /// Returns a reference to the underlying `Park` instance. - pub fn get_park(&self) -> &T { - &self.park - } - - /// Returns a mutable reference to the underlying `Park` instance. - pub fn get_park_mut(&mut self) -> &mut T { - &mut self.park - } -} - -impl Timer -where - T: Park, -{ - /// Create a new `Timer` instance that uses `park` to block the current + /// Create a new `Driver` instance that uses `park` to block the current /// thread and `now` to get the current `Instant`. /// /// Specifying the source of time is useful when testing. - pub fn new_with_clock(park: T, clock: Clock) -> Self { + pub(crate) fn new(park: T, clock: Clock) -> Driver { let unpark = Box::new(park.unpark()); - Timer { + Driver { inner: Arc::new(Inner::new(clock.now(), unpark)), wheel: wheel::Wheel::new(), park, @@ -218,38 +146,10 @@ where /// can either be created directly or the `Handle` instance can be passed to /// `with_default`, setting the timer as the default timer for the execution /// context. - pub fn handle(&self) -> Handle { + pub(crate) fn handle(&self) -> Handle { Handle::new(Arc::downgrade(&self.inner)) } - /// Performs one iteration of the timer loop. - /// - /// This function must be called repeatedly in order for the `Timer` - /// instance to make progress. This is where the work happens. - /// - /// The `Timer` will use the `Park` instance that was specified in [`new`] - /// to block the current thread until the next `Delay` instance elapses. One - /// call to `turn` results in at most one call to `park.park()`. - /// - /// # Return - /// - /// On success, `Ok(Turn)` is returned, where `Turn` is a placeholder type - /// that currently does nothing but may, in the future, have functions add - /// to provide information about the call to `turn`. - /// - /// If the call to `park.park()` fails, then `Err` is returned with the - /// error. - /// - /// [`new`]: #method.new - pub fn turn(&mut self, max_wait: Option) -> Result { - match max_wait { - Some(timeout) => self.park_timeout(timeout)?, - None => self.park()?, - } - - Ok(Turn(())) - } - /// Converts an `Expiration` to an `Instant`. fn expiration_instant(&self, when: u64) -> Instant { self.inner.start + Duration::from_millis(when) @@ -333,7 +233,7 @@ where } } -impl Park for Timer +impl Park for Driver where T: Park, { @@ -393,7 +293,7 @@ where } } -impl Drop for Timer { +impl Drop for Driver { fn drop(&mut self) { use std::u64; diff --git a/tokio/src/time/timer/registration.rs b/tokio/src/time/driver/registration.rs similarity index 87% rename from tokio/src/time/timer/registration.rs rename to tokio/src/time/driver/registration.rs index 1911fd520..3641e549b 100644 --- a/tokio/src/time/timer/registration.rs +++ b/tokio/src/time/driver/registration.rs @@ -1,9 +1,8 @@ -use crate::time::timer::{Entry, HandlePriv}; -use crate::time::Error; +use crate::time::driver::Entry; +use crate::time::{Duration, Error, Instant}; use std::sync::Arc; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; /// Registration with a timer. /// @@ -34,10 +33,6 @@ impl Registration { } } - pub(crate) fn register_with(&mut self, handle: HandlePriv) { - Entry::register_with(&mut self.entry, handle) - } - pub(crate) fn reset(&mut self, deadline: Instant) { unsafe { self.entry.time_mut().deadline = deadline; diff --git a/tokio/src/time/timer/stack.rs b/tokio/src/time/driver/stack.rs similarity index 99% rename from tokio/src/time/timer/stack.rs rename to tokio/src/time/driver/stack.rs index 763aa0ab8..220a96346 100644 --- a/tokio/src/time/timer/stack.rs +++ b/tokio/src/time/driver/stack.rs @@ -1,4 +1,4 @@ -use crate::time::timer::Entry; +use crate::time::driver::Entry; use crate::time::wheel; use std::ptr; diff --git a/tokio/src/time/instant.rs b/tokio/src/time/instant.rs new file mode 100644 index 000000000..faf7b1084 --- /dev/null +++ b/tokio/src/time/instant.rs @@ -0,0 +1,187 @@ +#![allow(clippy::trivially_copy_pass_by_ref)] + +use std::fmt; +use std::ops; +use std::time::Duration; + +/// A measurement of the system clock, useful for talking to +/// external entities like the file system or other processes. +#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] +pub struct Instant { + std: std::time::Instant, +} + +impl Instant { + /// Returns an instant corresponding to "now". + /// + /// # Examples + /// + /// ``` + /// use tokio::time::Instant; + /// + /// let now = Instant::now(); + /// ``` + pub fn now() -> Instant { + variant::now() + } + + /// Create a `tokio::time::Instant` from a `std::time::Instant`. + pub fn from_std(std: std::time::Instant) -> Instant { + Instant { std } + } + + /// Convert the value into a `std::time::Instant`. + pub fn into_std(self) -> std::time::Instant { + self.std + } + + /// Returns the amount of time elapsed from another instant to this one. + /// + /// # Panics + /// + /// This function will panic if `earlier` is later than `self`. + pub fn duration_since(&self, earlier: Instant) -> Duration { + self.std.duration_since(earlier.std) + } + + /// Returns the amount of time elapsed from another instant to this one, or + /// None if that instant is later than this one. + /// + /// # Examples + /// + /// ``` + /// use tokio::time::{Duration, Instant, delay_for}; + /// + /// #[tokio::main] + /// async fn main() { + /// let now = Instant::now(); + /// delay_for(Duration::new(1, 0)).await; + /// let new_now = Instant::now(); + /// println!("{:?}", new_now.checked_duration_since(now)); + /// println!("{:?}", now.checked_duration_since(new_now)); // None + /// } + /// ``` + pub fn checked_duration_since(&self, earlier: Instant) -> Option { + self.std.checked_duration_since(earlier.std) + } + + /// Returns the amount of time elapsed from another instant to this one, or + /// zero duration if that instant is earlier than this one. + /// + /// # Examples + /// + /// ``` + /// use tokio::time::{Duration, Instant, delay_for}; + /// + /// #[tokio::main] + /// async fn main() { + /// let now = Instant::now(); + /// delay_for(Duration::new(1, 0)).await; + /// let new_now = Instant::now(); + /// println!("{:?}", new_now.saturating_duration_since(now)); + /// println!("{:?}", now.saturating_duration_since(new_now)); // 0ns + /// } + /// ``` + pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { + self.std.saturating_duration_since(earlier.std) + } + + /// Returns the amount of time elapsed since this instant was created. + /// + /// # Panics + /// + /// This function may panic if the current time is earlier than this + /// instant, which is something that can happen if an `Instant` is + /// produced synthetically. + /// + /// # Examples + /// + /// ``` + /// use tokio::time::{Duration, Instant, delay_for}; + /// + /// #[tokio::main] + /// async fn main() { + /// let instant = Instant::now(); + /// let three_secs = Duration::from_secs(3); + /// delay_for(three_secs).await; + /// assert!(instant.elapsed() >= three_secs); + /// } + /// ``` + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Returns `Some(t)` where `t` is the time `self + duration` if `t` can be + /// represented as `Instant` (which means it's inside the bounds of the + /// underlying data structure), `None` otherwise. + pub fn checked_add(&self, duration: Duration) -> Option { + self.std.checked_add(duration).map(Instant::from_std) + } + + /// Returns `Some(t)` where `t` is the time `self - duration` if `t` can be + /// represented as `Instant` (which means it's inside the bounds of the + /// underlying data structure), `None` otherwise. + pub fn checked_sub(&self, duration: Duration) -> Option { + self.std.checked_sub(duration).map(Instant::from_std) + } +} + +impl ops::Add for Instant { + type Output = Instant; + + fn add(self, other: Duration) -> Instant { + Instant::from_std(self.std + other) + } +} + +impl ops::AddAssign for Instant { + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl ops::Sub for Instant { + type Output = Duration; + + fn sub(self, rhs: Instant) -> Duration { + self.std - rhs.std + } +} + +impl ops::Sub for Instant { + type Output = Instant; + + fn sub(self, rhs: Duration) -> Instant { + Instant::from_std(self.std - rhs) + } +} + +impl ops::SubAssign for Instant { + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl fmt::Debug for Instant { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.std.fmt(fmt) + } +} + +#[cfg(not(feature = "test-util"))] +mod variant { + use super::Instant; + + pub(super) fn now() -> Instant { + Instant::from_std(std::time::Instant::now()) + } +} + +#[cfg(feature = "test-util")] +mod variant { + use super::Instant; + + pub(super) fn now() -> Instant { + crate::time::clock::now() + } +} diff --git a/tokio/src/time/interval.rs b/tokio/src/time/interval.rs index f517b23f3..e60a8b8c6 100644 --- a/tokio/src/time/interval.rs +++ b/tokio/src/time/interval.rs @@ -1,11 +1,10 @@ -use crate::time::{clock, Delay}; +use crate::time::{Delay, Duration, Instant}; use futures_core::ready; use futures_util::future::poll_fn; use std::future::Future; use std::pin::Pin; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; /// A stream representing notifications at fixed interval #[derive(Debug)] @@ -47,7 +46,7 @@ impl Interval { /// /// This function panics if `duration` is zero. pub fn new_interval(duration: Duration) -> Interval { - Interval::new(clock::now() + duration, duration) + Interval::new(Instant::now() + duration, duration) } pub(crate) fn new_with_delay(delay: Delay, duration: Duration) -> Interval { diff --git a/tokio/src/time/mod.rs b/tokio/src/time/mod.rs index d6f1e0de1..ddcf9b23d 100644 --- a/tokio/src/time/mod.rs +++ b/tokio/src/time/mod.rs @@ -70,36 +70,42 @@ //! [Interval]: struct.Interval.html //! [`DelayQueue`]: struct.DelayQueue.html -pub mod clock; +mod clock; +pub(crate) use self::clock::Clock; +#[cfg(feature = "test-util")] +pub use clock::{advance, pause, resume}; pub mod delay_queue; #[doc(inline)] pub use self::delay_queue::DelayQueue; -pub mod throttle; +mod delay; +pub use self::delay::Delay; -// TODO: clean this up -pub mod timer; -pub use timer::{set_default, Timer}; +pub(crate) mod driver; + +mod error; +pub use error::Error; + +mod instant; +pub use self::instant::Instant; + +mod interval; +pub use interval::Interval; + +pub mod throttle; pub mod timeout; #[doc(inline)] pub use timeout::Timeout; -mod atomic; - -mod delay; -pub use self::delay::Delay; - -mod error; -pub use error::Error; - -mod interval; -pub use interval::Interval; - mod wheel; -use std::time::{Duration, Instant}; +#[cfg(test)] +#[cfg(not(loom))] +mod tests; + +pub use std::time::Duration; /// Create a Future that completes at `deadline`. pub fn delay(deadline: Instant) -> Delay { diff --git a/tokio/src/time/tests/mock_clock.rs b/tokio/src/time/tests/mock_clock.rs new file mode 100644 index 000000000..e38cbfa6d --- /dev/null +++ b/tokio/src/time/tests/mock_clock.rs @@ -0,0 +1,211 @@ +use crate::runtime::{Park, Unpark}; +use crate::time::driver::{self, Driver}; +use crate::time::{Clock, Duration, Instant}; + +use std::marker::PhantomData; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; + +/// Run the provided closure with a `MockClock` that starts at the current time. +pub(crate) fn mock(f: F) -> R +where + F: FnOnce(&mut Handle) -> R, +{ + let mut mock = MockClock::new(); + mock.enter(f) +} + +/// Mock clock for use with `tokio-timer` futures. +/// +/// A mock timer that is able to advance and wake after a +/// certain duration. +#[derive(Debug)] +pub(crate) struct MockClock { + time: MockTime, + clock: Clock, +} + +/// A handle to the `MockClock`. +#[derive(Debug)] +pub(crate) struct Handle { + timer: Driver, + time: MockTime, + clock: Clock, +} + +type Inner = Arc>; + +#[derive(Debug, Clone)] +struct MockTime { + inner: Inner, + _pd: PhantomData>, +} + +#[derive(Debug)] +struct MockNow { + inner: Inner, +} + +#[derive(Debug)] +struct MockPark { + inner: Inner, + _pd: PhantomData>, +} + +#[derive(Debug)] +struct MockUnpark { + inner: Inner, +} + +#[derive(Debug)] +struct State { + clock: Clock, + unparked: bool, + park_for: Option, +} + +impl MockClock { + /// Create a new `MockClock` with the current time. + pub(crate) fn new() -> Self { + let clock = Clock::new_frozen(); + let time = MockTime::new(clock.clone()); + + MockClock { time, clock } + } + + /// Enter the `MockClock` context. + pub(crate) fn enter(&mut self, f: F) -> R + where + F: FnOnce(&mut Handle) -> R, + { + self.clock.enter(|| { + let park = self.time.mock_park(); + let timer = Driver::new(park, self.clock.clone()); + let handle = timer.handle(); + let _e = driver::set_default(&handle); + + let time = self.time.clone(); + + let mut handle = Handle::new(timer, time, self.clock.clone()); + f(&mut handle) + // lazy(|| Ok::<_, ()>(f(&mut handle))).wait().unwrap() + }) + } +} + +impl Default for MockClock { + fn default() -> Self { + Self::new() + } +} + +impl Handle { + pub(self) fn new(timer: Driver, time: MockTime, clock: Clock) -> Self { + Handle { timer, time, clock } + } + + /// Turn the internal timer and mock park for the provided duration. + pub(crate) fn turn(&mut self) { + self.timer.park().unwrap(); + } + + /// Turn the internal timer and mock park for the provided duration. + pub(crate) fn turn_for(&mut self, duration: Duration) { + self.timer.park_timeout(duration).unwrap(); + } + + /// Advance the `MockClock` by the provided duration. + pub(crate) fn advance(&mut self, duration: Duration) { + let now = Instant::now(); + let end = now + duration; + + while Instant::now() < end { + self.turn_for(end - Instant::now()); + } + } + + /// Returns the total amount of time the time has been advanced. + pub(crate) fn advanced(&self) -> Duration { + self.clock.advanced() + } + + /// Get the currently mocked time + pub(crate) fn now(&mut self) -> Instant { + self.time.now() + } + + /// Turn the internal timer once, but force "parking" for `duration` regardless of any pending + /// timeouts + pub(crate) fn park_for(&mut self, duration: Duration) { + self.time.inner.lock().unwrap().park_for = Some(duration); + self.turn() + } +} + +impl MockTime { + pub(crate) fn new(clock: Clock) -> MockTime { + let state = State { + clock, + unparked: false, + park_for: None, + }; + + MockTime { + inner: Arc::new(Mutex::new(state)), + _pd: PhantomData, + } + } + + pub(crate) fn mock_park(&self) -> MockPark { + let inner = self.inner.clone(); + MockPark { + inner, + _pd: PhantomData, + } + } + + pub(crate) fn now(&self) -> Instant { + Instant::now() + } +} + +impl State {} + +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.clock.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.clock.advance(duration); + } else { + inner.clock.advance(duration); + } + + Ok(()) + } +} + +impl Unpark for MockUnpark { + fn unpark(&self) { + if let Ok(mut inner) = self.inner.lock() { + inner.unparked = true; + } + } +} diff --git a/tokio/src/time/tests/mod.rs b/tokio/src/time/tests/mod.rs new file mode 100644 index 000000000..0aead1363 --- /dev/null +++ b/tokio/src/time/tests/mod.rs @@ -0,0 +1,4 @@ +mod mock_clock; + +mod test_delay; +mod test_queue; diff --git a/tokio/tests/timer_delay.rs b/tokio/src/time/tests/test_delay.rs similarity index 91% rename from tokio/tests/timer_delay.rs rename to tokio/src/time/tests/test_delay.rs index 185067497..797d2625c 100644 --- a/tokio/tests/timer_delay.rs +++ b/tokio/src/time/tests/test_delay.rs @@ -1,15 +1,13 @@ #![warn(rust_2018_idioms)] -use tokio::time::delay; -use tokio::time::timer::Handle; +use crate::time::tests::mock_clock::mock; +use crate::time::{delay, Duration, Instant}; use tokio_test::task; -use tokio_test::{assert_pending, assert_ready, clock}; - -use std::time::{Duration, Instant}; +use tokio_test::{assert_pending, assert_ready}; #[test] fn immediate_delay() { - clock::mock(|clock| { + mock(|clock| { // Create `Delay` that elapsed immediately. let mut fut = task::spawn(delay(clock.now())); @@ -27,7 +25,7 @@ fn immediate_delay() { #[test] fn delayed_delay_level_0() { for &i in &[1, 10, 60] { - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(i))); @@ -44,7 +42,7 @@ fn delayed_delay_level_0() { #[test] fn sub_ms_delayed_delay() { - clock::mock(|clock| { + mock(|clock| { for _ in 0..5 { let deadline = clock.now() + Duration::from_millis(1) + Duration::new(0, 1); @@ -64,7 +62,7 @@ fn sub_ms_delayed_delay() { #[test] fn delayed_delay_wrapping_level_0() { - clock::mock(|clock| { + mock(|clock| { clock.turn_for(ms(5)); assert_eq!(clock.advanced(), ms(5)); @@ -85,7 +83,7 @@ fn delayed_delay_wrapping_level_0() { #[test] fn timer_wrapping_with_higher_levels() { - clock::mock(|clock| { + mock(|clock| { // Set delay to hit level 1 let mut s1 = task::spawn(delay(clock.now() + ms(64))); assert_pending!(s1.poll()); @@ -113,7 +111,7 @@ fn timer_wrapping_with_higher_levels() { #[test] fn delay_with_deadline_in_past() { - clock::mock(|clock| { + mock(|clock| { // Create `Delay` that elapsed immediately. let mut fut = task::spawn(delay(clock.now() - ms(100))); @@ -131,7 +129,7 @@ fn delay_with_deadline_in_past() { #[test] fn delayed_delay_level_1() { - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(234))); @@ -153,7 +151,7 @@ fn delayed_delay_level_1() { assert_ready!(fut.poll()); }); - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(234))); @@ -190,7 +188,7 @@ fn creating_delay_outside_of_context() { // that it will still expire. let mut fut = task::spawn(delay(now + ms(500))); - clock::mock_at(now, |clock| { + mock(|clock| { // This registers the delay with the timer assert_pending!(fut.poll()); @@ -210,7 +208,7 @@ fn creating_delay_outside_of_context() { #[test] fn concurrently_set_two_timers_second_one_shorter() { - clock::mock(|clock| { + mock(|clock| { let mut fut1 = task::spawn(delay(clock.now() + ms(500))); let mut fut2 = task::spawn(delay(clock.now() + ms(200))); @@ -245,7 +243,7 @@ fn concurrently_set_two_timers_second_one_shorter() { #[test] fn short_delay() { - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(1))); @@ -267,7 +265,7 @@ fn short_delay() { fn sorta_long_delay() { const MIN_5: u64 = 5 * 60 * 1000; - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(MIN_5))); @@ -295,7 +293,7 @@ fn sorta_long_delay() { fn very_long_delay() { const MO_5: u64 = 5 * 30 * 24 * 60 * 60 * 1000; - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(MO_5))); @@ -332,7 +330,7 @@ fn very_long_delay() { fn greater_than_max() { const YR_5: u64 = 5 * 365 * 24 * 60 * 60 * 1000; - clock::mock(|clock| { + mock(|clock| { // Create a `Delay` that elapses in the future let mut fut = task::spawn(delay(clock.now() + ms(YR_5))); @@ -347,7 +345,7 @@ fn greater_than_max() { #[test] fn unpark_is_delayed() { - clock::mock(|clock| { + mock(|clock| { let mut fut1 = task::spawn(delay(clock.now() + ms(100))); let mut fut2 = task::spawn(delay(clock.now() + ms(101))); let mut fut3 = task::spawn(delay(clock.now() + ms(200))); @@ -371,7 +369,7 @@ 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; - clock::mock(|clock| { + mock(|clock| { for _ in 0..5 { clock.turn_for(ms(YR_1)); } @@ -388,7 +386,7 @@ fn set_timeout_at_deadline_greater_than_max_timer() { #[test] fn reset_future_delay_before_fire() { - clock::mock(|clock| { + mock(|clock| { let mut fut = task::spawn(delay(clock.now() + ms(100))); assert_pending!(fut.poll()); @@ -409,7 +407,7 @@ fn reset_future_delay_before_fire() { #[test] fn reset_past_delay_before_turn() { - clock::mock(|clock| { + mock(|clock| { let mut fut = task::spawn(delay(clock.now() + ms(100))); assert_pending!(fut.poll()); @@ -430,7 +428,7 @@ fn reset_past_delay_before_turn() { #[test] fn reset_past_delay_before_fire() { - clock::mock(|clock| { + mock(|clock| { let mut fut = task::spawn(delay(clock.now() + ms(100))); assert_pending!(fut.poll()); @@ -453,7 +451,7 @@ fn reset_past_delay_before_fire() { #[test] fn reset_future_delay_after_fire() { - clock::mock(|clock| { + mock(|clock| { let mut fut = task::spawn(delay(clock.now() + ms(100))); assert_pending!(fut.poll()); @@ -476,22 +474,6 @@ fn reset_future_delay_after_fire() { }); } -#[test] -fn delay_with_default_handle() { - let handle = Handle::default(); - let now = Instant::now(); - - let mut fut = task::spawn(handle.delay(now + ms(1))); - - clock::mock_at(now, |clock| { - assert_pending!(fut.poll()); - - clock.turn_for(ms(1)); - - assert_ready!(fut.poll()); - }); -} - fn ms(n: u64) -> Duration { Duration::from_millis(n) } diff --git a/tokio/tests/timer_queue.rs b/tokio/src/time/tests/test_queue.rs similarity index 93% rename from tokio/tests/timer_queue.rs rename to tokio/src/time/tests/test_queue.rs index 118f86f3e..cfcbfc1a8 100644 --- a/tokio/tests/timer_queue.rs +++ b/tokio/src/time/tests/test_queue.rs @@ -1,10 +1,8 @@ #![warn(rust_2018_idioms)] -use tokio::time::*; -use tokio_test::{assert_ok, assert_pending, assert_ready}; -use tokio_test::{clock, task}; - -use std::time::Duration; +use crate::time::tests::mock_clock::mock; +use crate::time::{DelayQueue, Duration}; +use tokio_test::{assert_ok, assert_pending, assert_ready, task}; macro_rules! poll { ($queue:ident) => { @@ -23,7 +21,7 @@ macro_rules! assert_ready_ok { #[test] fn single_immediate_delay() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let _key = queue.insert_at("foo", clock.now()); @@ -37,7 +35,7 @@ fn single_immediate_delay() { #[test] fn multi_immediate_delays() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let _k = queue.insert_at("1", clock.now()); @@ -64,7 +62,7 @@ fn multi_immediate_delays() { #[test] fn single_short_delay() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let _key = queue.insert_at("foo", clock.now() + ms(5)); @@ -91,7 +89,7 @@ fn multi_delay_at_start() { let long = 262_144 + 9 * 4096; let delays = &[1000, 2, 234, long, 60, 10]; - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); // Setup the delays @@ -124,7 +122,7 @@ fn multi_delay_at_start() { #[test] fn insert_in_past_fires_immediately() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let now = clock.now(); @@ -139,7 +137,7 @@ fn insert_in_past_fires_immediately() { #[test] fn remove_entry() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let key = queue.insert_at("foo", clock.now() + ms(5)); @@ -158,7 +156,7 @@ fn remove_entry() { #[test] fn reset_entry() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let now = clock.now(); @@ -192,7 +190,7 @@ fn reset_entry() { #[test] fn reset_much_later() { // Reproduces tokio-rs/tokio#849. - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -216,7 +214,7 @@ fn reset_much_later() { #[test] fn reset_twice() { // Reproduces tokio-rs/tokio#849. - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -243,7 +241,7 @@ fn reset_twice() { #[test] fn remove_expired_item() { - clock::mock(|clock| { + mock(|clock| { let mut queue = DelayQueue::new(); let now = clock.now(); @@ -259,7 +257,7 @@ fn remove_expired_item() { #[test] fn expires_before_last_insert() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -285,7 +283,7 @@ fn expires_before_last_insert() { #[test] fn multi_reset() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -303,7 +301,7 @@ fn multi_reset() { #[test] fn expire_first_key_when_reset_to_expire_earlier() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -326,7 +324,7 @@ fn expire_first_key_when_reset_to_expire_earlier() { #[test] fn expire_second_key_when_reset_to_expire_earlier() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); @@ -348,7 +346,7 @@ fn expire_second_key_when_reset_to_expire_earlier() { #[test] fn reset_first_expiring_item_to_expire_later() { - clock::mock(|clock| { + mock(|clock| { let mut queue = task::spawn(DelayQueue::new()); let epoch = clock.now(); diff --git a/tokio/src/time/throttle.rs b/tokio/src/time/throttle.rs index 5572c0b89..07215cd16 100644 --- a/tokio/src/time/throttle.rs +++ b/tokio/src/time/throttle.rs @@ -1,6 +1,6 @@ //! Slow down a stream by enforcing a delay between items. -use crate::time::{clock, Delay}; +use crate::time::{Delay, Instant}; use futures_core::ready; use futures_core::Stream; @@ -16,17 +16,27 @@ use std::{ #[derive(Debug)] #[must_use = "streams do nothing unless polled"] pub struct Throttle { - delay: Delay, + /// `None` when duration is zero. + delay: Option, + /// Set to true when `delay` has returned ready, but `stream` hasn't. has_delayed: bool, + + /// The stream to throttle stream: T, } impl Throttle { /// Slow down a stream by enforcing a delay between items. pub fn new(stream: T, duration: Duration) -> Self { + let delay = if duration == Duration::from_millis(0) { + None + } else { + Some(Delay::new_timeout(Instant::now() + duration, duration)) + }; + Self { - delay: Delay::new_timeout(clock::now() + duration, duration), + delay, has_delayed: true, stream, } @@ -64,8 +74,11 @@ impl Stream for Throttle { fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { unsafe { - if !self.has_delayed { - ready!(self.as_mut().map_unchecked_mut(|me| &mut me.delay).poll(cx)); + if !self.has_delayed && self.delay.is_some() { + ready!(self + .as_mut() + .map_unchecked_mut(|me| me.delay.as_mut().unwrap()) + .poll(cx)); self.as_mut().get_unchecked_mut().has_delayed = true; } @@ -75,7 +88,10 @@ impl Stream for Throttle { .poll_next(cx)); if value.is_some() { - self.as_mut().get_unchecked_mut().delay.reset_timeout(); + if let Some(ref mut delay) = self.as_mut().get_unchecked_mut().delay { + delay.reset_timeout(); + } + self.as_mut().get_unchecked_mut().has_delayed = false; } diff --git a/tokio/src/time/timeout.rs b/tokio/src/time/timeout.rs index e1fdb252e..2cc350820 100644 --- a/tokio/src/time/timeout.rs +++ b/tokio/src/time/timeout.rs @@ -5,14 +5,13 @@ //! [`Timeout`]: struct.Timeout.html use crate::time::clock::now; -use crate::time::Delay; +use crate::time::{Delay, Duration, Instant}; use futures_core::ready; use std::fmt; use std::future::Future; use std::pin::Pin; use std::task::{self, Poll}; -use std::time::{Duration, Instant}; /// Allows a `Future` or `Stream` to execute for a limited amount of time. /// diff --git a/tokio/src/time/timer/handle.rs b/tokio/src/time/timer/handle.rs deleted file mode 100644 index 044c4aba8..000000000 --- a/tokio/src/time/timer/handle.rs +++ /dev/null @@ -1,187 +0,0 @@ -use crate::time::clock::now; -use crate::time::timer::Inner; -use crate::time::{Delay, Error, Timeout}; - -use std::cell::RefCell; -use std::fmt; -use std::marker::PhantomData; -use std::sync::{Arc, Weak}; -use std::time::{Duration, Instant}; - -/// Handle to timer instance. -/// -/// The `Handle` allows creating `Delay` instances that are driven by the -/// associated timer. -/// -/// A `Handle` is obtained by calling [`Timer::handle`], [`Handle::current`], or -/// [`Handle::default`]. -/// -/// * [`Timer::handle`]: returns a handle associated with the specific timer. -/// The handle will always reference the same timer. -/// -/// * [`Handle::current`]: returns a handle to the timer for the execution -/// context **at the time the function is called**. This function must be -/// called from a runtime that has an associated timer or it will panic. -/// The handle will always reference the same timer. -/// -/// * [`Handle::default`]: returns a handle to the timer for the execution -/// context **at the time the handle is used**. This function is safe to call -/// at any time. The handle may reference different specific timer instances. -/// Calling `Handle::default().delay(...)` is always equivalent to -/// `Delay::new(...)`. -/// -/// [`Timer::handle`]: struct.Timer.html#method.handle -/// [`Handle::current`]: #method.current -/// [`Handle::default`]: #method.default -#[derive(Debug, Clone)] -pub struct Handle { - inner: Option, -} - -/// Like `Handle` but never `None`. -#[derive(Clone)] -pub(crate) struct HandlePriv { - inner: Weak, -} - -thread_local! { - /// Tracks the timer for the current execution context. - static CURRENT_TIMER: RefCell> = RefCell::new(None) -} - -#[derive(Debug)] -///Unsets default timer handler on drop. -pub struct DefaultGuard<'a> { - _lifetime: PhantomData<&'a u8>, -} - -impl Drop for DefaultGuard<'_> { - fn drop(&mut self) { - CURRENT_TIMER.with(|current| { - let mut current = current.borrow_mut(); - *current = None; - }) - } -} - -///Sets handle to default timer, returning guard that unsets it on drop. -/// -/// # Panics -/// -/// This function panics if there already is a default timer set. -pub fn set_default(handle: &Handle) -> DefaultGuard<'_> { - CURRENT_TIMER.with(|current| { - let mut current = current.borrow_mut(); - - assert!( - current.is_none(), - "default Tokio timer already set \ - for execution context" - ); - - let handle = handle - .as_priv() - .unwrap_or_else(|| panic!("`handle` does not reference a timer")); - - *current = Some(handle.clone()); - }); - - DefaultGuard { - _lifetime: PhantomData, - } -} - -impl Handle { - pub(crate) fn new(inner: Weak) -> Handle { - let inner = HandlePriv { inner }; - Handle { inner: Some(inner) } - } - - /// Returns a handle to the current timer. - /// - /// The current timer is the timer that is currently set as default using - /// [`with_default`]. - /// - /// This function should only be called from within the context of - /// [`with_default`]. Calling this function from outside of this context - /// will return a `Handle` that does not reference a timer. `Delay` - /// instances created with this handle will error. - /// - /// See [type] level documentation for more ways to obtain a `Handle` value. - /// - /// [`with_default`]: fn.with_default - /// [type]: # - pub fn current() -> Handle { - let private = - HandlePriv::try_current().unwrap_or_else(|_| HandlePriv { inner: Weak::new() }); - - Handle { - inner: Some(private), - } - } - - /// Create a `Delay` driven by this handle's associated `Timer`. - pub fn delay(&self, deadline: Instant) -> Delay { - self.delay_timeout(deadline, Duration::from_secs(0)) - } - - fn delay_timeout(&self, deadline: Instant, duration: Duration) -> Delay { - match self.inner { - Some(ref handle_priv) => { - Delay::new_with_handle(deadline, duration, handle_priv.clone()) - } - None => Delay::new_timeout(deadline, duration), - } - } - - /// Create a `Timeout` driven by this handle's associated `Timer`. - pub fn timeout(&self, value: T, timeout: Duration) -> Timeout { - Timeout::new_with_delay(value, self.delay_timeout(now() + timeout, timeout)) - } - - /* - /// Create a new `Interval` that starts at `at` and yields every `duration` - /// interval after that. - pub fn interval(&self, at: Instant, duration: Duration) -> Interval { - Interval::new_with_delay(self.delay(at), duration) - } - */ - - fn as_priv(&self) -> Option<&HandlePriv> { - self.inner.as_ref() - } -} - -impl Default for Handle { - fn default() -> Handle { - Handle { inner: None } - } -} - -impl HandlePriv { - /// Try to get a handle to the current timer. - /// - /// Returns `Err` if no handle is found. - pub(crate) fn try_current() -> Result { - CURRENT_TIMER.with(|current| match *current.borrow() { - Some(ref handle) => Ok(handle.clone()), - None => Err(Error::shutdown()), - }) - } - - /// Try to return a strong ref to the inner - pub(crate) fn inner(&self) -> Option> { - self.inner.upgrade() - } - - /// Consume the handle, returning the weak Inner ref. - pub(crate) fn into_inner(self) -> Weak { - self.inner - } -} - -impl fmt::Debug for HandlePriv { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "HandlePriv") - } -} diff --git a/tokio/src/time/timer/now.rs b/tokio/src/time/timer/now.rs deleted file mode 100644 index 8e412b5eb..000000000 --- a/tokio/src/time/timer/now.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::time::Instant; - -#[doc(hidden)] -#[deprecated(since = "0.2.4", note = "use clock::Now instead")] -pub trait Now { - /// Returns an instant corresponding to "now". - fn now(&mut self) -> Instant; -} - -#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 -pub use crate::clock::Clock as SystemNow; diff --git a/tokio/tests/clock.rs b/tokio/tests/clock.rs deleted file mode 100644 index ff949eac9..000000000 --- a/tokio/tests/clock.rs +++ /dev/null @@ -1,67 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::runtime; -use tokio::time::clock::Clock; -use tokio::time::*; - -use std::sync::mpsc; -use std::time::{Duration, Instant}; - -struct MockNow(Instant); - -impl tokio::time::clock::Now for MockNow { - fn now(&self) -> Instant { - self.0 - } -} - -#[test] -fn clock_and_timer_concurrent() { - let when = Instant::now() + Duration::from_millis(5_000); - let clock = Clock::new_with_now(MockNow(when)); - - let mut rt = runtime::Builder::new() - .thread_pool() - .clock(clock) - .build() - .unwrap(); - - let (tx, rx) = mpsc::channel(); - - rt.block_on(async move { - tokio::spawn(async move { - delay(when).await; - assert!(Instant::now() < when); - tx.send(()).unwrap(); - }) - }); - - rx.recv().unwrap(); -} - -#[test] -fn clock_and_timer_single_threaded() { - let when = Instant::now() + Duration::from_millis(5_000); - let clock = Clock::new_with_now(MockNow(when)); - - let mut rt = runtime::Builder::new() - .current_thread() - .clock(clock) - .build() - .unwrap(); - - rt.block_on(async move { - delay(when).await; - assert!(Instant::now() < when); - }); -} - -#[test] -fn mocked_clock_delay_for() { - tokio_test::clock::mock(|handle| { - let mut f = tokio_test::task::spawn(delay_for(Duration::from_millis(1))); - tokio_test::assert_pending!(f.poll()); - handle.advance(Duration::from_millis(1)); - tokio_test::assert_ready!(f.poll()); - }); -} diff --git a/tokio/tests/rt_common.rs b/tokio/tests/rt_common.rs index d6c1b1fd0..73982ced3 100644 --- a/tokio/tests/rt_common.rs +++ b/tokio/tests/rt_common.rs @@ -264,14 +264,14 @@ rt_test! { #[test] fn spawn_from_other_thread() { let mut rt = rt(); - let sp = rt.spawner(); + let handle = rt.handle().clone(); let (tx, rx) = oneshot::channel(); thread::spawn(move || { thread::sleep(Duration::from_millis(50)); - sp.spawn(async move { + handle.spawn(async move { assert_ok!(tx.send(())); }); }); diff --git a/tokio/tests/rt_thread_pool.rs b/tokio/tests/rt_thread_pool.rs index fcf5ef850..d290d75a9 100644 --- a/tokio/tests/rt_thread_pool.rs +++ b/tokio/tests/rt_thread_pool.rs @@ -265,7 +265,7 @@ fn blocking() { for _ in 0..4 { let block = block.clone(); rt.spawn(async move { - tokio::runtime::blocking::in_place(move || { + tokio::blocking::in_place(move || { block.wait(); block.wait(); }) diff --git a/tokio/tests/support/mock_pool.rs b/tokio/tests/support/mock_pool.rs index acdb8dbc4..e1fdb4264 100644 --- a/tokio/tests/support/mock_pool.rs +++ b/tokio/tests/support/mock_pool.rs @@ -32,13 +32,13 @@ where } impl Future for Blocking { - type Output = T; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { use std::task::Poll::*; match Pin::new(&mut self.rx).poll(cx) { - Ready(Ok(v)) => Ready(v), + Ready(Ok(v)) => Ready(Ok(v)), Ready(Err(e)) => panic!("error = {:?}", e), Pending => Pending, } @@ -50,7 +50,7 @@ where F: FnOnce() -> io::Result + Send + 'static, T: Send + 'static, { - run(f).await + run(f).await? } pub(crate) fn len() -> usize { diff --git a/tokio/tests/time_interval.rs b/tokio/tests/time_interval.rs new file mode 100644 index 000000000..c884ca8e9 --- /dev/null +++ b/tokio/tests/time_interval.rs @@ -0,0 +1,45 @@ +#![warn(rust_2018_idioms)] + +use tokio::time::{self, Duration, Instant, Interval}; +use tokio_test::{assert_pending, assert_ready_eq, task}; + +#[tokio::test] +#[should_panic] +async fn interval_zero_duration() { + let _ = Interval::new(Instant::now(), ms(0)); +} + +#[tokio::test] +async fn usage() { + time::pause(); + + let start = Instant::now(); + + // TODO: Skip this + time::advance(ms(1)).await; + + let mut int = task::spawn(Interval::new(start, ms(300))); + + assert_ready_eq!(int.poll_next(), Some(start)); + assert_pending!(int.poll_next()); + + time::advance(ms(100)).await; + assert_pending!(int.poll_next()); + + time::advance(ms(200)).await; + assert_ready_eq!(int.poll_next(), Some(start + ms(300))); + assert_pending!(int.poll_next()); + + time::advance(ms(400)).await; + assert_ready_eq!(int.poll_next(), Some(start + ms(600))); + assert_pending!(int.poll_next()); + + time::advance(ms(500)).await; + assert_ready_eq!(int.poll_next(), Some(start + ms(900))); + assert_ready_eq!(int.poll_next(), Some(start + ms(1200))); + assert_pending!(int.poll_next()); +} + +fn ms(n: u64) -> Duration { + Duration::from_millis(n) +} diff --git a/tokio/tests/timer_rt.rs b/tokio/tests/time_rt.rs similarity index 98% rename from tokio/tests/timer_rt.rs rename to tokio/tests/time_rt.rs index ef337e590..ecce72d6f 100644 --- a/tokio/tests/timer_rt.rs +++ b/tokio/tests/time_rt.rs @@ -4,7 +4,6 @@ use tokio::prelude::*; use tokio::time::*; use std::sync::mpsc; -use std::time::{Duration, Instant}; #[test] fn timer_with_threaded_runtime() { diff --git a/tokio/tests/time_throttle.rs b/tokio/tests/time_throttle.rs new file mode 100644 index 000000000..0431a4f2c --- /dev/null +++ b/tokio/tests/time_throttle.rs @@ -0,0 +1,68 @@ +#![warn(rust_2018_idioms)] + +use tokio::sync::mpsc; +use tokio::time::throttle::Throttle; +use tokio::time::Instant; +use tokio_test::{assert_pending, assert_ready_eq}; + +use futures::future::poll_fn; +use futures::StreamExt; +use std::task::Poll; +use std::time::Duration; + +#[tokio::test] +async fn throttle() { + let (mut tx, rx) = mpsc::unbounded_channel(); + let mut stream = Throttle::new(rx, ms(1)); + + poll_fn(|cx| { + assert_pending!(stream.poll_next_unpin(cx)); + Poll::Ready(()) + }) + .await; + + for i in 0..3 { + tx.try_send(i).unwrap(); + } + + drop(tx); + + let mut now = Instant::now(); + + while let Some(_) = stream.next().await { + assert!(Instant::now() >= now); + now += ms(1); + } +} + +#[tokio::test] +async fn throttle_dur_0() { + let (mut tx, rx) = mpsc::unbounded_channel(); + let mut stream = Throttle::new(rx, ms(0)); + + poll_fn(|cx| { + assert_pending!(stream.poll_next_unpin(cx)); + + for i in 0..3 { + tx.try_send(i).unwrap(); + } + + Poll::Ready(()) + }) + .await; + + poll_fn(|cx| { + for i in 0..3 { + assert_ready_eq!(stream.poll_next_unpin(cx), Some(i), "i = {}", i); + } + + assert_pending!(stream.poll_next_unpin(cx)); + + Poll::Ready(()) + }) + .await; +} + +fn ms(n: u64) -> Duration { + Duration::from_millis(n) +} diff --git a/tokio/tests/time_timeout.rs b/tokio/tests/time_timeout.rs new file mode 100644 index 000000000..fe3298af6 --- /dev/null +++ b/tokio/tests/time_timeout.rs @@ -0,0 +1,166 @@ +#![warn(rust_2018_idioms)] + +use tokio::sync::oneshot; +use tokio::time::{self, Instant, Timeout}; +use tokio_test::*; + +use futures::future::pending; +use std::time::Duration; + +#[tokio::test] +async fn simultaneous_deadline_future_completion() { + // Create a future that is immediately ready + let mut fut = task::spawn(Timeout::new_at(async {}, Instant::now())); + + // Ready! + assert_ready_ok!(fut.poll()); +} + +#[tokio::test] +async fn completed_future_past_deadline() { + // Wrap it with a deadline + let mut fut = task::spawn(Timeout::new_at(async {}, Instant::now() - ms(1000))); + + // Ready! + assert_ready_ok!(fut.poll()); +} + +#[tokio::test] +async fn future_and_deadline_in_future() { + time::pause(); + + // Not yet complete + let (tx, rx) = oneshot::channel(); + + // Wrap it with a deadline + let mut fut = task::spawn(Timeout::new_at(rx, Instant::now() + ms(100))); + + assert_pending!(fut.poll()); + + // Turn the timer, it runs for the elapsed time + time::advance(ms(90)).await; + + assert_pending!(fut.poll()); + + // Complete the future + tx.send(()).unwrap(); + assert!(fut.is_woken()); + + assert_ready_ok!(fut.poll()).unwrap(); +} + +#[tokio::test] +async fn future_and_timeout_in_future() { + time::pause(); + + // Not yet complete + let (tx, rx) = oneshot::channel(); + + // Wrap it with a deadline + let mut fut = task::spawn(Timeout::new(rx, ms(100))); + + // Ready! + assert_pending!(fut.poll()); + + // Turn the timer, it runs for the elapsed time + time::advance(ms(90)).await; + + assert_pending!(fut.poll()); + + // Complete the future + tx.send(()).unwrap(); + + assert_ready_ok!(fut.poll()).unwrap(); +} + +#[tokio::test] +async fn deadline_now_elapses() { + use futures::future::pending; + + time::pause(); + + // Wrap it with a deadline + let mut fut = task::spawn(Timeout::new_at(pending::<()>(), Instant::now())); + + // Factor in jitter + // TODO: don't require this + time::advance(ms(1)).await; + + assert_ready_err!(fut.poll()); +} + +#[tokio::test] +async fn deadline_future_elapses() { + time::pause(); + + // Wrap it with a deadline + let mut fut = task::spawn(Timeout::new_at(pending::<()>(), Instant::now() + ms(300))); + + assert_pending!(fut.poll()); + + time::advance(ms(301)).await; + + assert!(fut.is_woken()); + assert_ready_err!(fut.poll()); +} + +#[tokio::test] +async fn stream_and_timeout_in_future() { + use tokio::sync::mpsc; + + time::pause(); + + // Not yet complete + let (mut tx, rx) = mpsc::unbounded_channel(); + + // Wrap it with a deadline + let mut stream = task::spawn(Timeout::new(rx, ms(100))); + + // Not ready + assert_pending!(stream.poll_next()); + + // Turn the timer, it runs for the elapsed time + time::advance(ms(90)).await; + + assert_pending!(stream.poll_next()); + + // Complete the future + tx.try_send(()).unwrap(); + + let item = assert_ready!(stream.poll_next()); + assert!(item.is_some()); +} + +#[tokio::test] +async fn idle_stream_timesout_periodically() { + use tokio::sync::mpsc; + + time::pause(); + + // Not yet complete + let (_tx, rx) = mpsc::unbounded_channel::<()>(); + + // Wrap it with a deadline + let mut stream = task::spawn(Timeout::new(rx, ms(100))); + + // Not ready + assert_pending!(stream.poll_next()); + + // Turn the timer, it runs for the elapsed time + time::advance(ms(101)).await; + + let v = assert_ready!(stream.poll_next()).unwrap(); + assert_err!(v); + + // Stream's timeout should reset + assert_pending!(stream.poll_next()); + + // Turn the timer, it runs for the elapsed time + time::advance(ms(101)).await; + let v = assert_ready!(stream.poll_next()).unwrap(); + assert_err!(v); +} + +fn ms(n: u64) -> Duration { + Duration::from_millis(n) +} diff --git a/tokio/tests/timer_clock.rs b/tokio/tests/timer_clock.rs deleted file mode 100644 index 2dc45dae9..000000000 --- a/tokio/tests/timer_clock.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::time::clock; -use tokio::time::clock::*; - -use std::time::Instant; - -struct ConstNow(Instant); - -impl Now for ConstNow { - fn now(&self) -> Instant { - self.0 - } -} - -#[test] -fn default_clock() { - let a = Instant::now(); - let b = clock::now(); - let c = Clock::new().now(); - - assert!(a <= b); - assert!(b <= c); -} - -#[test] -fn custom_clock() { - let now = ConstNow(Instant::now()); - let clock = Clock::new_with_now(now); - - let a = Instant::now(); - let b = clock.now(); - - assert!(b <= a); -} - -#[test] -fn execution_context() { - let now = ConstNow(Instant::now()); - let clock = Clock::new_with_now(now); - - with_default(&clock, || { - let a = Instant::now(); - let b = clock::now(); - - assert!(b <= a); - }); -} diff --git a/tokio/tests/timer_hammer.rs b/tokio/tests/timer_hammer.rs deleted file mode 100644 index 6f1ad98ea..000000000 --- a/tokio/tests/timer_hammer.rs +++ /dev/null @@ -1,246 +0,0 @@ -#![warn(rust_2018_idioms)] -#![cfg(broken)] - -use tokio::executor::park::{Park, Unpark, UnparkThread}; -use tokio::runtime; -use tokio::time::{Delay, Timer}; - -use rand::Rng; -use std::cmp; -use std::future::Future; -use std::pin::Pin; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::SeqCst; -use std::sync::{Arc, Barrier}; -use std::task::{Context, Poll}; -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 = rt(); - 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)); - let delay = handle.delay(deadline); - - exec.spawn(async move { - delay.await; - - let now = Instant::now(); - assert!(now >= deadline, "deadline greater by {:?}", deadline - now); - }); - } - - // Run the logic - exec.run().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 = rt(); - let mut rng = rand::thread_rng(); - - barrier.wait(); - - for _ in 0..PER_THREAD { - let timeout1 = Duration::from_millis(rng.gen_range(MIN_DELAY, MAX_DELAY)); - let timeout2 = Duration::from_millis(rng.gen_range(MIN_DELAY, MAX_DELAY)); - - let deadline = Instant::now() + cmp::min(timeout1, timeout2); - - let delay = handle.delay(Instant::now() + timeout1); - let join = handle.timeout(delay, timeout2); - - exec.spawn(async move { - let _ = join.await; - - let now = Instant::now(); - assert!(now >= deadline, "deadline greater by {:?}", deadline - now); - }); - } - - // Run the logic - exec.run().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 = rt(); - 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)); - - struct Select { - a: Option, - b: Option, - } - - impl Future for Select { - type Output = Delay; - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { - let res = Pin::new(self.a.as_mut().unwrap()).poll(cx); - - if res.is_ready() { - return Poll::Ready(self.a.take().unwrap()); - } - - let res = Pin::new(self.b.as_mut().unwrap()).poll(cx); - - if res.is_ready() { - return Poll::Ready(self.b.take().unwrap()); - } - - Poll::Pending - } - } - - let s = Select { - a: Some(handle.delay(deadline1)), - b: Some(handle.delay(deadline2)), - }; - - exec.spawn(async move { - let mut delay = s.await; - - let now = Instant::now(); - assert!( - now >= deadline1, - "deadline greater by {:?}", - deadline1 - now - ); - - delay.reset(deadline3); - delay.await; - }); - } - - // Run the logic - exec.run().unwrap(); - - if 1 == done.rem.fetch_sub(1, SeqCst) { - done.unpark.unpark(); - } - }); - } - - while done.rem.load(SeqCst) > 0 { - timer.turn(None).unwrap(); - } - } -} - -fn rt() -> runtime::Runtime { - runtime::Builder::new().current_thread().build().unwrap() -} diff --git a/tokio/tests/timer_interval.rs b/tokio/tests/timer_interval.rs deleted file mode 100644 index d15140f55..000000000 --- a/tokio/tests/timer_interval.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::time::*; -use tokio_test::task; -use tokio_test::{assert_pending, assert_ready_eq, clock}; - -use std::time::Duration; - -#[test] -#[should_panic] -fn interval_zero_duration() { - clock::mock(|clock| { - let _ = Interval::new(clock.now(), ms(0)); - }); -} - -#[test] -fn usage() { - clock::mock(|clock| { - let start = clock.now(); - let mut int = task::spawn(Interval::new(start, ms(300))); - - assert_ready_eq!(int.poll_next(), Some(start)); - assert_pending!(int.poll_next()); - - clock.advance(ms(100)); - assert_pending!(int.poll_next()); - - clock.advance(ms(200)); - assert_ready_eq!(int.poll_next(), Some(start + ms(300))); - assert_pending!(int.poll_next()); - - clock.advance(ms(400)); - assert_ready_eq!(int.poll_next(), Some(start + ms(600))); - assert_pending!(int.poll_next()); - - clock.advance(ms(500)); - assert_ready_eq!(int.poll_next(), Some(start + ms(900))); - assert_ready_eq!(int.poll_next(), Some(start + ms(1200))); - assert_pending!(int.poll_next()); - }); -} - -fn ms(n: u64) -> Duration { - Duration::from_millis(n) -} diff --git a/tokio/tests/timer_throttle.rs b/tokio/tests/timer_throttle.rs deleted file mode 100644 index e119ff937..000000000 --- a/tokio/tests/timer_throttle.rs +++ /dev/null @@ -1,55 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::sync::mpsc; -use tokio::time::throttle::Throttle; -use tokio_test::task; -use tokio_test::{assert_pending, assert_ready_eq, clock}; - -use std::time::Duration; - -#[test] -fn throttle() { - clock::mock(|clock| { - let (mut tx, rx) = mpsc::unbounded_channel(); - let mut stream = task::spawn(Throttle::new(rx, ms(1))); - - assert_pending!(stream.poll_next()); - - for i in 0..3 { - tx.try_send(i).unwrap(); - } - - for i in 0..3 { - assert_ready_eq!(stream.poll_next(), Some(i)); - assert_pending!(stream.poll_next()); - - clock.advance(ms(1)); - } - - assert_pending!(stream.poll_next()); - }); -} - -#[test] -fn throttle_dur_0() { - clock::mock(|_| { - let (mut tx, rx) = mpsc::unbounded_channel(); - let mut stream = task::spawn(Throttle::new(rx, ms(0))); - - assert_pending!(stream.poll_next()); - - for i in 0..3 { - tx.try_send(i).unwrap(); - } - - for i in 0..3 { - assert_ready_eq!(stream.poll_next(), Some(i)); - } - - assert_pending!(stream.poll_next()); - }); -} - -fn ms(n: u64) -> Duration { - Duration::from_millis(n) -} diff --git a/tokio/tests/timer_timeout.rs b/tokio/tests/timer_timeout.rs deleted file mode 100644 index 0bc135d8e..000000000 --- a/tokio/tests/timer_timeout.rs +++ /dev/null @@ -1,178 +0,0 @@ -#![warn(rust_2018_idioms)] - -use tokio::sync::oneshot; -use tokio::time::*; -use tokio_test::task; -use tokio_test::{ - assert_err, assert_pending, assert_ready, assert_ready_err, assert_ready_ok, clock, -}; - -use std::time::Duration; - -#[test] -fn simultaneous_deadline_future_completion() { - clock::mock(|clock| { - // Create a future that is immediately ready - let mut fut = task::spawn(Timeout::new_at(async {}, clock.now())); - - // Ready! - assert_ready_ok!(fut.poll()); - }); -} - -#[test] -fn completed_future_past_deadline() { - clock::mock(|clock| { - // Wrap it with a deadline - let mut fut = task::spawn(Timeout::new_at(async {}, clock.now() - ms(1000))); - - // Ready! - assert_ready_ok!(fut.poll()); - }); -} - -#[test] -fn future_and_deadline_in_future() { - clock::mock(|clock| { - // Not yet complete - let (tx, rx) = oneshot::channel(); - - // Wrap it with a deadline - let mut fut = task::spawn(Timeout::new_at(rx, clock.now() + ms(100))); - - assert_pending!(fut.poll()); - - // Turn the timer, it runs for the elapsed time - clock.advance(ms(90)); - - assert_pending!(fut.poll()); - - // Complete the future - tx.send(()).unwrap(); - - assert_ready_ok!(fut.poll()).unwrap(); - }); -} - -#[test] -fn future_and_timeout_in_future() { - clock::mock(|clock| { - // Not yet complete - let (tx, rx) = oneshot::channel(); - - // Wrap it with a deadline - let mut fut = task::spawn(Timeout::new(rx, ms(100))); - - // Ready! - assert_pending!(fut.poll()); - - // Turn the timer, it runs for the elapsed time - clock.advance(ms(90)); - - assert_pending!(fut.poll()); - - // Complete the future - tx.send(()).unwrap(); - - assert_ready_ok!(fut.poll()).unwrap(); - }); -} - -struct Empty; - -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -impl Future for Empty { - type Output = (); - - fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { - Poll::Pending - } -} - -#[test] -fn deadline_now_elapses() { - clock::mock(|clock| { - // Wrap it with a deadline - let mut fut = task::spawn(Timeout::new_at(Empty, clock.now())); - - assert_ready_err!(fut.poll()); - }); -} - -#[test] -fn deadline_future_elapses() { - clock::mock(|clock| { - // Wrap it with a deadline - let mut fut = task::spawn(Timeout::new_at(Empty, clock.now() + ms(300))); - - assert_pending!(fut.poll()); - - clock.advance(ms(300)); - - assert_ready_err!(fut.poll()); - }); -} - -#[test] -fn stream_and_timeout_in_future() { - use tokio::sync::mpsc; - - clock::mock(|clock| { - // Not yet complete - let (mut tx, rx) = mpsc::unbounded_channel(); - - // Wrap it with a deadline - let mut stream = task::spawn(Timeout::new(rx, ms(100))); - - // Not ready - assert_pending!(stream.poll_next()); - - // Turn the timer, it runs for the elapsed time - clock.advance(ms(90)); - - assert_pending!(stream.poll_next()); - - // Complete the future - tx.try_send(()).unwrap(); - - let item = assert_ready!(stream.poll_next()); - assert!(item.is_some()); - }); -} - -#[test] -fn idle_stream_timesout_periodically() { - use tokio::sync::mpsc; - - clock::mock(|clock| { - // Not yet complete - let (_tx, rx) = mpsc::unbounded_channel::<()>(); - - // Wrap it with a deadline - let mut stream = task::spawn(Timeout::new(rx, ms(100))); - - // Not ready - assert_pending!(stream.poll_next()); - - // Turn the timer, it runs for the elapsed time - clock.advance(ms(100)); - - let v = assert_ready!(stream.poll_next()).unwrap(); - assert_err!(v); - - // Stream's timeout should reset - assert_pending!(stream.poll_next()); - - // Turn the timer, it runs for the elapsed time - clock.advance(ms(100)); - let v = assert_ready!(stream.poll_next()).unwrap(); - assert_err!(v) - }); -} - -fn ms(n: u64) -> Duration { - Duration::from_millis(n) -}