mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-08 00:00:13 +02:00
threadpool: update to std::future (#1219)
An initial pass at updating `tokio-threadpool` to `std::future`. The codebase and tests both now run using `std::future` but the wake mechanism is not ideal. Follow up work will be required to improve on this. Refs: #1200
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@ members = [
|
|||||||
# "tokio-signal",
|
# "tokio-signal",
|
||||||
"tokio-sync",
|
"tokio-sync",
|
||||||
"tokio-test",
|
"tokio-test",
|
||||||
# "tokio-threadpool",
|
"tokio-threadpool",
|
||||||
"tokio-timer",
|
"tokio-timer",
|
||||||
"tokio-tcp",
|
"tokio-tcp",
|
||||||
# "tokio-tls",
|
# "tokio-tls",
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tokio-executor = { version = "0.2.0", path = "../tokio-executor" }
|
tokio-executor = { version = "0.2.0", path = "../tokio-executor" }
|
||||||
futures = "0.1.19"
|
tokio-sync = { version = "0.2.0", path = "../tokio-sync" }
|
||||||
|
|
||||||
|
arc-waker = { git = "https://github.com/tokio-rs/async" }
|
||||||
crossbeam-deque = "0.7.0"
|
crossbeam-deque = "0.7.0"
|
||||||
crossbeam-queue = "0.1.0"
|
crossbeam-queue = "0.1.0"
|
||||||
crossbeam-utils = "0.6.4"
|
crossbeam-utils = "0.6.4"
|
||||||
@@ -35,7 +37,6 @@ log = "0.4"
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.5"
|
env_logger = "0.5"
|
||||||
|
async-util = { git = "https://github.com/tokio-rs/async" }
|
||||||
# For comparison benchmarks
|
tokio = { version = "0.2.0", path = "../tokio" }
|
||||||
futures-cpupool = "0.1.7"
|
tokio-test = { version = "0.2.0", path = "../tokio-test" }
|
||||||
threadpool = "1.7.1"
|
|
||||||
|
|||||||
@@ -5,43 +5,6 @@ threads.
|
|||||||
|
|
||||||
[Documentation](https://docs.rs/tokio-threadpool/0.1.14/tokio_threadpool)
|
[Documentation](https://docs.rs/tokio-threadpool/0.1.14/tokio_threadpool)
|
||||||
|
|
||||||
### Why not Rayon?
|
|
||||||
|
|
||||||
Rayon is designed to handle parallelizing single computations by breaking them
|
|
||||||
into smaller chunks. The scheduling for each individual chunk doesn't matter as
|
|
||||||
long as the root computation completes in a timely fashion. In other words,
|
|
||||||
Rayon does not provide any guarantees of fairness with regards to how each task
|
|
||||||
gets scheduled.
|
|
||||||
|
|
||||||
On the other hand, `tokio-threadpool` is a general purpose scheduler and
|
|
||||||
attempts to schedule each task fairly. This is the ideal behavior when
|
|
||||||
scheduling a set of unrelated tasks.
|
|
||||||
|
|
||||||
### Why not futures-cpupool?
|
|
||||||
|
|
||||||
It's 10x slower.
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use tokio_threadpool::ThreadPool;
|
|
||||||
use futures::{Future, lazy};
|
|
||||||
use futures::sync::oneshot;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let pool = ThreadPool::new();
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
|
|
||||||
pool.spawn(lazy(|| {
|
|
||||||
println!("Running on the pool");
|
|
||||||
tx.send("complete").map_err(|e| println!("send error, {}", e))
|
|
||||||
}));
|
|
||||||
|
|
||||||
println!("Result: {:?}", rx.wait());
|
|
||||||
pool.shutdown().wait().unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the [MIT license](LICENSE).
|
This project is licensed under the [MIT license](LICENSE).
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::worker::Worker;
|
use crate::worker::Worker;
|
||||||
use futures::{try_ready, Poll};
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
/// Error raised by `blocking`.
|
/// Error raised by `blocking`.
|
||||||
pub struct BlockingError {
|
pub struct BlockingError {
|
||||||
@@ -116,7 +117,7 @@ pub struct BlockingError {
|
|||||||
/// pool.shutdown_on_idle().wait().unwrap();
|
/// pool.shutdown_on_idle().wait().unwrap();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn blocking<F, T>(f: F) -> Poll<T, BlockingError>
|
pub fn blocking<F, T>(f: F) -> Poll<Result<T, BlockingError>>
|
||||||
where
|
where
|
||||||
F: FnOnce() -> T,
|
F: FnOnce() -> T,
|
||||||
{
|
{
|
||||||
@@ -124,7 +125,7 @@ where
|
|||||||
let worker = match worker {
|
let worker = match worker {
|
||||||
Some(worker) => worker,
|
Some(worker) => worker,
|
||||||
None => {
|
None => {
|
||||||
return Err(BlockingError { _p: () });
|
return Poll::Ready(Err(BlockingError { _p: () }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
// If the transition cannot happen, exit early
|
// If the transition cannot happen, exit early
|
||||||
try_ready!(res);
|
ready!(res)?;
|
||||||
|
|
||||||
// Currently in blocking mode, so call the inner closure
|
// Currently in blocking mode, so call the inner closure
|
||||||
let ret = f();
|
let ret = f();
|
||||||
@@ -148,7 +149,7 @@ where
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Return the result
|
// Return the result
|
||||||
Ok(ret.into())
|
Poll::Ready(Ok(ret))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BlockingError {
|
impl fmt::Display for BlockingError {
|
||||||
|
|||||||
@@ -131,21 +131,30 @@
|
|||||||
|
|
||||||
pub mod park;
|
pub mod park;
|
||||||
|
|
||||||
|
macro_rules! ready {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
::std::task::Poll::Ready(t) => t,
|
||||||
|
::std::task::Poll::Pending => return ::std::task::Poll::Pending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mod blocking;
|
mod blocking;
|
||||||
mod builder;
|
mod builder;
|
||||||
mod callback;
|
mod callback;
|
||||||
mod config;
|
mod config;
|
||||||
mod notifier;
|
|
||||||
mod pool;
|
mod pool;
|
||||||
mod sender;
|
mod sender;
|
||||||
mod shutdown;
|
mod shutdown;
|
||||||
mod task;
|
mod task;
|
||||||
mod thread_pool;
|
mod thread_pool;
|
||||||
|
mod waker;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
pub use crate::blocking::{blocking, BlockingError};
|
pub use crate::blocking::{blocking, BlockingError};
|
||||||
pub use crate::builder::Builder;
|
pub use crate::builder::Builder;
|
||||||
pub use crate::sender::Sender;
|
pub use crate::sender::Sender;
|
||||||
pub use crate::shutdown::Shutdown;
|
pub use crate::shutdown::Shutdown;
|
||||||
pub use crate::thread_pool::{SpawnHandle, ThreadPool};
|
pub use crate::thread_pool::ThreadPool;
|
||||||
pub use crate::worker::{Worker, WorkerId};
|
pub use crate::worker::{Worker, WorkerId};
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
use crate::pool::Pool;
|
|
||||||
use crate::task::Task;
|
|
||||||
use futures::executor::Notify;
|
|
||||||
use log::trace;
|
|
||||||
use std::mem;
|
|
||||||
use std::ops;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Implements the future `Notify` API.
|
|
||||||
///
|
|
||||||
/// This is how external events are able to signal the task, informing it to try
|
|
||||||
/// to poll the future again.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Notifier {
|
|
||||||
pub pool: Arc<Pool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A guard that ensures that the inner value gets forgotten.
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Forget<T>(Option<T>);
|
|
||||||
|
|
||||||
impl Notify for Notifier {
|
|
||||||
fn notify(&self, id: usize) {
|
|
||||||
trace!("Notifier::notify; id=0x{:x}", id);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let ptr = id as *const Task;
|
|
||||||
|
|
||||||
// We did not actually take ownership of the `Arc` in this function
|
|
||||||
// so we must ensure that the Arc is forgotten.
|
|
||||||
let task = Forget::new(Arc::from_raw(ptr));
|
|
||||||
|
|
||||||
// TODO: Unify this with Task::notify
|
|
||||||
if task.schedule() {
|
|
||||||
// TODO: Check if the pool is still running
|
|
||||||
//
|
|
||||||
// Bump the ref count
|
|
||||||
let task = task.clone();
|
|
||||||
|
|
||||||
let _ = self.pool.submit(task, &self.pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_id(&self, id: usize) -> usize {
|
|
||||||
let ptr = id as *const Task;
|
|
||||||
|
|
||||||
// This function doesn't actually get a strong ref to the task here.
|
|
||||||
// However, the only method we have to convert a raw pointer -> &Arc<T>
|
|
||||||
// is to call `Arc::from_raw` which returns a strong ref. So, to
|
|
||||||
// maintain the invariants, `t1` has to be forgotten. This prevents the
|
|
||||||
// ref count from being decremented.
|
|
||||||
let t1 = Forget::new(unsafe { Arc::from_raw(ptr) });
|
|
||||||
|
|
||||||
// The clone is forgotten so that the fn exits without decrementing the ref
|
|
||||||
// count. The caller of `clone_id` ensures that `drop_id` is called when
|
|
||||||
// the ref count needs to be decremented.
|
|
||||||
let _ = Forget::new(t1.clone());
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drop_id(&self, id: usize) {
|
|
||||||
unsafe {
|
|
||||||
let ptr = id as *const Task;
|
|
||||||
let _ = Arc::from_raw(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== impl Forget =====
|
|
||||||
|
|
||||||
impl<T> Forget<T> {
|
|
||||||
fn new(t: T) -> Self {
|
|
||||||
Forget(Some(t))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ops::Deref for Forget<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
self.0.as_ref().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for Forget<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
mem::forget(self.0.take());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ use crate::task::{Blocking, Task};
|
|||||||
use crate::worker::{self, Worker, WorkerId};
|
use crate::worker::{self, Worker, WorkerId};
|
||||||
use crossbeam_deque::Injector;
|
use crossbeam_deque::Injector;
|
||||||
use crossbeam_utils::CachePadded;
|
use crossbeam_utils::CachePadded;
|
||||||
use futures::Poll;
|
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use rand;
|
use rand;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
@@ -23,6 +23,7 @@ use std::num::Wrapping;
|
|||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use std::task::Poll;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -219,7 +220,10 @@ impl Pool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_blocking_capacity(&self, task: &Arc<Task>) -> Poll<(), crate::BlockingError> {
|
pub fn poll_blocking_capacity(
|
||||||
|
&self,
|
||||||
|
task: &Arc<Task>,
|
||||||
|
) -> Poll<Result<(), crate::BlockingError>> {
|
||||||
self.blocking.poll_blocking_capacity(task)
|
self.blocking.poll_blocking_capacity(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
use crate::pool::{self, Lifecycle, Pool, MAX_FUTURES};
|
use crate::pool::{self, Lifecycle, Pool, MAX_FUTURES};
|
||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
use futures::{future, Future};
|
|
||||||
|
use tokio_executor::{self, SpawnError};
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_executor::{self, SpawnError};
|
|
||||||
|
|
||||||
/// Submit futures to the associated thread pool for execution.
|
/// Submit futures to the associated thread pool for execution.
|
||||||
///
|
///
|
||||||
@@ -75,10 +78,10 @@ impl Sender {
|
|||||||
/// ```
|
/// ```
|
||||||
pub fn spawn<F>(&self, future: F) -> Result<(), SpawnError>
|
pub fn spawn<F>(&self, future: F) -> Result<(), SpawnError>
|
||||||
where
|
where
|
||||||
F: Future<Item = (), Error = ()> + Send + 'static,
|
F: Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
let mut s = self;
|
let mut s = self;
|
||||||
tokio_executor::Executor::spawn(&mut s, Box::new(future))
|
tokio_executor::Executor::spawn(&mut s, Box::pin(future))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logic to prepare for spawning
|
/// Logic to prepare for spawning
|
||||||
@@ -128,7 +131,7 @@ impl tokio_executor::Executor for Sender {
|
|||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
&mut self,
|
&mut self,
|
||||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||||
) -> Result<(), SpawnError> {
|
) -> Result<(), SpawnError> {
|
||||||
let mut s = &*self;
|
let mut s = &*self;
|
||||||
tokio_executor::Executor::spawn(&mut s, future)
|
tokio_executor::Executor::spawn(&mut s, future)
|
||||||
@@ -154,7 +157,7 @@ impl<'a> tokio_executor::Executor for &'a Sender {
|
|||||||
|
|
||||||
fn spawn(
|
fn spawn(
|
||||||
&mut self,
|
&mut self,
|
||||||
future: Box<dyn Future<Item = (), Error = ()> + Send>,
|
future: Pin<Box<dyn Future<Output = ()> + Send>>,
|
||||||
) -> Result<(), SpawnError> {
|
) -> Result<(), SpawnError> {
|
||||||
self.prepare_for_spawn()?;
|
self.prepare_for_spawn()?;
|
||||||
|
|
||||||
@@ -175,34 +178,14 @@ impl<'a> tokio_executor::Executor for &'a Sender {
|
|||||||
|
|
||||||
impl<T> tokio_executor::TypedExecutor<T> for Sender
|
impl<T> tokio_executor::TypedExecutor<T> for Sender
|
||||||
where
|
where
|
||||||
T: Future<Item = (), Error = ()> + Send + 'static,
|
T: Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
fn status(&self) -> Result<(), tokio_executor::SpawnError> {
|
fn status(&self) -> Result<(), tokio_executor::SpawnError> {
|
||||||
tokio_executor::Executor::status(self)
|
tokio_executor::Executor::status(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
fn spawn(&mut self, future: T) -> Result<(), SpawnError> {
|
||||||
tokio_executor::Executor::spawn(self, Box::new(future))
|
tokio_executor::Executor::spawn(self, Box::pin(future))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> future::Executor<T> for Sender
|
|
||||||
where
|
|
||||||
T: Future<Item = (), Error = ()> + Send + 'static,
|
|
||||||
{
|
|
||||||
fn execute(&self, future: T) -> Result<(), future::ExecuteError<T>> {
|
|
||||||
if let Err(e) = tokio_executor::Executor::status(self) {
|
|
||||||
let kind = if e.is_at_capacity() {
|
|
||||||
future::ExecuteErrorKind::NoCapacity
|
|
||||||
} else {
|
|
||||||
future::ExecuteErrorKind::Shutdown
|
|
||||||
};
|
|
||||||
|
|
||||||
return Err(future::ExecuteError::new(kind, future));
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = self.spawn(future);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
use crate::worker;
|
use crate::worker;
|
||||||
|
|
||||||
|
use tokio_sync::task::AtomicWaker;
|
||||||
|
|
||||||
use crossbeam_deque::Injector;
|
use crossbeam_deque::Injector;
|
||||||
use futures::task::AtomicTask;
|
use std::future::Future;
|
||||||
use futures::{Async, Future, Poll};
|
use std::pin::Pin;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
/// Future that resolves when the thread pool is shutdown.
|
/// Future that resolves when the thread pool is shutdown.
|
||||||
///
|
///
|
||||||
@@ -27,7 +31,7 @@ pub struct Shutdown {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Inner {
|
struct Inner {
|
||||||
/// The task to notify when the threadpool completes the shutdown process.
|
/// The task to notify when the threadpool completes the shutdown process.
|
||||||
task: AtomicTask,
|
task: AtomicWaker,
|
||||||
/// `true` if the threadpool has been shut down.
|
/// `true` if the threadpool has been shut down.
|
||||||
completed: bool,
|
completed: bool,
|
||||||
}
|
}
|
||||||
@@ -38,20 +42,25 @@ impl Shutdown {
|
|||||||
inner: trigger.inner.clone(),
|
inner: trigger.inner.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Wait for the shutdown to complete
|
||||||
|
pub fn wait(self) {
|
||||||
|
let mut enter = tokio_executor::enter().unwrap();
|
||||||
|
enter.block_on(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for Shutdown {
|
impl Future for Shutdown {
|
||||||
type Item = ();
|
type Output = ();
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), ()> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
|
|
||||||
if !inner.completed {
|
if !inner.completed {
|
||||||
inner.task.register();
|
inner.task.register_by_ref(cx.waker());
|
||||||
Ok(Async::NotReady)
|
Poll::Pending
|
||||||
} else {
|
} else {
|
||||||
Ok(().into())
|
Poll::Ready(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +83,7 @@ impl ShutdownTrigger {
|
|||||||
) -> ShutdownTrigger {
|
) -> ShutdownTrigger {
|
||||||
ShutdownTrigger {
|
ShutdownTrigger {
|
||||||
inner: Arc::new(Mutex::new(Inner {
|
inner: Arc::new(Mutex::new(Inner {
|
||||||
task: AtomicTask::new(),
|
task: AtomicWaker::new(),
|
||||||
completed: false,
|
completed: false,
|
||||||
})),
|
})),
|
||||||
workers,
|
workers,
|
||||||
@@ -96,6 +105,6 @@ impl Drop for ShutdownTrigger {
|
|||||||
// Notify the task interested in shutdown.
|
// Notify the task interested in shutdown.
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
inner.completed = true;
|
inner.completed = true;
|
||||||
inner.task.notify();
|
inner.task.wake();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::pool::Pool;
|
use crate::pool::Pool;
|
||||||
use crate::task::{BlockingState, Task};
|
use crate::task::{BlockingState, Task};
|
||||||
use futures::{Async, Poll};
|
|
||||||
use std::cell::UnsafeCell;
|
use std::cell::UnsafeCell;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
|
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::Poll;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
/// Manages the state around entering a blocking section and tasks that are
|
/// Manages the state around entering a blocking section and tasks that are
|
||||||
@@ -109,7 +110,10 @@ impl Blocking {
|
|||||||
///
|
///
|
||||||
/// The caller must ensure that `task` has not previously been queued to be
|
/// The caller must ensure that `task` has not previously been queued to be
|
||||||
/// notified when capacity becomes available.
|
/// notified when capacity becomes available.
|
||||||
pub fn poll_blocking_capacity(&self, task: &Arc<Task>) -> Poll<(), crate::BlockingError> {
|
pub fn poll_blocking_capacity(
|
||||||
|
&self,
|
||||||
|
task: &Arc<Task>,
|
||||||
|
) -> Poll<Result<(), crate::BlockingError>> {
|
||||||
// This requires atomically claiming blocking capacity and if none is
|
// This requires atomically claiming blocking capacity and if none is
|
||||||
// available, queuing &task.
|
// available, queuing &task.
|
||||||
|
|
||||||
@@ -193,7 +197,7 @@ impl Blocking {
|
|||||||
|
|
||||||
// The node was queued to be notified once capacity is made
|
// The node was queued to be notified once capacity is made
|
||||||
// available.
|
// available.
|
||||||
Ok(Async::NotReady)
|
Poll::Pending
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
debug_assert!(curr.remaining_capacity() > 0);
|
debug_assert!(curr.remaining_capacity() > 0);
|
||||||
@@ -208,7 +212,7 @@ impl Blocking {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Capacity has been obtained
|
// Capacity has been obtained
|
||||||
Ok(().into())
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,17 @@ mod state;
|
|||||||
pub(crate) use self::blocking::{Blocking, CanBlock};
|
pub(crate) use self::blocking::{Blocking, CanBlock};
|
||||||
use self::blocking_state::BlockingState;
|
use self::blocking_state::BlockingState;
|
||||||
use self::state::State;
|
use self::state::State;
|
||||||
use crate::notifier::Notifier;
|
|
||||||
use crate::pool::Pool;
|
use crate::pool::Pool;
|
||||||
use futures::executor::{self, Spawn};
|
use crate::waker::Waker;
|
||||||
use futures::{self, Async, Future};
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
|
use std::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release};
|
||||||
use std::sync::atomic::{AtomicPtr, AtomicUsize};
|
use std::sync::atomic::{AtomicPtr, AtomicUsize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
use std::{fmt, panic, ptr};
|
use std::{fmt, panic, ptr};
|
||||||
|
|
||||||
/// Harness around a future.
|
/// Harness around a future.
|
||||||
@@ -48,7 +50,7 @@ pub(crate) struct Task {
|
|||||||
/// Store the future at the head of the struct
|
/// Store the future at the head of the struct
|
||||||
///
|
///
|
||||||
/// The future is dropped immediately when it transitions to Complete
|
/// The future is dropped immediately when it transitions to Complete
|
||||||
future: UnsafeCell<Option<Spawn<BoxFuture>>>,
|
future: UnsafeCell<Option<BoxFuture>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -58,31 +60,27 @@ pub(crate) enum Run {
|
|||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
||||||
type BoxFuture = Box<dyn Future<Item = (), Error = ()> + Send + 'static>;
|
type BoxFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
|
||||||
|
|
||||||
// ===== impl Task =====
|
// ===== impl Task =====
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
/// Create a new `Task` as a harness for `future`.
|
/// Create a new `Task` as a harness for `future`.
|
||||||
pub fn new(future: BoxFuture) -> Task {
|
pub fn new(future: BoxFuture) -> Task {
|
||||||
// Wrap the future with an execution context.
|
|
||||||
let task_fut = executor::spawn(future);
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
state: AtomicUsize::new(State::new().into()),
|
state: AtomicUsize::new(State::new().into()),
|
||||||
blocking: AtomicUsize::new(BlockingState::new().into()),
|
blocking: AtomicUsize::new(BlockingState::new().into()),
|
||||||
next_blocking: AtomicPtr::new(ptr::null_mut()),
|
next_blocking: AtomicPtr::new(ptr::null_mut()),
|
||||||
reg_worker: Cell::new(None),
|
reg_worker: Cell::new(None),
|
||||||
reg_index: Cell::new(0),
|
reg_index: Cell::new(0),
|
||||||
future: UnsafeCell::new(Some(task_fut)),
|
future: UnsafeCell::new(Some(future)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a fake `Task` to be used as part of the intrusive mpsc channel
|
/// Create a fake `Task` to be used as part of the intrusive mpsc channel
|
||||||
/// algorithm.
|
/// algorithm.
|
||||||
fn stub() -> Task {
|
fn stub() -> Task {
|
||||||
let future = Box::new(futures::empty()) as BoxFuture;
|
let future = Box::pin(Empty) as BoxFuture;
|
||||||
let task_fut = executor::spawn(future);
|
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
state: AtomicUsize::new(State::stub().into()),
|
state: AtomicUsize::new(State::stub().into()),
|
||||||
@@ -90,18 +88,18 @@ impl Task {
|
|||||||
next_blocking: AtomicPtr::new(ptr::null_mut()),
|
next_blocking: AtomicPtr::new(ptr::null_mut()),
|
||||||
reg_worker: Cell::new(None),
|
reg_worker: Cell::new(None),
|
||||||
reg_index: Cell::new(0),
|
reg_index: Cell::new(0),
|
||||||
future: UnsafeCell::new(Some(task_fut)),
|
future: UnsafeCell::new(Some(future)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the task returning `Run::Schedule` if the task needs to be
|
/// Execute the task returning `Run::Schedule` if the task needs to be
|
||||||
/// scheduled again.
|
/// scheduled again.
|
||||||
pub fn run(&self, unpark: &Arc<Notifier>) -> Run {
|
pub fn run(me: &Arc<Task>, pool: &Arc<Pool>) -> Run {
|
||||||
use self::State::*;
|
use self::State::*;
|
||||||
|
|
||||||
// Transition task to running state. At this point, the task must be
|
// Transition task to running state. At this point, the task must be
|
||||||
// scheduled.
|
// scheduled.
|
||||||
let actual: State = self
|
let actual: State = me
|
||||||
.state
|
.state
|
||||||
.compare_and_swap(Scheduled.into(), Running.into(), AcqRel)
|
.compare_and_swap(Scheduled.into(), Running.into(), AcqRel)
|
||||||
.into();
|
.into();
|
||||||
@@ -111,14 +109,11 @@ impl Task {
|
|||||||
_ => panic!("unexpected task state; {:?}", actual),
|
_ => panic!("unexpected task state; {:?}", actual),
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!(
|
trace!("Task::run; state={:?}", State::from(me.state.load(Relaxed)));
|
||||||
"Task::run; state={:?}",
|
|
||||||
State::from(self.state.load(Relaxed))
|
|
||||||
);
|
|
||||||
|
|
||||||
// The transition to `Running` done above ensures that a lock on the
|
// The transition to `Running` done above ensures that a lock on the
|
||||||
// future has been obtained.
|
// future has been obtained.
|
||||||
let fut = unsafe { &mut (*self.future.get()) };
|
let fut = unsafe { &mut (*me.future.get()) };
|
||||||
|
|
||||||
// This block deals with the future panicking while being polled.
|
// This block deals with the future panicking while being polled.
|
||||||
//
|
//
|
||||||
@@ -126,7 +121,7 @@ impl Task {
|
|||||||
// `thread::panicking() -> true`. To do this, the future is dropped from
|
// `thread::panicking() -> true`. To do this, the future is dropped from
|
||||||
// within the catch_unwind block.
|
// within the catch_unwind block.
|
||||||
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
let res = panic::catch_unwind(panic::AssertUnwindSafe(|| {
|
||||||
struct Guard<'a>(&'a mut Option<Spawn<BoxFuture>>, bool);
|
struct Guard<'a>(&'a mut Option<BoxFuture>, bool);
|
||||||
|
|
||||||
impl<'a> Drop for Guard<'a> {
|
impl<'a> Drop for Guard<'a> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
@@ -139,10 +134,14 @@ impl Task {
|
|||||||
|
|
||||||
let mut g = Guard(fut, true);
|
let mut g = Guard(fut, true);
|
||||||
|
|
||||||
let ret =
|
let mut waker = arc_waker::waker(Arc::new(Waker {
|
||||||
g.0.as_mut()
|
task: me.clone(),
|
||||||
.unwrap()
|
pool: pool.clone(),
|
||||||
.poll_future_notify(unpark, self as *const _ as usize);
|
}));
|
||||||
|
|
||||||
|
let mut cx = Context::from_waker(&mut waker);
|
||||||
|
|
||||||
|
let ret = g.0.as_mut().unwrap().as_mut().poll(&mut cx);
|
||||||
|
|
||||||
g.1 = false;
|
g.1 = false;
|
||||||
|
|
||||||
@@ -150,7 +149,7 @@ impl Task {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(Ok(Async::Ready(_))) | Ok(Err(_)) | Err(_) => {
|
Ok(Poll::Ready(_)) | Err(_) => {
|
||||||
trace!(" -> task complete");
|
trace!(" -> task complete");
|
||||||
|
|
||||||
// The future has completed. Drop it immediately to free
|
// The future has completed. Drop it immediately to free
|
||||||
@@ -158,20 +157,20 @@ impl Task {
|
|||||||
//
|
//
|
||||||
// The `Task` harness will stay around longer if it is contained
|
// The `Task` harness will stay around longer if it is contained
|
||||||
// by any of the various queues.
|
// by any of the various queues.
|
||||||
self.drop_future();
|
me.drop_future();
|
||||||
|
|
||||||
// Transition to the completed state
|
// Transition to the completed state
|
||||||
self.state.store(State::Complete.into(), Release);
|
me.state.store(State::Complete.into(), Release);
|
||||||
|
|
||||||
if let Err(panic_err) = res {
|
if let Err(panic_err) = res {
|
||||||
if let Some(ref f) = unpark.pool.config.panic_handler {
|
if let Some(ref f) = pool.config.panic_handler {
|
||||||
f(panic_err);
|
f(panic_err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Run::Complete
|
Run::Complete
|
||||||
}
|
}
|
||||||
Ok(Ok(Async::NotReady)) => {
|
Ok(Poll::Pending) => {
|
||||||
trace!(" -> not ready");
|
trace!(" -> not ready");
|
||||||
|
|
||||||
// Attempt to transition from Running -> Idle, if successful,
|
// Attempt to transition from Running -> Idle, if successful,
|
||||||
@@ -179,7 +178,7 @@ impl Task {
|
|||||||
// fails, then the task has been unparked concurrent to running,
|
// fails, then the task has been unparked concurrent to running,
|
||||||
// in which case it transitions immediately back to scheduled
|
// in which case it transitions immediately back to scheduled
|
||||||
// and we return `true`.
|
// and we return `true`.
|
||||||
let prev: State = self
|
let prev: State = me
|
||||||
.state
|
.state
|
||||||
.compare_and_swap(Running.into(), Idle.into(), AcqRel)
|
.compare_and_swap(Running.into(), Idle.into(), AcqRel)
|
||||||
.into();
|
.into();
|
||||||
@@ -187,7 +186,7 @@ impl Task {
|
|||||||
match prev {
|
match prev {
|
||||||
Running => Run::Idle,
|
Running => Run::Idle,
|
||||||
Notified => {
|
Notified => {
|
||||||
self.state.store(Scheduled.into(), Release);
|
me.state.store(Scheduled.into(), Release);
|
||||||
Run::Schedule
|
Run::Schedule
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
@@ -231,23 +230,23 @@ impl Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Notify the task
|
|
||||||
pub fn notify(me: Arc<Task>, pool: &Arc<Pool>) {
|
|
||||||
if me.schedule() {
|
|
||||||
let _ = pool.submit(me, pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Notify the task it has been allocated blocking capacity
|
/// Notify the task it has been allocated blocking capacity
|
||||||
pub fn notify_blocking(me: Arc<Task>, pool: &Arc<Pool>) {
|
pub fn notify_blocking(me: Arc<Task>, pool: &Arc<Pool>) {
|
||||||
BlockingState::notify_blocking(&me.blocking, AcqRel);
|
BlockingState::notify_blocking(&me.blocking, AcqRel);
|
||||||
Task::notify(me, pool);
|
Task::schedule(&me, pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schedule(me: &Arc<Self>, pool: &Arc<Pool>) {
|
||||||
|
if me.schedule2() {
|
||||||
|
let task = me.clone();
|
||||||
|
let _ = pool.submit(task, &pool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transition the task state to scheduled.
|
/// Transition the task state to scheduled.
|
||||||
///
|
///
|
||||||
/// Returns `true` if the caller is permitted to schedule the task.
|
/// Returns `true` if the caller is permitted to schedule the task.
|
||||||
pub fn schedule(&self) -> bool {
|
fn schedule2(&self) -> bool {
|
||||||
use self::State::*;
|
use self::State::*;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -300,7 +299,18 @@ impl fmt::Debug for Task {
|
|||||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
fmt.debug_struct("Task")
|
fmt.debug_struct("Task")
|
||||||
.field("state", &self.state)
|
.field("state", &self.state)
|
||||||
.field("future", &"Spawn<BoxFuture>")
|
.field("future", &"BoxFuture")
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Empty;
|
||||||
|
|
||||||
|
impl Future for Empty {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
|
||||||
|
// Never used
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use crate::builder::Builder;
|
|||||||
use crate::pool::Pool;
|
use crate::pool::Pool;
|
||||||
use crate::sender::Sender;
|
use crate::sender::Sender;
|
||||||
use crate::shutdown::{Shutdown, ShutdownTrigger};
|
use crate::shutdown::{Shutdown, ShutdownTrigger};
|
||||||
use futures::sync::oneshot;
|
|
||||||
use futures::{Future, Poll};
|
use std::future::Future;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Work-stealing based thread pool for executing futures.
|
/// Work-stealing based thread pool for executing futures.
|
||||||
@@ -72,11 +72,14 @@ impl ThreadPool {
|
|||||||
/// version that returns a `Result` instead of panicking.
|
/// version that returns a `Result` instead of panicking.
|
||||||
pub fn spawn<F>(&self, future: F)
|
pub fn spawn<F>(&self, future: F)
|
||||||
where
|
where
|
||||||
F: Future<Item = (), Error = ()> + Send + 'static,
|
F: Future<Output = ()> + Send + 'static,
|
||||||
{
|
{
|
||||||
self.sender().spawn(future).unwrap();
|
self.sender().spawn(future).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Bring back
|
||||||
|
|
||||||
/// Spawn a future on to the thread pool, return a future representing
|
/// Spawn a future on to the thread pool, return a future representing
|
||||||
/// the produced value.
|
/// the produced value.
|
||||||
///
|
///
|
||||||
@@ -114,6 +117,8 @@ impl ThreadPool {
|
|||||||
SpawnHandle(oneshot::spawn(future, self.sender()))
|
SpawnHandle(oneshot::spawn(future, self.sender()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
/// Return a reference to the sender handle
|
/// Return a reference to the sender handle
|
||||||
///
|
///
|
||||||
/// The handle is used to spawn futures onto the thread pool. It also
|
/// The handle is used to spawn futures onto the thread pool. It also
|
||||||
@@ -183,11 +188,15 @@ impl Drop for ThreadPool {
|
|||||||
drop(inner);
|
drop(inner);
|
||||||
|
|
||||||
// Wait until all worker threads terminate and the threadpool's resources clean up.
|
// Wait until all worker threads terminate and the threadpool's resources clean up.
|
||||||
let _ = shutdown.wait();
|
let mut enter = tokio_executor::enter().unwrap();
|
||||||
|
enter.block_on(shutdown);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO: Bring back
|
||||||
|
|
||||||
/// Handle returned from ThreadPool::spawn_handle.
|
/// Handle returned from ThreadPool::spawn_handle.
|
||||||
///
|
///
|
||||||
/// This handle is a future representing the completion of a different future
|
/// This handle is a future representing the completion of a different future
|
||||||
@@ -205,3 +214,5 @@ impl<T, E> Future for SpawnHandle<T, E> {
|
|||||||
self.0.poll()
|
self.0.poll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use crate::pool::Pool;
|
||||||
|
use crate::task::Task;
|
||||||
|
|
||||||
|
use arc_waker::Wake;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Implements the future `Waker` API.
|
||||||
|
///
|
||||||
|
/// This is how external events are able to signal the task, informing it to try
|
||||||
|
/// to poll the future again.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Waker {
|
||||||
|
pub pool: Arc<Pool>,
|
||||||
|
pub task: Arc<Task>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Waker {}
|
||||||
|
unsafe impl Sync for Waker {}
|
||||||
|
|
||||||
|
impl Wake for Waker {
|
||||||
|
fn wake_by_ref(me: &Arc<Self>) {
|
||||||
|
Task::schedule(&me.task, &me.pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,21 +6,22 @@ pub(crate) use self::entry::WorkerEntry as Entry;
|
|||||||
pub(crate) use self::stack::Stack;
|
pub(crate) use self::stack::Stack;
|
||||||
pub(crate) use self::state::{Lifecycle, State};
|
pub(crate) use self::state::{Lifecycle, State};
|
||||||
|
|
||||||
use crate::notifier::Notifier;
|
|
||||||
use crate::pool::{self, BackupId, Pool};
|
use crate::pool::{self, BackupId, Pool};
|
||||||
use crate::sender::Sender;
|
use crate::sender::Sender;
|
||||||
use crate::shutdown::ShutdownTrigger;
|
use crate::shutdown::ShutdownTrigger;
|
||||||
use crate::task::{self, CanBlock, Task};
|
use crate::task::{self, CanBlock, Task};
|
||||||
use futures::{Async, Poll};
|
|
||||||
|
use tokio_executor;
|
||||||
|
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
use std::sync::atomic::Ordering::{AcqRel, Acquire};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::task::Poll;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio_executor;
|
|
||||||
|
|
||||||
/// Thread worker
|
/// Thread worker
|
||||||
///
|
///
|
||||||
@@ -148,7 +149,7 @@ impl Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transition the current worker to a blocking worker
|
/// Transition the current worker to a blocking worker
|
||||||
pub(crate) fn transition_to_blocking(&self) -> Poll<(), crate::BlockingError> {
|
pub(crate) fn transition_to_blocking(&self) -> Poll<Result<(), crate::BlockingError>> {
|
||||||
use self::CanBlock::*;
|
use self::CanBlock::*;
|
||||||
|
|
||||||
// If we get this far, then `current_task` has been set.
|
// If we get this far, then `current_task` has been set.
|
||||||
@@ -161,7 +162,7 @@ impl Worker {
|
|||||||
|
|
||||||
// The task has already requested capacity to block, but there is
|
// The task has already requested capacity to block, but there is
|
||||||
// none yet available.
|
// none yet available.
|
||||||
NoCapacity => return Ok(Async::NotReady),
|
NoCapacity => return Poll::Pending,
|
||||||
|
|
||||||
// The task has yet to ask for capacity
|
// The task has yet to ask for capacity
|
||||||
CanRequest => {
|
CanRequest => {
|
||||||
@@ -169,12 +170,12 @@ impl Worker {
|
|||||||
// is available, register the task to be notified once capacity
|
// is available, register the task to be notified once capacity
|
||||||
// becomes available.
|
// becomes available.
|
||||||
match self.pool.poll_blocking_capacity(task_ref)? {
|
match self.pool.poll_blocking_capacity(task_ref)? {
|
||||||
Async::Ready(()) => {
|
Poll::Ready(()) => {
|
||||||
self.current_task.set_can_block(Allocated);
|
self.current_task.set_can_block(Allocated);
|
||||||
}
|
}
|
||||||
Async::NotReady => {
|
Poll::Pending => {
|
||||||
self.current_task.set_can_block(NoCapacity);
|
self.current_task.set_can_block(NoCapacity);
|
||||||
return Ok(Async::NotReady);
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,7 +188,7 @@ impl Worker {
|
|||||||
if self.is_blocking.get() {
|
if self.is_blocking.get() {
|
||||||
// The thread is already in blocking mode, so there is nothing else
|
// The thread is already in blocking mode, so there is nothing else
|
||||||
// to do. Return `Ready` and allow the caller to block the thread.
|
// to do. Return `Ready` and allow the caller to block the thread.
|
||||||
return Ok(().into());
|
return Poll::Ready(Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("transition to blocking state");
|
trace!("transition to blocking state");
|
||||||
@@ -200,7 +201,7 @@ impl Worker {
|
|||||||
// Track that the thread has now fully entered the blocking state.
|
// Track that the thread has now fully entered the blocking state.
|
||||||
self.is_blocking.set(true);
|
self.is_blocking.set(true);
|
||||||
|
|
||||||
Ok(().into())
|
Poll::Ready(Ok(()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transition from blocking
|
/// Transition from blocking
|
||||||
@@ -223,11 +224,6 @@ impl Worker {
|
|||||||
const MAX_SPINS: usize = 3;
|
const MAX_SPINS: usize = 3;
|
||||||
const LIGHT_SLEEP_INTERVAL: usize = 32;
|
const LIGHT_SLEEP_INTERVAL: usize = 32;
|
||||||
|
|
||||||
// Get the notifier.
|
|
||||||
let notify = Arc::new(Notifier {
|
|
||||||
pool: self.pool.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
let mut spin_cnt = 0;
|
let mut spin_cnt = 0;
|
||||||
let mut tick = 0;
|
let mut tick = 0;
|
||||||
@@ -236,7 +232,7 @@ impl Worker {
|
|||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
// Run the next available task
|
// Run the next available task
|
||||||
if self.try_run_task(¬ify) {
|
if self.try_run_task(&self.pool) {
|
||||||
if self.is_blocking.get() {
|
if self.is_blocking.get() {
|
||||||
// Exit out of the run state
|
// Exit out of the run state
|
||||||
return;
|
return;
|
||||||
@@ -291,12 +287,12 @@ impl Worker {
|
|||||||
///
|
///
|
||||||
/// Returns `true` if work was found.
|
/// Returns `true` if work was found.
|
||||||
#[inline]
|
#[inline]
|
||||||
fn try_run_task(&self, notify: &Arc<Notifier>) -> bool {
|
fn try_run_task(&self, pool: &Arc<Pool>) -> bool {
|
||||||
if self.try_run_owned_task(notify) {
|
if self.try_run_owned_task(pool) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.try_steal_task(notify)
|
self.try_steal_task(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks the worker's current state, updating it as needed.
|
/// Checks the worker's current state, updating it as needed.
|
||||||
@@ -381,11 +377,11 @@ impl Worker {
|
|||||||
/// Runs the next task on this worker's queue.
|
/// Runs the next task on this worker's queue.
|
||||||
///
|
///
|
||||||
/// Returns `true` if work was found.
|
/// Returns `true` if work was found.
|
||||||
fn try_run_owned_task(&self, notify: &Arc<Notifier>) -> bool {
|
fn try_run_owned_task(&self, pool: &Arc<Pool>) -> bool {
|
||||||
// Poll the internal queue for a task to run
|
// Poll the internal queue for a task to run
|
||||||
match self.entry().pop_task() {
|
match self.entry().pop_task() {
|
||||||
Some(task) => {
|
Some(task) => {
|
||||||
self.run_task(task, notify);
|
self.run_task(task, pool);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
@@ -395,7 +391,7 @@ impl Worker {
|
|||||||
/// Tries to steal a task from another worker.
|
/// Tries to steal a task from another worker.
|
||||||
///
|
///
|
||||||
/// Returns `true` if work was found
|
/// Returns `true` if work was found
|
||||||
fn try_steal_task(&self, notify: &Arc<Notifier>) -> bool {
|
fn try_steal_task(&self, pool: &Arc<Pool>) -> bool {
|
||||||
use crossbeam_deque::Steal;
|
use crossbeam_deque::Steal;
|
||||||
|
|
||||||
debug_assert!(!self.is_blocking.get());
|
debug_assert!(!self.is_blocking.get());
|
||||||
@@ -411,7 +407,7 @@ impl Worker {
|
|||||||
Steal::Success(task) => {
|
Steal::Success(task) => {
|
||||||
trace!("stole task from another worker");
|
trace!("stole task from another worker");
|
||||||
|
|
||||||
self.run_task(task, notify);
|
self.run_task(task, pool);
|
||||||
|
|
||||||
trace!(
|
trace!(
|
||||||
"try_steal_task -- signal_work; self={}; from={}",
|
"try_steal_task -- signal_work; self={}; from={}",
|
||||||
@@ -444,7 +440,7 @@ impl Worker {
|
|||||||
found_work
|
found_work
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_task(&self, task: Arc<Task>, notify: &Arc<Notifier>) {
|
fn run_task(&self, task: Arc<Task>, pool: &Arc<Pool>) {
|
||||||
use crate::task::Run::*;
|
use crate::task::Run::*;
|
||||||
|
|
||||||
// If this is the first time this task is being polled, register it so that we can keep
|
// If this is the first time this task is being polled, register it so that we can keep
|
||||||
@@ -454,7 +450,7 @@ impl Worker {
|
|||||||
self.entry().register_task(&task);
|
self.entry().register_task(&task);
|
||||||
}
|
}
|
||||||
|
|
||||||
let run = self.run_task2(&task, notify);
|
let run = self.run_task2(&task, pool);
|
||||||
|
|
||||||
// TODO: Try to claim back the worker state in case the backup thread
|
// TODO: Try to claim back the worker state in case the backup thread
|
||||||
// did not start up fast enough. This is a performance optimization.
|
// did not start up fast enough. This is a performance optimization.
|
||||||
@@ -528,7 +524,7 @@ impl Worker {
|
|||||||
///
|
///
|
||||||
/// Great care is needed to ensure that `current_task` is unset in this
|
/// Great care is needed to ensure that `current_task` is unset in this
|
||||||
/// function.
|
/// function.
|
||||||
fn run_task2(&self, task: &Arc<Task>, notify: &Arc<Notifier>) -> task::Run {
|
fn run_task2(&self, task: &Arc<Task>, pool: &Arc<Pool>) -> task::Run {
|
||||||
struct Guard<'a> {
|
struct Guard<'a> {
|
||||||
worker: &'a Worker,
|
worker: &'a Worker,
|
||||||
}
|
}
|
||||||
@@ -562,7 +558,7 @@ impl Worker {
|
|||||||
// function returns, even if the return is caused by a panic.
|
// function returns, even if the return is caused by a panic.
|
||||||
let _g = Guard { worker: self };
|
let _g = Guard { worker: self };
|
||||||
|
|
||||||
task.run(notify)
|
Task::run(task, pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Put the worker to sleep
|
/// Put the worker to sleep
|
||||||
|
|||||||
@@ -1,14 +1,26 @@
|
|||||||
#![deny(warnings, rust_2018_idioms)]
|
#![deny(/* warnings, */ rust_2018_idioms)]
|
||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
use futures::future::{lazy, poll_fn};
|
use tokio_test::*;
|
||||||
use futures::*;
|
use tokio_threadpool::*;
|
||||||
|
|
||||||
|
use async_util::future::poll_fn;
|
||||||
use rand::*;
|
use rand::*;
|
||||||
use std::sync::atomic::Ordering::*;
|
use std::sync::atomic::Ordering::*;
|
||||||
use std::sync::atomic::*;
|
use std::sync::atomic::*;
|
||||||
use std::sync::*;
|
use std::sync::*;
|
||||||
|
use std::task::{Poll, Waker};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio_threadpool::*;
|
|
||||||
|
macro_rules! ready {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
::std::task::Poll::Ready(t) => t,
|
||||||
|
::std::task::Poll::Pending => return ::std::task::Poll::Pending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic() {
|
fn basic() {
|
||||||
@@ -19,21 +31,18 @@ fn basic() {
|
|||||||
let (tx1, rx1) = mpsc::channel();
|
let (tx1, rx1) = mpsc::channel();
|
||||||
let (tx2, rx2) = mpsc::channel();
|
let (tx2, rx2) = mpsc::channel();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
let res = blocking(|| {
|
let res = blocking(|| {
|
||||||
let v = rx1.recv().unwrap();
|
let v = rx1.recv().unwrap();
|
||||||
tx2.send(v).unwrap();
|
tx2.send(v).unwrap();
|
||||||
})
|
});
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(res.is_ready());
|
assert_ready!(res).unwrap();
|
||||||
Ok(().into())
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
tx1.send(()).unwrap();
|
tx1.send(()).unwrap();
|
||||||
Ok(().into())
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
rx2.recv().unwrap();
|
rx2.recv().unwrap();
|
||||||
}
|
}
|
||||||
@@ -51,11 +60,11 @@ fn notify_task_on_capacity() {
|
|||||||
let rem = rem.clone();
|
let rem = rem.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
blocking(|| {
|
blocking(|| {
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
let prev = rem.fetch_sub(1, Relaxed);
|
let prev = rem.fetch_sub(1, SeqCst);
|
||||||
|
|
||||||
if prev == 1 {
|
if prev == 1 {
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
@@ -63,20 +72,19 @@ fn notify_task_on_capacity() {
|
|||||||
})
|
})
|
||||||
.map_err(|e| panic!("blocking err {:?}", e))
|
.map_err(|e| panic!("blocking err {:?}", e))
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
rx.recv().unwrap();
|
rx.recv().unwrap();
|
||||||
|
|
||||||
assert_eq!(0, rem.load(Relaxed));
|
assert_eq!(0, rem.load(SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn capacity_is_use_it_or_lose_it() {
|
fn capacity_is_use_it_or_lose_it() {
|
||||||
use futures::sync::oneshot;
|
use tokio_sync::oneshot;
|
||||||
use futures::task::Task;
|
|
||||||
use futures::Async::*;
|
|
||||||
use futures::*;
|
|
||||||
|
|
||||||
// TODO: Run w/ bigger pool size
|
// TODO: Run w/ bigger pool size
|
||||||
|
|
||||||
@@ -88,74 +96,77 @@ fn capacity_is_use_it_or_lose_it() {
|
|||||||
let (tx4, rx4) = mpsc::channel();
|
let (tx4, rx4) = mpsc::channel();
|
||||||
|
|
||||||
// First, fill the blocking capacity
|
// First, fill the blocking capacity
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
blocking(|| {
|
blocking(|| {
|
||||||
rx1.recv().unwrap();
|
rx1.recv().unwrap();
|
||||||
})
|
})
|
||||||
.map_err(|_| panic!())
|
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
rx2.map_err(|_| panic!()).and_then(|task: Task| {
|
let task: Waker = rx2.await.unwrap();
|
||||||
poll_fn(move || {
|
|
||||||
blocking(|| {
|
|
||||||
// Notify the other task
|
|
||||||
task.notify();
|
|
||||||
|
|
||||||
// Block until woken
|
poll_fn(move |_| {
|
||||||
rx3.recv().unwrap();
|
blocking(|| {
|
||||||
})
|
// Notify the other task
|
||||||
.map_err(|_| panic!())
|
task.wake_by_ref();
|
||||||
|
|
||||||
|
// Block until woken
|
||||||
|
rx3.recv().unwrap();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
// Spawn a future that will try to block, get notified, then not actually
|
// Spawn a future that will try to block, get notified, then not actually
|
||||||
// use the blocking
|
// use the blocking
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut tx2 = Some(tx2);
|
let mut tx2 = Some(tx2);
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |cx| {
|
||||||
match i {
|
match i {
|
||||||
0 => {
|
0 => {
|
||||||
i = 1;
|
i = 1;
|
||||||
|
|
||||||
let res = blocking(|| unreachable!()).map_err(|_| panic!());
|
let res = blocking(|| unreachable!()).map_err(|_| panic!());
|
||||||
|
|
||||||
assert!(res.unwrap().is_not_ready());
|
assert_pending!(res);
|
||||||
|
|
||||||
// Unblock the first blocker
|
// Unblock the first blocker
|
||||||
tx1.send(()).unwrap();
|
tx1.send(()).unwrap();
|
||||||
|
|
||||||
return Ok(NotReady);
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
i = 2;
|
i = 2;
|
||||||
|
|
||||||
// Skip blocking, and notify the second task that it should
|
// Skip blocking, and notify the second task that it should
|
||||||
// start blocking
|
// start blocking
|
||||||
let me = task::current();
|
let me = cx.waker().clone();
|
||||||
tx2.take().unwrap().send(me).unwrap();
|
tx2.take().unwrap().send(me).unwrap();
|
||||||
|
|
||||||
return Ok(NotReady);
|
return Poll::Pending;
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
let res = blocking(|| unreachable!()).map_err(|_| panic!());
|
let res = blocking(|| unreachable!()).map_err(|_| panic!());
|
||||||
|
|
||||||
assert!(res.unwrap().is_not_ready());
|
assert_pending!(res);
|
||||||
|
|
||||||
// Unblock the first blocker
|
// Unblock the first blocker
|
||||||
tx3.send(()).unwrap();
|
tx3.send(()).unwrap();
|
||||||
tx4.send(()).unwrap();
|
tx4.send(()).unwrap();
|
||||||
Ok(().into())
|
Poll::Ready(())
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
rx4.recv().unwrap();
|
rx4.recv().unwrap();
|
||||||
}
|
}
|
||||||
@@ -173,33 +184,35 @@ fn blocking_thread_does_not_take_over_shutdown_worker_thread() {
|
|||||||
{
|
{
|
||||||
let exited = exited.clone();
|
let exited = exited.clone();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
blocking(|| {
|
blocking(|| {
|
||||||
enter_tx.send(()).unwrap();
|
enter_tx.send(()).unwrap();
|
||||||
exit_rx.recv().unwrap();
|
exit_rx.recv().unwrap();
|
||||||
exited.store(true, Relaxed);
|
exited.store(true, SeqCst);
|
||||||
})
|
})
|
||||||
.map_err(|_| panic!())
|
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the task to block
|
// Wait for the task to block
|
||||||
let _ = enter_rx.recv().unwrap();
|
let _ = enter_rx.recv().unwrap();
|
||||||
|
|
||||||
// Spawn another task that attempts to block
|
// Spawn another task that attempts to block
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
let res = blocking(|| {}).unwrap();
|
let res = blocking(|| {});
|
||||||
|
|
||||||
assert_eq!(res.is_ready(), exited.load(Relaxed));
|
assert_eq!(res.is_ready(), exited.load(SeqCst));
|
||||||
|
|
||||||
try_tx.send(res.is_ready()).unwrap();
|
try_tx.send(res.is_ready()).unwrap();
|
||||||
|
|
||||||
Ok(res)
|
res.map(|_| ())
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
});
|
||||||
|
|
||||||
// Wait for the second task to try to block (and not be ready).
|
// Wait for the second task to try to block (and not be ready).
|
||||||
let res = try_rx.recv().unwrap();
|
let res = try_rx.recv().unwrap();
|
||||||
@@ -230,35 +243,35 @@ fn blocking_one_time_gets_capacity_for_multiple_blocks() {
|
|||||||
let rem = rem.clone();
|
let rem = rem.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
// First block
|
// First block
|
||||||
let res = blocking(|| {
|
let res = blocking(|| {
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
})
|
});
|
||||||
.map_err(|e| panic!("blocking err {:?}", e));
|
|
||||||
|
|
||||||
try_ready!(res);
|
ready!(res).unwrap();
|
||||||
|
|
||||||
let res = blocking(|| {
|
let res = blocking(|| {
|
||||||
thread::sleep(Duration::from_millis(100));
|
thread::sleep(Duration::from_millis(100));
|
||||||
let prev = rem.fetch_sub(1, Relaxed);
|
let prev = rem.fetch_sub(1, SeqCst);
|
||||||
|
|
||||||
if prev == 1 {
|
if prev == 1 {
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
assert!(res.unwrap().is_ready());
|
assert!(res.is_ready());
|
||||||
|
|
||||||
Ok(().into())
|
Poll::Ready(())
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
rx.recv().unwrap();
|
rx.recv().unwrap();
|
||||||
|
|
||||||
assert_eq!(0, rem.load(Relaxed));
|
assert_eq!(0, rem.load(SeqCst));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,10 +293,10 @@ fn shutdown() {
|
|||||||
.pool_size(1)
|
.pool_size(1)
|
||||||
.max_blocking(BLOCKING)
|
.max_blocking(BLOCKING)
|
||||||
.after_start(move || {
|
.after_start(move || {
|
||||||
num_inc.fetch_add(1, Relaxed);
|
num_inc.fetch_add(1, SeqCst);
|
||||||
})
|
})
|
||||||
.before_stop(move || {
|
.before_stop(move || {
|
||||||
num_dec.fetch_add(1, Relaxed);
|
num_dec.fetch_add(1, SeqCst);
|
||||||
})
|
})
|
||||||
.build()
|
.build()
|
||||||
};
|
};
|
||||||
@@ -294,18 +307,16 @@ fn shutdown() {
|
|||||||
let barrier = barrier.clone();
|
let barrier = barrier.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
let res = blocking(|| {
|
let res = blocking(|| {
|
||||||
barrier.wait();
|
barrier.wait();
|
||||||
Ok::<_, ()>(())
|
Ok::<_, ()>(())
|
||||||
})
|
});
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
|
|
||||||
assert!(res.is_ready());
|
assert!(res.is_ready());
|
||||||
Ok(().into())
|
});
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..BLOCKING {
|
for _ in 0..BLOCKING {
|
||||||
@@ -315,8 +326,8 @@ fn shutdown() {
|
|||||||
// Shutdown
|
// Shutdown
|
||||||
drop(pool);
|
drop(pool);
|
||||||
|
|
||||||
assert_eq!(11, num_inc.load(Relaxed));
|
assert_eq!(11, num_inc.load(SeqCst));
|
||||||
assert_eq!(11, num_dec.load(Relaxed));
|
assert_eq!(11, num_dec.load(SeqCst));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,10 +367,10 @@ fn hammer() {
|
|||||||
let cnt_task = cnt_task.clone();
|
let cnt_task = cnt_task.clone();
|
||||||
let cnt_block = cnt_block.clone();
|
let cnt_block = cnt_block.clone();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
pool.spawn(async move {
|
||||||
cnt_task.fetch_add(1, Relaxed);
|
cnt_task.fetch_add(1, SeqCst);
|
||||||
|
|
||||||
poll_fn(move || {
|
poll_fn(move |_| {
|
||||||
blocking(|| {
|
blocking(|| {
|
||||||
match sleep {
|
match sleep {
|
||||||
Skip => {}
|
Skip => {}
|
||||||
@@ -375,18 +386,20 @@ fn hammer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cnt_block.fetch_add(1, Relaxed);
|
cnt_block.fetch_add(1, SeqCst);
|
||||||
})
|
})
|
||||||
.map_err(|_| panic!())
|
.map_err(|_| panic!())
|
||||||
})
|
})
|
||||||
}));
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the work to complete
|
// Wait for the work to complete
|
||||||
pool.shutdown_on_idle().wait().unwrap();
|
pool.shutdown_on_idle().wait();
|
||||||
|
|
||||||
assert_eq!(n, cnt_task.load(Relaxed));
|
assert_eq!(n, cnt_task.load(SeqCst));
|
||||||
assert_eq!(n, cnt_block.load(Relaxed));
|
assert_eq!(n, cnt_block.load(SeqCst));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
#![deny(warnings, rust_2018_idioms)]
|
#![deny(warnings, rust_2018_idioms)]
|
||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
use futures::{Future, Poll, Sink, Stream};
|
use tokio_sync::{mpsc, oneshot};
|
||||||
|
use tokio_threadpool::*;
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::AtomicUsize;
|
use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::atomic::Ordering::*;
|
use std::sync::atomic::Ordering::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio_threadpool::*;
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hammer() {
|
fn hammer() {
|
||||||
use futures::future;
|
|
||||||
use futures::sync::{mpsc, oneshot};
|
|
||||||
|
|
||||||
const N: usize = 1000;
|
const N: usize = 1000;
|
||||||
const ITER: usize = 20;
|
const ITER: usize = 20;
|
||||||
|
|
||||||
@@ -20,11 +22,13 @@ fn hammer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Future> Future for Counted<T> {
|
impl<T: Future> Future for Counted<T> {
|
||||||
type Item = T::Item;
|
type Output = T::Output;
|
||||||
type Error = T::Error;
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<T::Item, T::Error> {
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T::Output> {
|
||||||
self.inner.poll()
|
unsafe {
|
||||||
|
let inner = &mut self.get_unchecked_mut().inner;
|
||||||
|
Pin::new_unchecked(inner).poll(cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,13 +45,30 @@ fn hammer() {
|
|||||||
|
|
||||||
let cnt = Arc::new(AtomicUsize::new(0));
|
let cnt = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
let (listen_tx, listen_rx) = mpsc::unbounded::<oneshot::Sender<oneshot::Sender<()>>>();
|
let (mut listen_tx, mut listen_rx) =
|
||||||
let mut listen_tx = listen_tx.wait();
|
mpsc::unbounded_channel::<oneshot::Sender<oneshot::Sender<()>>>();
|
||||||
|
|
||||||
pool.spawn({
|
pool.spawn({
|
||||||
let c1 = cnt.clone();
|
let c1 = cnt.clone();
|
||||||
let c2 = cnt.clone();
|
let c2 = cnt.clone();
|
||||||
let pool = pool.sender().clone();
|
let pool = pool.sender().clone();
|
||||||
|
let task = async move {
|
||||||
|
while let Some(tx) = listen_rx.recv().await {
|
||||||
|
let task = async {
|
||||||
|
let (tx2, rx2) = oneshot::channel();
|
||||||
|
tx.send(tx2).unwrap();
|
||||||
|
rx2.await.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
pool.spawn(Counted {
|
||||||
|
inner: task,
|
||||||
|
cnt: c1.clone(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
let task = listen_rx
|
let task = listen_rx
|
||||||
.map_err(|e| panic!("accept error = {:?}", e))
|
.map_err(|e| panic!("accept error = {:?}", e))
|
||||||
.for_each(move |tx| {
|
.for_each(move |tx| {
|
||||||
@@ -68,6 +89,7 @@ fn hammer() {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
Counted {
|
Counted {
|
||||||
inner: task,
|
inner: task,
|
||||||
@@ -78,21 +100,28 @@ fn hammer() {
|
|||||||
for _ in 0..N {
|
for _ in 0..N {
|
||||||
let cnt = cnt.clone();
|
let cnt = cnt.clone();
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
listen_tx.send(tx).unwrap();
|
listen_tx.try_send(tx).unwrap();
|
||||||
|
|
||||||
pool.spawn({
|
pool.spawn(async {
|
||||||
|
let task = async {
|
||||||
|
let tx = rx.await.unwrap();
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
let task = rx.map_err(|e| panic!("rx err={:?}", e)).and_then(|tx| {
|
let task = rx.map_err(|e| panic!("rx err={:?}", e)).and_then(|tx| {
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
Counted { inner: task, cnt }
|
Counted { inner: task, cnt }.await
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(listen_tx);
|
drop(listen_tx);
|
||||||
|
|
||||||
pool.shutdown_on_idle().wait().unwrap();
|
pool.shutdown_on_idle().wait();
|
||||||
assert_eq!(N * 2 + 1, cnt.load(Relaxed));
|
assert_eq!(N * 2 + 1, cnt.load(Relaxed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,21 @@
|
|||||||
#![deny(warnings, rust_2018_idioms)]
|
#![deny(warnings, rust_2018_idioms)]
|
||||||
|
#![feature(async_await)]
|
||||||
|
|
||||||
use futures::future::lazy;
|
|
||||||
use futures::{Async, Future, Poll, Sink, Stream};
|
|
||||||
use std::cell::Cell;
|
|
||||||
use std::sync::atomic::Ordering::Relaxed;
|
|
||||||
use std::sync::atomic::*;
|
|
||||||
use std::sync::{mpsc, Arc};
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio_executor::park::{Park, Unpark};
|
use tokio_executor::park::{Park, Unpark};
|
||||||
|
use tokio_test::assert_pending;
|
||||||
use tokio_threadpool::park::{DefaultPark, DefaultUnpark};
|
use tokio_threadpool::park::{DefaultPark, DefaultUnpark};
|
||||||
use tokio_threadpool::*;
|
use tokio_threadpool::*;
|
||||||
|
|
||||||
thread_local!(static FOO: Cell<u32> = Cell::new(0));
|
use std::cell::Cell;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::atomic::Ordering::Relaxed;
|
||||||
|
use std::sync::atomic::*;
|
||||||
|
use std::sync::{mpsc, Arc};
|
||||||
|
use std::task::{Context, Poll, Waker};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
fn ignore_results<F: Future + Send + 'static>(
|
thread_local!(static FOO: Cell<u32> = Cell::new(0));
|
||||||
f: F,
|
|
||||||
) -> Box<dyn Future<Item = (), Error = ()> + Send> {
|
|
||||||
Box::new(f.map(|_| ()).map_err(|_| ()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn natural_shutdown_simple_futures() {
|
fn natural_shutdown_simple_futures() {
|
||||||
@@ -47,26 +45,24 @@ fn natural_shutdown_simple_futures() {
|
|||||||
|
|
||||||
let a = {
|
let a = {
|
||||||
let (t, rx) = mpsc::channel();
|
let (t, rx) = mpsc::channel();
|
||||||
tx.spawn(lazy(move || {
|
tx.spawn(async move {
|
||||||
// Makes sure this runs on a worker thread
|
// Makes sure this runs on a worker thread
|
||||||
FOO.with(|f| assert_eq!(f.get(), 0));
|
FOO.with(|f| assert_eq!(f.get(), 0));
|
||||||
|
|
||||||
t.send("one").unwrap();
|
t.send("one").unwrap();
|
||||||
Ok(())
|
})
|
||||||
}))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
rx
|
rx
|
||||||
};
|
};
|
||||||
|
|
||||||
let b = {
|
let b = {
|
||||||
let (t, rx) = mpsc::channel();
|
let (t, rx) = mpsc::channel();
|
||||||
tx.spawn(lazy(move || {
|
tx.spawn(async move {
|
||||||
// Makes sure this runs on a worker thread
|
// Makes sure this runs on a worker thread
|
||||||
FOO.with(|f| assert_eq!(f.get(), 0));
|
FOO.with(|f| assert_eq!(f.get(), 0));
|
||||||
|
|
||||||
t.send("two").unwrap();
|
t.send("two").unwrap();
|
||||||
Ok(())
|
})
|
||||||
}))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
rx
|
rx
|
||||||
};
|
};
|
||||||
@@ -77,7 +73,7 @@ fn natural_shutdown_simple_futures() {
|
|||||||
assert_eq!("two", b.recv().unwrap());
|
assert_eq!("two", b.recv().unwrap());
|
||||||
|
|
||||||
// Wait for the pool to shutdown
|
// Wait for the pool to shutdown
|
||||||
pool.shutdown().wait().unwrap();
|
pool.shutdown().wait();
|
||||||
|
|
||||||
// Assert that at least one thread started
|
// Assert that at least one thread started
|
||||||
let num_inc = num_inc.load(Relaxed);
|
let num_inc = num_inc.load(Relaxed);
|
||||||
@@ -102,11 +98,10 @@ fn force_shutdown_drops_futures() {
|
|||||||
struct Never(Arc<AtomicUsize>);
|
struct Never(Arc<AtomicUsize>);
|
||||||
|
|
||||||
impl Future for Never {
|
impl Future for Never {
|
||||||
type Item = ();
|
type Output = ();
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), ()> {
|
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
|
||||||
Ok(Async::NotReady)
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +126,7 @@ fn force_shutdown_drops_futures() {
|
|||||||
tx.spawn(Never(num_drop.clone())).unwrap();
|
tx.spawn(Never(num_drop.clone())).unwrap();
|
||||||
|
|
||||||
// Wait for the pool to shutdown
|
// Wait for the pool to shutdown
|
||||||
pool.shutdown_now().wait().unwrap();
|
pool.shutdown_now().wait();
|
||||||
|
|
||||||
// Assert that only a single thread was spawned.
|
// Assert that only a single thread was spawned.
|
||||||
let a = num_inc.load(Relaxed);
|
let a = num_inc.load(Relaxed);
|
||||||
@@ -159,11 +154,10 @@ fn drop_threadpool_drops_futures() {
|
|||||||
struct Never(Arc<AtomicUsize>);
|
struct Never(Arc<AtomicUsize>);
|
||||||
|
|
||||||
impl Future for Never {
|
impl Future for Never {
|
||||||
type Item = ();
|
type Output = ();
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), ()> {
|
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
|
||||||
Ok(Async::NotReady)
|
Poll::Pending
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,15 +213,14 @@ fn many_oneshot_futures() {
|
|||||||
|
|
||||||
for _ in 0..NUM {
|
for _ in 0..NUM {
|
||||||
let cnt = cnt.clone();
|
let cnt = cnt.clone();
|
||||||
tx.spawn(lazy(move || {
|
tx.spawn(async move {
|
||||||
cnt.fetch_add(1, Relaxed);
|
cnt.fetch_add(1, Relaxed);
|
||||||
Ok(())
|
})
|
||||||
}))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for the pool to shutdown
|
// Wait for the pool to shutdown
|
||||||
pool.shutdown().wait().unwrap();
|
pool.shutdown().wait();
|
||||||
|
|
||||||
let num = cnt.load(Relaxed);
|
let num = cnt.load(Relaxed);
|
||||||
assert_eq!(num, NUM);
|
assert_eq!(num, NUM);
|
||||||
@@ -236,7 +229,7 @@ fn many_oneshot_futures() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn many_multishot_futures() {
|
fn many_multishot_futures() {
|
||||||
use futures::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
const CHAIN: usize = 200;
|
const CHAIN: usize = 200;
|
||||||
const CYCLES: usize = 5;
|
const CYCLES: usize = 5;
|
||||||
@@ -255,57 +248,61 @@ fn many_multishot_futures() {
|
|||||||
let (start_tx, mut chain_rx) = mpsc::channel(10);
|
let (start_tx, mut chain_rx) = mpsc::channel(10);
|
||||||
|
|
||||||
for _ in 0..CHAIN {
|
for _ in 0..CHAIN {
|
||||||
let (next_tx, next_rx) = mpsc::channel(10);
|
let (mut next_tx, next_rx) = mpsc::channel(10);
|
||||||
|
|
||||||
let rx = chain_rx.map_err(|e| panic!("{:?}", e));
|
|
||||||
|
|
||||||
// Forward all the messages
|
// Forward all the messages
|
||||||
pool_tx
|
pool_tx
|
||||||
.spawn(
|
.spawn(async move {
|
||||||
next_tx
|
while let Some(v) = chain_rx.recv().await {
|
||||||
.send_all(rx)
|
next_tx.send(v).await.unwrap();
|
||||||
.map(|_| ())
|
}
|
||||||
.map_err(|e| panic!("{:?}", e)),
|
})
|
||||||
)
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
chain_rx = next_rx;
|
chain_rx = next_rx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This final task cycles if needed
|
// This final task cycles if needed
|
||||||
let (final_tx, final_rx) = mpsc::channel(10);
|
let (mut final_tx, final_rx) = mpsc::channel(10);
|
||||||
let cycle_tx = start_tx.clone();
|
let mut cycle_tx = start_tx.clone();
|
||||||
let mut rem = CYCLES;
|
let mut rem = CYCLES;
|
||||||
|
|
||||||
let task = chain_rx.take(CYCLES as u64).for_each(move |msg| {
|
pool_tx
|
||||||
rem -= 1;
|
.spawn(async move {
|
||||||
let send = if rem == 0 {
|
for _ in 0..CYCLES {
|
||||||
final_tx.clone().send(msg)
|
let msg = chain_rx.recv().await.unwrap();
|
||||||
} else {
|
|
||||||
cycle_tx.clone().send(msg)
|
|
||||||
};
|
|
||||||
|
|
||||||
send.then(|res| {
|
rem -= 1;
|
||||||
res.unwrap();
|
|
||||||
Ok(())
|
if rem == 0 {
|
||||||
|
final_tx.send(msg).await.unwrap();
|
||||||
|
} else {
|
||||||
|
cycle_tx.send(msg).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
.unwrap();
|
||||||
pool_tx.spawn(ignore_results(task)).unwrap();
|
|
||||||
|
|
||||||
start_txs.push(start_tx);
|
start_txs.push(start_tx);
|
||||||
final_rxs.push(final_rx);
|
final_rxs.push(final_rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
for start_tx in start_txs {
|
{
|
||||||
start_tx.send("ping").wait().unwrap();
|
let mut e = tokio_executor::enter().unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
for final_rx in final_rxs {
|
e.block_on(async move {
|
||||||
final_rx.wait().next().unwrap().unwrap();
|
for mut start_tx in start_txs {
|
||||||
|
start_tx.send("ping").await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for mut final_rx in final_rxs {
|
||||||
|
final_rx.recv().await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the pool
|
// Shutdown the pool
|
||||||
pool.shutdown().wait().unwrap();
|
pool.shutdown().wait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,30 +313,27 @@ fn global_executor_is_configured() {
|
|||||||
|
|
||||||
let (signal_tx, signal_rx) = mpsc::channel();
|
let (signal_tx, signal_rx) = mpsc::channel();
|
||||||
|
|
||||||
tx.spawn(lazy(move || {
|
tx.spawn(async move {
|
||||||
tokio_executor::spawn(lazy(move || {
|
tokio_executor::spawn(async move {
|
||||||
signal_tx.send(()).unwrap();
|
signal_tx.send(()).unwrap();
|
||||||
Ok(())
|
});
|
||||||
}));
|
})
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}))
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
signal_rx.recv().unwrap();
|
signal_rx.recv().unwrap();
|
||||||
|
|
||||||
pool.shutdown().wait().unwrap();
|
pool.shutdown().wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_threadpool_is_idle() {
|
fn new_threadpool_is_idle() {
|
||||||
let pool = ThreadPool::new();
|
let pool = ThreadPool::new();
|
||||||
pool.shutdown_on_idle().wait().unwrap();
|
pool.shutdown_on_idle().wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn busy_threadpool_is_not_idle() {
|
fn busy_threadpool_is_not_idle() {
|
||||||
use futures::sync::oneshot;
|
use tokio_sync::oneshot;
|
||||||
|
|
||||||
// let pool = ThreadPool::new();
|
// let pool = ThreadPool::new();
|
||||||
let pool = Builder::new().pool_size(4).max_blocking(2).build();
|
let pool = Builder::new().pool_size(4).max_blocking(2).build();
|
||||||
@@ -347,26 +341,31 @@ fn busy_threadpool_is_not_idle() {
|
|||||||
|
|
||||||
let (term_tx, term_rx) = oneshot::channel();
|
let (term_tx, term_rx) = oneshot::channel();
|
||||||
|
|
||||||
tx.spawn(term_rx.then(|_| Ok(()))).unwrap();
|
tx.spawn(async move {
|
||||||
|
term_rx.await.unwrap();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut idle = pool.shutdown_on_idle();
|
let mut idle = pool.shutdown_on_idle();
|
||||||
|
|
||||||
struct IdleFut<'a>(&'a mut Shutdown);
|
struct IdleFut<'a>(&'a mut Shutdown);
|
||||||
|
|
||||||
impl<'a> Future for IdleFut<'a> {
|
impl<'a> Future for IdleFut<'a> {
|
||||||
type Item = ();
|
type Output = ();
|
||||||
type Error = ();
|
|
||||||
fn poll(&mut self) -> Poll<(), ()> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
assert!(self.0.poll().unwrap().is_not_ready());
|
assert_pending!(Pin::new(&mut self.as_mut().0).poll(cx));
|
||||||
Ok(Async::Ready(()))
|
Poll::Ready(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IdleFut(&mut idle).wait().unwrap();
|
let idle_fut = IdleFut(&mut idle);
|
||||||
|
tokio_executor::enter().unwrap().block_on(idle_fut);
|
||||||
|
|
||||||
term_tx.send(()).unwrap();
|
term_tx.send(()).unwrap();
|
||||||
|
|
||||||
idle.wait().unwrap();
|
let idle_fut = IdleFut(&mut idle);
|
||||||
|
tokio_executor::enter().unwrap().block_on(idle_fut);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -377,10 +376,9 @@ fn panic_in_task() {
|
|||||||
struct Boom;
|
struct Boom;
|
||||||
|
|
||||||
impl Future for Boom {
|
impl Future for Boom {
|
||||||
type Item = ();
|
type Output = ();
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<(), ()> {
|
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
|
||||||
panic!();
|
panic!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,7 +391,7 @@ fn panic_in_task() {
|
|||||||
|
|
||||||
tx.spawn(Boom).unwrap();
|
tx.spawn(Boom).unwrap();
|
||||||
|
|
||||||
pool.shutdown_on_idle().wait().unwrap();
|
pool.shutdown_on_idle().wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -407,15 +405,15 @@ fn count_panics() {
|
|||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
// Spawn a future that will panic.
|
// Spawn a future that will panic.
|
||||||
pool.spawn(lazy(|| -> Result<(), ()> { panic!() }));
|
pool.spawn(async { panic!() });
|
||||||
pool.shutdown_on_idle().wait().unwrap();
|
pool.shutdown_on_idle().wait();
|
||||||
let counter = counter.load(Relaxed);
|
let counter = counter.load(Relaxed);
|
||||||
assert_eq!(counter, 1);
|
assert_eq!(counter, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multi_threadpool() {
|
fn multi_threadpool() {
|
||||||
use futures::sync::oneshot;
|
use tokio_sync::oneshot;
|
||||||
|
|
||||||
let pool1 = ThreadPool::new();
|
let pool1 = ThreadPool::new();
|
||||||
let pool2 = ThreadPool::new();
|
let pool2 = ThreadPool::new();
|
||||||
@@ -423,36 +421,22 @@ fn multi_threadpool() {
|
|||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let (done_tx, done_rx) = mpsc::channel();
|
let (done_tx, done_rx) = mpsc::channel();
|
||||||
|
|
||||||
pool2.spawn({
|
pool2.spawn(async move {
|
||||||
rx.and_then(move |_| {
|
rx.await.unwrap();
|
||||||
done_tx.send(()).unwrap();
|
done_tx.send(()).unwrap();
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.map_err(|e| panic!("err={:?}", e))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
pool1.spawn(lazy(move || {
|
pool1.spawn(async move {
|
||||||
tx.send(()).unwrap();
|
tx.send(()).unwrap();
|
||||||
Ok(())
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
done_rx.recv().unwrap();
|
done_rx.recv().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn eagerly_drops_futures() {
|
fn eagerly_drops_futures() {
|
||||||
use futures::future::{empty, lazy, Future};
|
|
||||||
use futures::task;
|
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
struct NotifyOnDrop(mpsc::Sender<()>);
|
|
||||||
|
|
||||||
impl Drop for NotifyOnDrop {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.0.send(()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MyPark {
|
struct MyPark {
|
||||||
inner: DefaultPark,
|
inner: DefaultPark,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -497,9 +481,6 @@ fn eagerly_drops_futures() {
|
|||||||
let (park_tx, park_rx) = mpsc::sync_channel(0);
|
let (park_tx, park_rx) = mpsc::sync_channel(0);
|
||||||
let (unpark_tx, unpark_rx) = mpsc::sync_channel(0);
|
let (unpark_tx, unpark_rx) = mpsc::sync_channel(0);
|
||||||
|
|
||||||
// Get the signal that the handler dropped.
|
|
||||||
let notify_on_drop = NotifyOnDrop(drop_tx);
|
|
||||||
|
|
||||||
let pool = tokio_threadpool::Builder::new()
|
let pool = tokio_threadpool::Builder::new()
|
||||||
.custom_park(move |_| MyPark {
|
.custom_park(move |_| MyPark {
|
||||||
inner: DefaultPark::new(),
|
inner: DefaultPark::new(),
|
||||||
@@ -508,29 +489,33 @@ fn eagerly_drops_futures() {
|
|||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pool.spawn(lazy(move || {
|
struct MyTask {
|
||||||
// Get a handle to the current task.
|
task_tx: Option<mpsc::Sender<Waker>>,
|
||||||
let task = task::current();
|
drop_tx: mpsc::Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
// Send it to the main thread to hold on to.
|
impl Future for MyTask {
|
||||||
task_tx.send(task).unwrap();
|
type Output = ();
|
||||||
|
|
||||||
// This future will never resolve, it is only used to hold on to thee
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
// `notify_on_drop` handle.
|
if let Some(tx) = self.get_mut().task_tx.take() {
|
||||||
empty::<(), ()>().then(move |_| {
|
tx.send(cx.waker().clone()).unwrap();
|
||||||
// This code path should never be reached.
|
|
||||||
if true {
|
|
||||||
panic!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicitly drop `notify_on_drop` here, this is mostly to ensure
|
Poll::Pending
|
||||||
// that the `notify_on_drop` handle gets moved into the task. It
|
}
|
||||||
// will actually get dropped when the runtime is dropped.
|
}
|
||||||
drop(notify_on_drop);
|
|
||||||
|
|
||||||
Ok(())
|
impl Drop for MyTask {
|
||||||
})
|
fn drop(&mut self) {
|
||||||
}));
|
self.drop_tx.send(()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pool.spawn(MyTask {
|
||||||
|
task_tx: Some(task_tx),
|
||||||
|
drop_tx,
|
||||||
|
});
|
||||||
|
|
||||||
// Wait until we get the task handle.
|
// Wait until we get the task handle.
|
||||||
let task = task_rx.recv().unwrap();
|
let task = task_rx.recv().unwrap();
|
||||||
|
|||||||
Reference in New Issue
Block a user