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:
Carl Lerche
2019-11-12 15:23:40 -08:00
committed by GitHub
parent e3df2eafd3
commit 27e5b41067
109 changed files with 2632 additions and 3089 deletions
+1 -1
View File
@@ -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 }}
+1 -1
View File
@@ -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"
-277
View File
@@ -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()
}
}
+5 -9
View File
@@ -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");
}
-1
View File
@@ -13,7 +13,6 @@
//! Tokio and Futures based testing utilites
pub mod clock;
pub mod io;
mod macros;
pub mod task;
+1 -3
View File
@@ -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 }));
-25
View File
@@ -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());
});
}
+1 -3
View File
@@ -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
View File
@@ -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 }
+65
View File
@@ -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)
}
+291
View File
@@ -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());
}
+18
View File
@@ -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!();
}
}
+32
View File
@@ -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())
}
}
+3 -3
View File
@@ -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);
+5 -5
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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)]
+2 -2
View File
@@ -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
View File
@@ -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;
+1 -2
View File
@@ -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 _};
+45
View File
@@ -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()
}
}
}
-366
View File
@@ -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()));
}
+85 -84
View File
@@ -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 -1
View File
@@ -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;
+2
View File
@@ -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;
+3 -9
View File
@@ -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: &current_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: &current_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)
})
}
}
+9 -5
View File
@@ -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
View File
@@ -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
}
}
+2 -2
View File
@@ -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;
+3 -2
View File
@@ -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;
+79
View File
@@ -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) };
}
-33
View File
@@ -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),
_ => {}
}
}};
}
+3 -3
View File
@@ -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,
{
+10 -59
View File
@@ -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();
}
}
+10 -1
View File
@@ -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 -1
View File
@@ -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;
+15 -19
View File
@@ -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);
}
}
+5 -9
View File
@@ -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;
+8 -3
View File
@@ -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);
+2 -2
View File
@@ -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();
}
+347 -348
View File
@@ -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) }
}
}
+61
View File
@@ -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()
}
}
-41
View File
@@ -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;
+27
View File
@@ -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;
+32
View File
@@ -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;
+243
View File
@@ -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()
})
}
}
}
-149
View File
@@ -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()
})
}
-15
View File
@@ -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;
}
-162
View File
@@ -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
View File
@@ -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()
+15 -41
View File
@@ -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.
+108
View File
@@ -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;
+187
View File
@@ -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()
}
}
+2 -3
View File
@@ -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
View File
@@ -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 {
+211
View File
@@ -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;
}
}
}
+4
View File
@@ -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();
+22 -6
View File
@@ -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;
}
+1 -2
View File
@@ -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.
///
-187
View File
@@ -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")
}
}
-11
View File
@@ -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;
-67
View File
@@ -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());
});
}
+2 -2
View File
@@ -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(()));
});
});
+1 -1
View File
@@ -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();
})
+3 -3
View File
@@ -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