mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-26 00:00:16 +02:00
reorganize modules (#1766)
This patch started as an effort to make `time::Timer` private. However, in an effort to get the build compiling again, more and more changes were made. This probably should have been broken up, but here we are. I will attempt to summarize the changes here. * Feature flags are reorganized to make clearer. `net-driver` becomes `io-driver`. `rt-current-thread` becomes `rt-core`. * The `Runtime` can be created without any executor. This replaces `enter`. It also allows creating I/O / time drivers that are standalone. * `tokio::timer` is renamed to `tokio::time`. This brings it in line with `std`. * `tokio::timer::Timer` is renamed to `Driver` and made private. * The `clock` module is removed. Instead, an `Instant` type is provided. This type defaults to calling `std::time::Instant`. A `test-util` feature flag can be used to enable hooking into time. * The `blocking` module is moved to the top level and is cleaned up. * The `task` module is moved to the top level. * The thread-pool's in-place blocking implementation is cleaned up. * `runtime::Spawner` is renamed to `runtime::Handle` and can be used to "enter" a runtime context.
This commit is contained in:
+1
-1
@@ -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 }}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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, R>(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<F, R>(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<MockPark>,
|
||||
time: MockTime,
|
||||
}
|
||||
|
||||
type Inner = Arc<Mutex<State>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MockTime {
|
||||
inner: Inner,
|
||||
_pd: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockNow {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockPark {
|
||||
inner: Inner,
|
||||
_pd: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockUnpark {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
base: Instant,
|
||||
advance: Duration,
|
||||
unparked: bool,
|
||||
park_for: Option<Duration>,
|
||||
}
|
||||
|
||||
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<F, R>(&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<MockPark>, 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()
|
||||
}
|
||||
}
|
||||
@@ -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<Action>,
|
||||
waiting: Option<Instant>,
|
||||
|
||||
timer_handle: timer::Handle,
|
||||
sleep: Option<Delay>,
|
||||
read_wait: Option<Waker>,
|
||||
rx: mpsc::UnboundedReceiver<Action>,
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
//! Tokio and Futures based testing utilites
|
||||
|
||||
pub mod clock;
|
||||
pub mod io;
|
||||
mod macros;
|
||||
pub mod task;
|
||||
|
||||
@@ -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 }));
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
@@ -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"] }
|
||||
|
||||
+37
-42
@@ -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 }
|
||||
|
||||
@@ -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, R>(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<dyn std::error::Error>>{
|
||||
/// 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, R>(f: F) -> JoinHandle<R>
|
||||
where
|
||||
F: FnOnce() -> R + Send + 'static,
|
||||
R: Send + 'static,
|
||||
{
|
||||
pool::spawn(f)
|
||||
}
|
||||
@@ -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<Inner>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
/// State shared between worker threads
|
||||
shared: Mutex<Shared>,
|
||||
|
||||
/// Pool threads wait on this.
|
||||
condvar: Condvar,
|
||||
|
||||
/// Spawned threads use this name
|
||||
thread_name: String,
|
||||
|
||||
/// Spawned thread stack size
|
||||
stack_size: Option<usize>,
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
queue: VecDeque<Task>,
|
||||
num_th: u32,
|
||||
num_idle: u32,
|
||||
num_notify: u32,
|
||||
shutdown: bool,
|
||||
}
|
||||
|
||||
type Task = task::Task<NoopSchedule>;
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current executor
|
||||
static BLOCKING: Cell<Option<*const Spawner>> = 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<F, R>(func: F) -> JoinHandle<R>
|
||||
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<usize>) -> 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<F>(&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<F, R>(&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>>, 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());
|
||||
}
|
||||
@@ -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<Self>) {}
|
||||
|
||||
fn release(&self, _task: Task<Self>) {}
|
||||
|
||||
fn release_local(&self, _task: &Task<Self>) {}
|
||||
|
||||
fn schedule(&self, _task: Task<Self>) {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
@@ -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<T> {
|
||||
func: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> BlockingTask<T> {
|
||||
/// Initialize a new blocking task from the given function
|
||||
pub(super) fn new(func: T) -> BlockingTask<T> {
|
||||
BlockingTask { func: Some(func) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> Future for BlockingTask<T>
|
||||
where
|
||||
T: FnOnce() -> R,
|
||||
{
|
||||
type Output = R;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<R> {
|
||||
let me = unsafe { self.get_unchecked_mut() };
|
||||
let func = me
|
||||
.func
|
||||
.take()
|
||||
.expect("[internal exception] blocking task ran twice.");
|
||||
|
||||
Poll::Ready(func())
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-2
@@ -84,12 +84,20 @@ where
|
||||
F: FnOnce() -> io::Result<T> + 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;
|
||||
}
|
||||
|
||||
@@ -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))));
|
||||
|
||||
+24
-25
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
+27
-9
@@ -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 = <str as sealed::ToSocketAddrsPriv>::Iter;
|
||||
type Future = <str as sealed::ToSocketAddrsPriv>::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<SocketAddr>),
|
||||
Blocking(Blocking<io::Result<vec::IntoIter<SocketAddr>>>),
|
||||
Blocking(JoinHandle<io::Result<vec::IntoIter<SocketAddr>>>),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
#[cfg(feature = "dns")]
|
||||
pub enum OneOrMore {
|
||||
One(option::IntoIter<SocketAddr>),
|
||||
More(vec::IntoIter<SocketAddr>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "dns")]
|
||||
impl Future for MaybeReady {
|
||||
type Output = io::Result<OneOrMore>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
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;
|
||||
|
||||
|
||||
@@ -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 _};
|
||||
|
||||
@@ -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<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
f()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Pool>),
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current executor
|
||||
static BLOCKING: Cell<State> = 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<F, R>(pool: &Arc<Pool>, 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>, 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<Shared>,
|
||||
|
||||
/// Pool threads wait on this.
|
||||
condvar: Condvar,
|
||||
|
||||
/// Spawned threads use this name
|
||||
thread_name: String,
|
||||
|
||||
/// Spawned thread stack size
|
||||
stack_size: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PoolWaiter(Arc<Pool>);
|
||||
|
||||
impl fmt::Debug for Pool {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Pool").finish()
|
||||
}
|
||||
}
|
||||
|
||||
struct Shared {
|
||||
queue: VecDeque<Box<dyn FnOnce() + Send>>,
|
||||
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<T> {
|
||||
rx: oneshot::Receiver<T>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub(crate) fn new(thread_name: String, stack_size: Option<usize>) -> Arc<Pool> {
|
||||
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<Self>, f: Box<dyn FnOnce() + Send + 'static>) {
|
||||
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<Self>) {
|
||||
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<Pool> for PoolWaiter {
|
||||
fn from(p: Pool) -> Self {
|
||||
Self::from(Arc::new(p))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<Pool>> for PoolWaiter {
|
||||
fn from(p: Arc<Pool>) -> Self {
|
||||
Self(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for PoolWaiter {
|
||||
type Target = Arc<Pool>;
|
||||
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, R>(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, R>(f: F) -> Blocking<R>
|
||||
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<T> Future for Blocking<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
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<dyn FnOnce() + Send>) {
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
let _ = catch_unwind(AssertUnwindSafe(|| f()));
|
||||
}
|
||||
@@ -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<usize>,
|
||||
pub(super) thread_stack_size: Option<usize>,
|
||||
|
||||
/// Callback to run after each thread starts.
|
||||
after_start: Option<Callback>,
|
||||
|
||||
/// To run before each worker thread stops
|
||||
before_stop: Option<Callback>,
|
||||
|
||||
/// 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<Runtime> {
|
||||
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<Runtime> {
|
||||
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<Runtime> {
|
||||
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<Runtime> {
|
||||
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<blocking::Pool> {
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<F: FnOnce() -> 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<F: Future>(&mut self, mut f: F) -> F::Output {
|
||||
use crate::runtime::park::{CachedParkThread, Park};
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -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<F, R>(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<F, R>(state: State, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
|
||||
@@ -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<io::Handle>,
|
||||
|
||||
/// Handles to the time drivers
|
||||
pub(super) time_handles: Vec<time::Handle>,
|
||||
|
||||
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<F>(&self, future: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future<Output = ()> + 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<F, R>(&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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
|
||||
+37
-59
@@ -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<io::Handle>,
|
||||
/// Handle to runtime, also contains driver handles
|
||||
handle: Handle,
|
||||
|
||||
/// Timer handles
|
||||
timer_handles: Vec<timer::Handle>,
|
||||
|
||||
/// 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<timer::Driver>),
|
||||
#[cfg(feature = "rt-core")]
|
||||
CurrentThread(CurrentThread<time::Driver>),
|
||||
|
||||
/// 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<F>(&self, future: F) -> JoinHandle<F::Output>
|
||||
where
|
||||
F: Future<Output = ()> + 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<F: Future>(&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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,6 +22,7 @@ pub(crate) struct CachedParkThread {
|
||||
_anchor: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ParkThread {
|
||||
inner: Arc<Inner>,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 = <io::Driver as Park>::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<F>(&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) };
|
||||
}
|
||||
@@ -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),
|
||||
_ => {}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -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<Inner> = Cell::new(Inner::new()));
|
||||
|
||||
pub(super) fn set<F, R, P>(pool: &Arc<worker::Set<P>>, index: usize, f: F) -> R
|
||||
pub(super) fn set<F, R, P>(pool: &Arc<slice::Set<P>>, 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<P>) -> Option<&'a Owned<P>>
|
||||
pub(super) fn as_member<'a, P>(&self, set: &'a slice::Set<P>) -> Option<&'a Owned<P>>
|
||||
where
|
||||
P: Unpark,
|
||||
{
|
||||
|
||||
@@ -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<Box<_>> is needed because loom doesn't support Arc<T> where T: !Sized
|
||||
@@ -66,7 +61,7 @@ type Callback = Arc<Box<dyn Fn(usize, &mut dyn FnMut()) + Send + Sync>>;
|
||||
impl ThreadPool {
|
||||
pub(crate) fn new<F, P>(
|
||||
pool_size: usize,
|
||||
blocking_pool: Arc<blocking::Pool>,
|
||||
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<BoxedPark<P>>| {
|
||||
// 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<dyn FnOnce() + Send + 'static>
|
||||
})
|
||||
as Box<dyn Fn(Worker<BoxedPark<P>>) -> Box<dyn FnOnce() + Send> + Send + Sync>);
|
||||
|
||||
let (pool, workers) = worker::create_set::<_, BoxedPark<P>>(
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<P: 'static> {
|
||||
/// 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<u16>,
|
||||
|
||||
@@ -40,6 +48,7 @@ where
|
||||
{
|
||||
pub(super) fn new(work_queue: queue::Worker<Shared<P>>, rand: FastRand) -> Owned<P> {
|
||||
Owned {
|
||||
generation: AtomicUsize::new(0),
|
||||
tick: Cell::new(1),
|
||||
is_running: Cell::new(true),
|
||||
is_searching: Cell::new(false),
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<T: 'static> {
|
||||
cluster: Arc<Cluster<T>>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<P>,
|
||||
/// The slice::Set itself is tracked by an `Arc`, but this pointer is not
|
||||
/// included in the ref count.
|
||||
slices: *const slice::Set<P>,
|
||||
}
|
||||
|
||||
unsafe impl<P: Unpark> Send for Shared<P> {}
|
||||
@@ -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>) {
|
||||
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<P>) {
|
||||
self.set = set;
|
||||
fn slices(&self) -> &slice::Set<P> {
|
||||
unsafe { &*self.slices }
|
||||
}
|
||||
|
||||
fn set(&self) -> &worker::Set<P> {
|
||||
unsafe { &*self.set }
|
||||
pub(super) fn set_slices_ptr(&mut self, slices: *const slice::Set<P>) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<crate::runtime::blocking::Pool>,
|
||||
}
|
||||
|
||||
unsafe impl<P: Unpark> Send for Set<P> {}
|
||||
@@ -40,11 +36,7 @@ where
|
||||
P: Unpark,
|
||||
{
|
||||
/// Create a new worker set using the provided queues.
|
||||
pub(crate) fn new<F>(
|
||||
num_workers: usize,
|
||||
mut mk_unpark: F,
|
||||
blocking: Arc<crate::runtime::blocking::Pool>,
|
||||
) -> Self
|
||||
pub(crate) fn new<F>(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<crate::runtime::blocking::Pool> {
|
||||
&self.blocking
|
||||
}
|
||||
|
||||
pub(crate) fn schedule(&self, task: Task<Shared<P>>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<worker::Set<Box<dyn Unpark>>>,
|
||||
workers: Arc<slice::Set<Box<dyn Unpark>>>,
|
||||
}
|
||||
|
||||
impl Spawner {
|
||||
pub(super) fn new(workers: Arc<worker::Set<Box<dyn Unpark>>>) -> Spawner {
|
||||
pub(super) fn new(workers: Arc<slice::Set<Box<dyn Unpark>>>) -> Spawner {
|
||||
Spawner { workers }
|
||||
}
|
||||
|
||||
@@ -45,12 +45,8 @@ impl Spawner {
|
||||
self.workers.spawn_background(future);
|
||||
}
|
||||
|
||||
pub(super) fn blocking_pool(&self) -> &Arc<crate::runtime::blocking::Pool> {
|
||||
self.workers.blocking_pool()
|
||||
}
|
||||
|
||||
/// Reference to the worker set. Used by `ThreadPool` to initiate shutdown.
|
||||
pub(super) fn workers(&self) -> &worker::Set<Box<dyn Unpark>> {
|
||||
pub(super) fn workers(&self) -> &slice::Set<Box<dyn Unpark>> {
|
||||
&*self.workers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Output = &'static str> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 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<Notify>,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -9,6 +9,3 @@ mod pool;
|
||||
|
||||
#[cfg(not(loom))]
|
||||
mod queue;
|
||||
|
||||
#[cfg(not(loom))]
|
||||
mod worker;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<Box<_>> is needed because loom doesn't support Arc<T> where T: !Sized
|
||||
// loom doesn't support that because it requires CoerceUnsized, which is unstable
|
||||
type LaunchWorker<P> = Arc<Box<dyn Fn(Worker<P>) -> Box<dyn FnOnce() + Send> + Send + Sync>>;
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current executor
|
||||
static ON_BLOCK: Cell<Option<*mut dyn FnMut()>> = Cell::new(None)
|
||||
/// Used to handle block_in_place
|
||||
static ON_BLOCK: Cell<Option<*const dyn Fn()>> = Cell::new(None)
|
||||
}
|
||||
|
||||
#[cfg(feature = "blocking")]
|
||||
pub(crate) fn blocking<F, R>(f: F) -> R
|
||||
pub(crate) fn block_in_place<F, R>(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<P: Park + 'static> {
|
||||
/// Entry in the set of workers.
|
||||
entry: Entry<P::Unpark>,
|
||||
/// Parks the thread. Requires the calling worker to have obtained unique
|
||||
/// access via the generation synchronization action.
|
||||
inner: Arc<Inner<P>>,
|
||||
|
||||
/// Park the thread
|
||||
park: Box<P>,
|
||||
/// Scheduler slices
|
||||
slices: Arc<slice::Set<P::Unpark>>,
|
||||
|
||||
/// Fn for launching another Worker should we need it
|
||||
launch_worker: LaunchWorker<P>,
|
||||
/// 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<bool>,
|
||||
}
|
||||
|
||||
/// Internal worker state. This may be referenced from multiple threads, but the
|
||||
/// generation guard protects unsafe access
|
||||
struct Inner<P: Park + 'static> {
|
||||
/// Used to park the thread
|
||||
park: CausalCell<P>,
|
||||
|
||||
/// Only held so that the scheduler can be signaled on shutdown.
|
||||
shutdown_tx: shutdown::Sender,
|
||||
}
|
||||
|
||||
// TODO: clean up
|
||||
unsafe impl<P: Park + Send + 'static> Send for Worker<P> {}
|
||||
|
||||
/// Used to ensure the invariants are respected
|
||||
struct GenerationGuard<'a, P: Park + 'static> {
|
||||
/// Worker reference
|
||||
worker: &'a Worker<P>,
|
||||
|
||||
/// Prevent `Sync` access
|
||||
_p: PhantomData<Cell<()>>,
|
||||
}
|
||||
|
||||
struct WorkerGone;
|
||||
|
||||
// TODO: Move into slices
|
||||
pub(super) fn create_set<F, P>(
|
||||
pool_size: usize,
|
||||
mk_park: F,
|
||||
launch_worker: LaunchWorker<P>,
|
||||
blocking: Arc<crate::runtime::blocking::Pool>,
|
||||
) -> (Arc<Set<P::Unpark>>, Vec<Worker<P>>)
|
||||
blocking_pool: blocking::Spawner,
|
||||
around_worker: Callback,
|
||||
shutdown_tx: shutdown::Sender,
|
||||
) -> (Arc<slice::Set<P::Unpark>>, Vec<Worker<P>>)
|
||||
where
|
||||
P: Send + Park,
|
||||
F: FnMut(usize) -> Box<P>,
|
||||
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<P> Worker<P>
|
||||
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<Set<P::Unpark>>,
|
||||
// Safe as aquiring a lock is required before doing anything potentially
|
||||
// dangerous.
|
||||
pub(super) fn new(
|
||||
slices: Arc<slice::Set<P::Unpark>>,
|
||||
index: usize,
|
||||
park: Box<P>,
|
||||
launch_worker: LaunchWorker<P>,
|
||||
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<Unpark = Box<dyn Unpark>>,
|
||||
{
|
||||
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<Option<*const dyn Fn()>>);
|
||||
|
||||
// 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<Option<*mut dyn FnMut()>>);
|
||||
|
||||
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<Set>` 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<Set>`
|
||||
// (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<GenerationGuard<'_, P>> {
|
||||
// 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<F, R>(&self, f: F) -> R
|
||||
/// Enter an in-place blocking section
|
||||
fn block_in_place(&self)
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
P: Park<Unpark = Box<dyn Unpark>>,
|
||||
{
|
||||
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<slice::Set>` 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<P: 'static> {
|
||||
pool: Arc<Set<P>>,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl<P> Entry<P>
|
||||
impl<P> 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<Set<P>>, index: usize) -> Self {
|
||||
Entry { pool, index }
|
||||
}
|
||||
fn run(self) -> Result<(), WorkerGone> {
|
||||
let mut me = self;
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
park: &mut impl Park<Unpark = P>,
|
||||
gone: &Cell<bool>,
|
||||
) -> 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<Unpark = P>,
|
||||
gone: &Cell<bool>,
|
||||
) -> Result<bool, WorkerGone> {
|
||||
// Process all pending tasks in the local queue.
|
||||
if !self.process_local_queue(park, gone)? {
|
||||
return Ok(false);
|
||||
}
|
||||
fn process_available_work(self) -> Result<Self, WorkerGone> {
|
||||
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<Unpark = P>,
|
||||
gone: &Cell<bool>,
|
||||
) -> Result<bool, WorkerGone> {
|
||||
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<Task<Shared<P>>> {
|
||||
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<Task<Shared<P::Unpark>>> {
|
||||
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<Task<Shared<P::Unpark>>> {
|
||||
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<bool>) -> Result<bool, WorkerGone> {
|
||||
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<Shared<P>>, gone: &Cell<bool>) -> 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<Shared<P::Unpark>>) -> Result<Self, WorkerGone> {
|
||||
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<Unpark = P>) {
|
||||
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<Unpark = P>) {
|
||||
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<Unpark = P>) {
|
||||
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<P> {
|
||||
&self.pool
|
||||
fn index(&self) -> usize {
|
||||
self.worker.index
|
||||
}
|
||||
|
||||
fn shared(&self) -> &Shared<P> {
|
||||
&self.set().shared()[self.index]
|
||||
fn slices(&self) -> &slice::Set<P::Unpark> {
|
||||
&self.worker.slices
|
||||
}
|
||||
|
||||
fn owned(&mut self) -> &Owned<P> {
|
||||
fn shared(&self) -> &Shared<P::Unpark> {
|
||||
&self.slices().shared()[self.index()]
|
||||
}
|
||||
|
||||
fn owned(&self) -> &Owned<P::Unpark> {
|
||||
let index = self.index();
|
||||
// safety: we own the slot
|
||||
unsafe { &*self.set().owned()[self.index].get() }
|
||||
}
|
||||
}
|
||||
|
||||
struct DropNotGone<'a, T> {
|
||||
gone: &'a Cell<bool>,
|
||||
inner: Option<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> DropNotGone<'a, T> {
|
||||
fn new(inner: T, gone: &'a Cell<bool>) -> 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) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<io::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<F, R>(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<F, R>(_handler: &Handle, _clock: &Clock, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
f()
|
||||
}
|
||||
}
|
||||
@@ -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<io::Driver>;
|
||||
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) {}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<JoinError> 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",
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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<T> {
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for JoinHandle<T> {}
|
||||
unsafe impl<T: Send> Sync for JoinHandle<T> {}
|
||||
|
||||
impl<T> JoinHandle<T> {
|
||||
pub(super) fn new(raw: RawTask) -> JoinHandle<T> {
|
||||
JoinHandle {
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::runtime::task::{Header, Task};
|
||||
use crate::task::{Header, Task};
|
||||
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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};
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::runtime::task::{Schedule, Task};
|
||||
use crate::task::{Schedule, Task};
|
||||
|
||||
use loom::sync::Notify;
|
||||
use std::collections::VecDeque;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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<F, R>(&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<Inner>,
|
||||
}
|
||||
|
||||
#[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<Option<Duration>>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current clock
|
||||
static CLOCK: Cell<Option<*const Clock>> = 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<F, R>(&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<Option<*const Clock>>);
|
||||
|
||||
impl Drop for Reset<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(cell);
|
||||
|
||||
cell.set(Some(self as *const Clock));
|
||||
|
||||
f()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Arc<dyn Now>>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Thread-local tracking the current clock
|
||||
static CLOCK: Cell<Option<*const Clock>> = Cell::new(None)
|
||||
}
|
||||
|
||||
/// Returns an `Instant` corresponding to "now".
|
||||
///
|
||||
/// This function delegates to the source of time configured for the current
|
||||
/// execution context. By default, this is `Instant::now()`.
|
||||
///
|
||||
/// Note that, because the source of time is configurable, it is possible to
|
||||
/// observe non-monotonic behavior when calling `now` from different
|
||||
/// executors.
|
||||
///
|
||||
/// See [module](index.html) level documentation for more details.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use tokio::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<Now>)"
|
||||
} else {
|
||||
&"None"
|
||||
}
|
||||
})
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the default clock for the duration of the closure.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if there already is a default clock set.
|
||||
pub fn with_default<F, R>(clock: &Clock, 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<Option<*const Clock>>);
|
||||
|
||||
impl Drop for Reset<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(None);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = Reset(cell);
|
||||
|
||||
cell.set(Some(clock as *const Clock));
|
||||
|
||||
f()
|
||||
})
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<T> {
|
||||
future: T,
|
||||
delay: Delay,
|
||||
}
|
||||
|
||||
#[deprecated(since = "0.2.6", note = "use Timeout instead")]
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct DeadlineError<T>(Kind<T>);
|
||||
|
||||
/// Deadline error variants
|
||||
#[derive(Debug)]
|
||||
enum Kind<T> {
|
||||
/// Inner future returned an error
|
||||
Inner(T),
|
||||
|
||||
/// The deadline elapsed.
|
||||
Elapsed,
|
||||
|
||||
/// Timer returned an error.
|
||||
Timer(crate::Error),
|
||||
}
|
||||
|
||||
impl<T> Deadline<T> {
|
||||
/// Create a new `Deadline` that completes when `future` completes or when
|
||||
/// `deadline` is reached.
|
||||
pub fn new(future: T, deadline: Instant) -> Deadline<T> {
|
||||
Deadline::new_with_delay(future, Delay::new(deadline))
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_delay(future: T, delay: Delay) -> Deadline<T> {
|
||||
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<T> Future for Deadline<T>
|
||||
where
|
||||
T: Future,
|
||||
{
|
||||
type Item = T::Item;
|
||||
type Error = DeadlineError<T::Error>;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
// 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<T> DeadlineError<T> {
|
||||
/// Create a new `DeadlineError` representing the inner future completing
|
||||
/// with `Err`.
|
||||
pub fn inner(err: T) -> DeadlineError<T> {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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<T> {
|
||||
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<crate::Error> {
|
||||
match self.0 {
|
||||
Kind::Timer(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: error::Error> error::Error for DeadlineError<T> {}
|
||||
|
||||
impl<T: fmt::Display> fmt::Display for DeadlineError<T> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-13
@@ -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()
|
||||
|
||||
@@ -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<T> {
|
||||
/// Handle to the timer driving the `DelayQueue`
|
||||
handle: Handle,
|
||||
|
||||
/// Stores data associated with entries
|
||||
slab: Slab<Data<T>>,
|
||||
|
||||
@@ -224,31 +218,6 @@ impl<T> DelayQueue<T> {
|
||||
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<u32> = DelayQueue::with_capacity_and_handle(0, &handle);
|
||||
/// ```
|
||||
pub fn with_capacity_and_handle(capacity: usize, handle: &Handle) -> DelayQueue<T> {
|
||||
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<T> DelayQueue<T> {
|
||||
/// delay_queue.insert(11, Duration::from_secs(11));
|
||||
/// ```
|
||||
pub fn with_capacity(capacity: usize) -> DelayQueue<T> {
|
||||
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<T> DelayQueue<T> {
|
||||
/// 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<T> DelayQueue<T> {
|
||||
};
|
||||
|
||||
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<T> DelayQueue<T> {
|
||||
/// [`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<T> DelayQueue<T> {
|
||||
/// 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<T> DelayQueue<T> {
|
||||
/// // "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<T> DelayQueue<T> {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::time::timer::Entry;
|
||||
use crate::time::driver::Entry;
|
||||
use crate::time::Error;
|
||||
|
||||
use std::ptr;
|
||||
@@ -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.
|
||||
@@ -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<HandlePriv>,
|
||||
}
|
||||
|
||||
/// Like `Handle` but never `None`.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct HandlePriv {
|
||||
inner: Weak<Inner>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Tracks the timer for the current execution context.
|
||||
static CURRENT_TIMER: RefCell<Option<HandlePriv>> = RefCell::new(None)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
///Unsets default timer handler on drop.
|
||||
pub(crate) struct DefaultGuard<'a> {
|
||||
prev: Option<HandlePriv>,
|
||||
_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<Inner>) -> 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<HandlePriv, Error> {
|
||||
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<Arc<Inner>> {
|
||||
self.inner.upgrade()
|
||||
}
|
||||
|
||||
/// Consume the handle, returning the weak Inner ref.
|
||||
pub(crate) fn into_inner(self) -> Weak<Inner> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HandlePriv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "HandlePriv")
|
||||
}
|
||||
}
|
||||
@@ -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<T> {
|
||||
pub(crate) struct Driver<T> {
|
||||
/// Shared state
|
||||
inner: Arc<Inner>,
|
||||
|
||||
/// Timer wheel
|
||||
wheel: wheel::Wheel<Stack>,
|
||||
|
||||
/// 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<T> Timer<T>
|
||||
impl<T> Driver<T>
|
||||
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<T> Timer<T> {
|
||||
/// 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<T> Timer<T>
|
||||
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<T> {
|
||||
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<Duration>) -> Result<Turn, T::Error> {
|
||||
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<T> Park for Timer<T>
|
||||
impl<T> Park for Driver<T>
|
||||
where
|
||||
T: Park,
|
||||
{
|
||||
@@ -393,7 +293,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Timer<T> {
|
||||
impl<T> Drop for Driver<T> {
|
||||
fn drop(&mut self) {
|
||||
use std::u64;
|
||||
|
||||
@@ -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;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::time::timer::Entry;
|
||||
use crate::time::driver::Entry;
|
||||
use crate::time::wheel;
|
||||
|
||||
use std::ptr;
|
||||
@@ -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<Duration> {
|
||||
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<Instant> {
|
||||
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<Instant> {
|
||||
self.std.checked_sub(duration).map(Instant::from_std)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn add(self, other: Duration) -> Instant {
|
||||
Instant::from_std(self.std + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign<Duration> 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<Duration> for Instant {
|
||||
type Output = Instant;
|
||||
|
||||
fn sub(self, rhs: Duration) -> Instant {
|
||||
Instant::from_std(self.std - rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::SubAssign<Duration> 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()
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
+23
-17
@@ -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 {
|
||||
|
||||
@@ -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, R>(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<MockPark>,
|
||||
time: MockTime,
|
||||
clock: Clock,
|
||||
}
|
||||
|
||||
type Inner = Arc<Mutex<State>>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct MockTime {
|
||||
inner: Inner,
|
||||
_pd: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockNow {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockPark {
|
||||
inner: Inner,
|
||||
_pd: PhantomData<Rc<()>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MockUnpark {
|
||||
inner: Inner,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
clock: Clock,
|
||||
unparked: bool,
|
||||
park_for: Option<Duration>,
|
||||
}
|
||||
|
||||
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<F, R>(&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<MockPark>, 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod mock_clock;
|
||||
|
||||
mod test_delay;
|
||||
mod test_queue;
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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<T> {
|
||||
delay: Delay,
|
||||
/// `None` when duration is zero.
|
||||
delay: Option<Delay>,
|
||||
|
||||
/// Set to true when `delay` has returned ready, but `stream` hasn't.
|
||||
has_delayed: bool,
|
||||
|
||||
/// The stream to throttle
|
||||
stream: T,
|
||||
}
|
||||
|
||||
impl<T> Throttle<T> {
|
||||
/// 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<T: Stream> Stream for Throttle<T> {
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
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<T: Stream> Stream for Throttle<T> {
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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<HandlePriv>,
|
||||
}
|
||||
|
||||
/// Like `Handle` but never `None`.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct HandlePriv {
|
||||
inner: Weak<Inner>,
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
/// Tracks the timer for the current execution context.
|
||||
static CURRENT_TIMER: RefCell<Option<HandlePriv>> = 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<Inner>) -> 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<T>(&self, value: T, timeout: Duration) -> Timeout<T> {
|
||||
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<HandlePriv, Error> {
|
||||
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<Arc<Inner>> {
|
||||
self.inner.upgrade()
|
||||
}
|
||||
|
||||
/// Consume the handle, returning the weak Inner ref.
|
||||
pub(crate) fn into_inner(self) -> Weak<Inner> {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HandlePriv {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "HandlePriv")
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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());
|
||||
});
|
||||
}
|
||||
@@ -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(()));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
})
|
||||
|
||||
@@ -32,13 +32,13 @@ where
|
||||
}
|
||||
|
||||
impl<T> Future for Blocking<T> {
|
||||
type Output = T;
|
||||
type Output = Result<T, io::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
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<T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
run(f).await
|
||||
run(f).await?
|
||||
}
|
||||
|
||||
pub(crate) fn len() -> usize {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user