executor: switch some APIs to crate private. (#1731)

* switch `enter` to crate private
* make executor types pub(crate)
This commit is contained in:
Carl Lerche
2019-11-04 14:12:24 -08:00
committed by GitHub
parent 966ccd5d53
commit 94f9b04b06
21 changed files with 204 additions and 416 deletions
+28
View File
@@ -249,6 +249,34 @@ impl Drop for PoolWaiter {
}
}
/// 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::executor::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::executor;
executor::enter::exit(|| executor::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
+8 -6
View File
@@ -1,6 +1,7 @@
use std::cell::{Cell, RefCell};
use std::error::Error;
use std::fmt;
#[cfg(feature = "rt-full")]
use std::future::Future;
use std::marker::PhantomData;
@@ -9,13 +10,13 @@ thread_local!(static ENTERED: Cell<bool> = Cell::new(false));
/// Represents an executor context.
///
/// For more details, see [`enter` documentation](fn.enter.html)
pub struct Enter {
pub(crate) struct Enter {
_p: PhantomData<RefCell<()>>,
}
/// An error returned by `enter` if an execution scope has already been
/// entered.
pub struct EnterError {
pub(crate) struct EnterError {
_a: (),
}
@@ -49,7 +50,7 @@ impl Error for EnterError {}
/// # Error
///
/// Returns an error if the current thread is already marked
pub fn enter() -> Result<Enter, EnterError> {
pub(crate) fn enter() -> Result<Enter, EnterError> {
ENTERED.with(|c| {
if c.get() {
Err(EnterError { _a: () })
@@ -68,8 +69,8 @@ pub fn enter() -> Result<Enter, EnterError> {
//
// This is hidden for a reason. Do not use without fully understanding
// executors. Misuing can easily cause your program to deadlock.
#[doc(hidden)]
pub fn exit<F: FnOnce() -> R, R>(f: F) -> R {
#[cfg(feature = "rt-full")]
pub(crate) fn exit<F: FnOnce() -> R, R>(f: F) -> R {
// Reset in case the closure panics
struct Reset;
impl Drop for Reset {
@@ -100,7 +101,8 @@ pub 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.
pub fn block_on<F: Future>(&mut self, mut f: F) -> F::Output {
#[cfg(feature = "rt-full")]
pub(crate) fn block_on<F: Future>(&mut self, mut f: F) -> F::Output {
use crate::executor::park::{Park, ParkThread};
use std::pin::Pin;
use std::task::Context;
+1
View File
@@ -22,6 +22,7 @@ pub(crate) mod std {
#[cfg(feature = "rt-full")]
pub(crate) use self::std::rand;
#[cfg(any(feature = "blocking", feature = "rt-current-thread"))]
pub(crate) use self::std::sync;
#[cfg(any(feature = "blocking", feature = "rt-full"))]
pub(crate) use self::std::thread;
@@ -11,6 +11,7 @@ unsafe impl Send for AtomicUsize {}
unsafe impl Sync for AtomicUsize {}
impl AtomicUsize {
#[cfg(feature = "rt-current-thread")]
pub(crate) fn new(val: usize) -> AtomicUsize {
let inner = UnsafeCell::new(std::sync::atomic::AtomicUsize::new(val));
AtomicUsize { inner }
+2
View File
@@ -56,11 +56,13 @@ pub(crate) mod rand {
}
pub(crate) mod sync {
#[cfg(any(feature = "blocking", feature = "rt-current-thread"))]
pub(crate) use std::sync::{Arc, Condvar, Mutex};
pub(crate) mod atomic {
#[cfg(feature = "rt-full")]
pub(crate) use crate::executor::loom::std::atomic_u32::AtomicU32;
#[cfg(feature = "rt-current-thread")]
pub(crate) use crate::executor::loom::std::atomic_usize::AtomicUsize;
#[cfg(feature = "rt-full")]
+4 -4
View File
@@ -44,8 +44,10 @@
#[macro_use]
mod tests;
#[cfg(feature = "rt-current-thread")]
mod enter;
pub use self::enter::{enter, exit, Enter, EnterError};
#[cfg(feature = "rt-current-thread")]
pub(crate) use self::enter::enter;
mod global;
pub use self::global::spawn;
@@ -71,6 +73,4 @@ pub mod blocking;
pub(crate) mod current_thread;
#[cfg(feature = "rt-full")]
pub mod thread_pool;
pub use futures_util::future::RemoteHandle;
pub(crate) mod thread_pool;
+3 -1
View File
@@ -44,8 +44,10 @@
//! [up]: trait.Unpark.html
//! [mio]: https://docs.rs/mio/0.6/mio/struct.Poll.html
#[cfg(feature = "rt-full")]
mod thread;
pub use self::thread::{ParkError, ParkThread, UnparkThread};
#[cfg(feature = "rt-full")]
pub(crate) use self::thread::ParkThread;
use std::sync::Arc;
use std::time::Duration;
+67 -57
View File
@@ -3,10 +3,8 @@ use crate::executor::loom::sync::{Arc, Condvar, Mutex};
use crate::executor::park::{Park, Unpark};
use std::marker::PhantomData;
use std::mem;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::task::{RawWaker, RawWakerVTable, Waker};
use std::time::Duration;
/// Blocks the current thread using a condition variable.
@@ -20,7 +18,7 @@ use std::time::Duration;
/// means that an instance of `ParkThread` might be unblocked by a handle
/// associated with a different `ParkThread` instance.
#[derive(Debug)]
pub struct ParkThread {
pub(crate) struct ParkThread {
_anchor: PhantomData<Rc<()>>,
}
@@ -30,7 +28,7 @@ pub struct ParkThread {
///
/// [`ParkThread`]: struct.ParkThread.html
#[derive(Debug)]
pub struct ParkError {
pub(crate) struct ParkError {
_p: (),
}
@@ -40,7 +38,7 @@ struct Parker {
/// Unblocks a thread that was blocked by `ParkThread`.
#[derive(Clone, Debug)]
pub struct UnparkThread {
pub(crate) struct UnparkThread {
inner: Arc<Inner>,
}
@@ -62,7 +60,7 @@ thread_local! {
// ==== impl Parker ====
impl Parker {
pub(crate) fn new() -> Self {
fn new() -> Self {
Self {
unparker: Arc::new(Inner {
state: AtomicUsize::new(IDLE),
@@ -72,15 +70,15 @@ impl Parker {
}
}
pub(crate) fn unparker(&self) -> &Arc<Inner> {
fn unparker(&self) -> &Arc<Inner> {
&self.unparker
}
pub(crate) fn park(&self) -> Result<(), ParkError> {
fn park(&self) -> Result<(), ParkError> {
self.unparker.park(None)
}
pub(crate) fn park_timeout(&self, timeout: Duration) -> Result<(), ParkError> {
fn park_timeout(&self, timeout: Duration) -> Result<(), ParkError> {
self.unparker.park(Some(timeout))
}
}
@@ -88,17 +86,8 @@ impl Parker {
// ==== impl Inner ====
impl Inner {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn into_raw(this: Arc<Inner>) -> *const () {
Arc::into_raw(this) as *const ()
}
pub(crate) unsafe fn from_raw(ptr: *const ()) -> Arc<Inner> {
Arc::from_raw(ptr as *const Inner)
}
/// Park the current thread for at most `dur`.
pub(crate) fn park(&self, timeout: Option<Duration>) -> Result<(), ParkError> {
fn park(&self, timeout: Option<Duration>) -> Result<(), ParkError> {
// If currently notified, then we skip sleeping. This is checked outside
// of the lock to avoid acquiring a mutex if not necessary.
match self.state.compare_and_swap(NOTIFY, IDLE, Ordering::SeqCst) {
@@ -140,7 +129,7 @@ impl Inner {
Ok(())
}
pub(crate) fn unpark(&self) {
fn unpark(&self) {
// First, try transitioning from IDLE -> NOTIFY, this does not require a
// lock.
match self.state.compare_and_swap(IDLE, NOTIFY, Ordering::SeqCst) {
@@ -172,7 +161,7 @@ impl ParkThread {
///
/// This type cannot be moved to other threads, so it should be created on
/// the thread that the caller intends to park.
pub fn new() -> ParkThread {
pub(crate) fn new() -> ParkThread {
ParkThread {
_anchor: PhantomData,
}
@@ -221,43 +210,64 @@ impl Unpark for UnparkThread {
}
}
static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker);
#[cfg(feature = "rt-full")]
mod waker {
use super::{Inner, UnparkThread};
use crate::executor::loom::sync::Arc;
impl UnparkThread {
pub(crate) fn into_waker(self) -> Waker {
unsafe {
let raw = unparker_to_raw_waker(self.inner);
Waker::from_raw(raw)
use std::mem;
use std::task::{RawWaker, RawWakerVTable, Waker};
impl UnparkThread {
pub(crate) fn into_waker(self) -> Waker {
unsafe {
let raw = unparker_to_raw_waker(self.inner);
Waker::from_raw(raw)
}
}
}
}
unsafe fn unparker_to_raw_waker(unparker: Arc<Inner>) -> RawWaker {
RawWaker::new(Inner::into_raw(unparker), &VTABLE)
}
unsafe fn clone(raw: *const ()) -> RawWaker {
let unparker = Inner::from_raw(raw);
// Increment the ref count
mem::forget(unparker.clone());
unparker_to_raw_waker(unparker)
}
unsafe fn drop_waker(raw: *const ()) {
let _ = Inner::from_raw(raw);
}
unsafe fn wake(raw: *const ()) {
let unparker = Inner::from_raw(raw);
unparker.unpark();
}
unsafe fn wake_by_ref(raw: *const ()) {
let unparker = Inner::from_raw(raw);
unparker.unpark();
// We don't actually own a reference to the unparker
mem::forget(unparker);
impl Inner {
#[allow(clippy::wrong_self_convention)]
fn into_raw(this: Arc<Inner>) -> *const () {
Arc::into_raw(this) as *const ()
}
unsafe fn from_raw(ptr: *const ()) -> Arc<Inner> {
Arc::from_raw(ptr as *const Inner)
}
}
unsafe fn unparker_to_raw_waker(unparker: Arc<Inner>) -> RawWaker {
RawWaker::new(
Inner::into_raw(unparker),
&RawWakerVTable::new(clone, wake, wake_by_ref, drop_waker),
)
}
unsafe fn clone(raw: *const ()) -> RawWaker {
let unparker = Inner::from_raw(raw);
// Increment the ref count
mem::forget(unparker.clone());
unparker_to_raw_waker(unparker)
}
unsafe fn drop_waker(raw: *const ()) {
let _ = Inner::from_raw(raw);
}
unsafe fn wake(raw: *const ()) {
let unparker = Inner::from_raw(raw);
unparker.unpark();
}
unsafe fn wake_by_ref(raw: *const ()) {
let unparker = Inner::from_raw(raw);
unparker.unpark();
// We don't actually own a reference to the unparker
mem::forget(unparker);
}
}
+7 -68
View File
@@ -2,13 +2,12 @@ use crate::executor::loom::sync::Arc;
use crate::executor::loom::sys::num_cpus;
use crate::executor::loom::thread;
use crate::executor::park::Park;
use crate::executor::thread_pool::park::DefaultPark;
use crate::executor::thread_pool::{shutdown, worker, worker::Worker, Spawner, ThreadPool};
use std::{fmt, usize};
/// Builds a thread pool with custom configuration values.
pub struct Builder {
pub(crate) struct Builder {
/// Number of worker threads to spawn
pool_size: usize,
@@ -29,7 +28,7 @@ type Callback = Arc<Box<dyn Fn(usize, &mut dyn FnMut()) + Send + Sync>>;
impl Builder {
/// Returns a new thread pool builder initialized with default configuration
/// values.
pub fn new() -> Builder {
pub(crate) fn new() -> Builder {
Builder {
pool_size: num_cpus(),
name: "tokio-runtime-worker".to_string(),
@@ -44,17 +43,7 @@ impl Builder {
/// this value on the smaller side.
///
/// The default value is the number of cores available to the system.
///
/// # Examples
///
/// ```
/// use tokio::executor::thread_pool::Builder;
///
/// let thread_pool = Builder::new()
/// .num_threads(4)
/// .build();
/// ```
pub fn num_threads(&mut self, value: usize) -> &mut Self {
pub(crate) fn num_threads(&mut self, value: usize) -> &mut Self {
self.pool_size = value;
self
}
@@ -63,17 +52,7 @@ impl Builder {
///
/// If this configuration is not set, then the thread will use the system
/// default naming scheme.
///
/// # Examples
///
/// ```
/// use tokio::executor::thread_pool::Builder;
///
/// let thread_pool = Builder::new()
/// .name("my-pool")
/// .build();
/// ```
pub fn name<S: Into<String>>(&mut self, val: S) -> &mut Self {
pub(crate) fn name<S: Into<String>>(&mut self, val: S) -> &mut Self {
self.name = val.into();
self
}
@@ -85,17 +64,7 @@ impl Builder {
///
/// The default stack size for spawned threads is 2 MiB, though this
/// particular stack size is subject to change in the future.
///
/// # Examples
///
/// ```
/// use tokio::executor::thread_pool::Builder;
///
/// let thread_pool = Builder::new()
/// .stack_size(32 * 1024)
/// .build();
/// ```
pub fn stack_size(&mut self, val: usize) -> &mut Self {
pub(crate) fn stack_size(&mut self, val: usize) -> &mut Self {
self.stack_size = Some(val);
self
}
@@ -105,21 +74,7 @@ impl Builder {
/// This function is provided a function that executes the worker and is
/// expected to call it, otherwise the worker thread will shutdown without
/// doing any work.
///
/// # Examples
///
/// ```
/// use tokio::executor::thread_pool::Builder;
///
/// let thread_pool = Builder::new()
/// .around_worker(|index, work| {
/// println!("worker {} is starting up", index);
/// work();
/// println!("worker {} is shutting down", index);
/// })
/// .build();
/// ```
pub fn around_worker<F>(&mut self, f: F) -> &mut Self
pub(crate) fn around_worker<F>(&mut self, f: F) -> &mut Self
where
F: Fn(usize, &mut dyn FnMut()) + Send + Sync + 'static,
{
@@ -127,27 +82,11 @@ impl Builder {
self
}
/// Create the configured `ThreadPool`.
///
/// The returned `ThreadPool` instance is ready to spawn tasks.
///
/// # Examples
///
/// ```
/// use tokio::executor::thread_pool::Builder;
///
/// let thread_pool = Builder::new()
/// .build();
/// ```
pub fn build(&self) -> ThreadPool {
self.build_with_park(|_| DefaultPark::new())
}
/// Create the configured `ThreadPool` with a custom `park` instances.
///
/// The provided closure `build_park` is called once per worker and returns
/// a `Park` instance that is used by the worker to put itself to sleep.
pub fn build_with_park<F, P>(&self, mut build_park: F) -> ThreadPool
pub(crate) fn build<F, P>(&self, mut build_park: F) -> ThreadPool
where
F: FnMut(usize) -> P,
P: Park + Send + 'static,
+5 -8
View File
@@ -1,7 +1,7 @@
//! Threadpool
mod builder;
pub use self::builder::Builder;
pub(crate) use self::builder::Builder;
mod current;
@@ -11,15 +11,13 @@ use self::idle::Idle;
mod owned;
use self::owned::Owned;
mod park;
mod pool;
pub use self::pool::ThreadPool;
pub(crate) use self::pool::ThreadPool;
mod queue;
mod spawner;
pub use self::spawner::Spawner;
pub(crate) use self::spawner::Spawner;
mod set;
@@ -29,14 +27,13 @@ use self::shared::Shared;
mod shutdown;
mod worker;
#[cfg(feature = "blocking")]
pub(crate) use worker::blocking;
/// Unit tests
#[cfg(test)]
mod tests;
#[cfg(feature = "blocking")]
pub use worker::blocking;
#[cfg(not(loom))]
const LOCAL_QUEUE_CAPACITY: usize = 256;
-182
View File
@@ -1,182 +0,0 @@
use crate::executor::loom::sync::atomic::AtomicUsize;
use crate::executor::loom::sync::{Arc, Condvar, Mutex};
use crate::executor::park::{Park, Unpark};
use std::error::Error;
use std::fmt;
use std::sync::atomic::Ordering::SeqCst;
use std::time::Duration;
/// Parks the thread.
#[derive(Debug)]
pub(crate) struct DefaultPark {
inner: Arc<Inner>,
}
/// Unparks threads that were parked by `DefaultPark`.
#[derive(Debug)]
pub(crate) struct DefaultUnpark {
inner: Arc<Inner>,
}
/// Error returned by [`ParkThread`]
///
/// This currently is never returned, but might at some point in the future.
///
/// [`ParkThread`]: struct.ParkThread.html
#[derive(Debug)]
pub(crate) struct ParkError {
_p: (),
}
const EMPTY: usize = 0;
const PARKED: usize = 1;
const NOTIFIED: usize = 2;
#[derive(Debug)]
struct Inner {
state: AtomicUsize,
lock: Mutex<()>,
cvar: Condvar,
}
impl DefaultPark {
/// Creates a new `DefaultPark` instance.
pub(crate) fn new() -> DefaultPark {
DefaultPark {
inner: Arc::new(Inner {
state: AtomicUsize::new(EMPTY),
lock: Mutex::new(()),
cvar: Condvar::new(),
}),
}
}
}
impl Park for DefaultPark {
type Unpark = DefaultUnpark;
type Error = ParkError;
fn unpark(&self) -> Self::Unpark {
let inner = self.inner.clone();
DefaultUnpark { inner }
}
fn park(&mut self) -> Result<(), Self::Error> {
self.inner.park(None);
Ok(())
}
fn park_timeout(&mut self, duration: Duration) -> Result<(), Self::Error> {
self.inner.park(Some(duration));
Ok(())
}
}
impl Unpark for DefaultUnpark {
fn unpark(&self) {
self.inner.unpark();
}
}
impl fmt::Display for ParkError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "unknown park error")
}
}
impl Error for ParkError {}
impl Inner {
fn park(&self, timeout: Option<Duration>) {
// If we were previously notified then we consume this notification and return quickly.
if self
.state
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
.is_ok()
{
return;
}
// If the timeout is zero, then there is no need to actually block.
if let Some(ref dur) = timeout {
if *dur == Duration::from_millis(0) {
return;
}
}
// Otherwise we need to coordinate going to sleep.
let mut _m = self.lock.lock().unwrap();
match self.state.compare_exchange(EMPTY, PARKED, SeqCst, SeqCst) {
Ok(_) => {}
// Consume this notification to avoid spurious wakeups in the next park.
Err(NOTIFIED) => {
// We must read `state` here, even though we know it will be `NOTIFIED`. This is
// because `unpark` may have been called again since we read `NOTIFIED` in the
// `compare_exchange` above. We must perform an acquire operation that synchronizes
// with that `unpark` to observe any writes it made before the call to `unpark`. To
// do that we must read from the write it made to `state`.
let old = self.state.swap(EMPTY, SeqCst);
assert_eq!(old, NOTIFIED, "park state changed unexpectedly");
return;
}
Err(n) => panic!("inconsistent park_timeout state: {}", n),
}
match timeout {
None => {
loop {
// Block the current thread on the conditional variable.
_m = self.cvar.wait(_m).unwrap();
if self
.state
.compare_exchange(NOTIFIED, EMPTY, SeqCst, SeqCst)
.is_ok()
{
return; // got a notification
}
// spurious wakeup, go back to sleep
}
}
Some(timeout) => {
// Wait with a timeout, and if we spuriously wake up or otherwise wake up from a
// notification we just want to unconditionally set `state` back to `EMPTY`, either
// consuming a notification or un-flagging ourselves as parked.
_m = self.cvar.wait_timeout(_m, timeout).unwrap().0;
match self.state.swap(EMPTY, SeqCst) {
NOTIFIED => {} // got a notification
PARKED => {} // no notification
n => panic!("inconsistent park_timeout state: {}", n),
}
}
}
}
fn unpark(&self) {
// To ensure the unparked thread will observe any writes we made before this call, we must
// perform a release operation that `park` can synchronize with. To do that we must write
// `NOTIFIED` even if `state` is already `NOTIFIED`. That is why this must be a swap rather
// than a compare-and-swap that returns if it reads `NOTIFIED` on failure.
match self.state.swap(NOTIFIED, SeqCst) {
EMPTY => return, // no one was waiting
NOTIFIED => return, // already unparked
PARKED => {} // gotta go wake someone up
n => panic!("inconsistent state in unpark: {}", n),
}
// There is a period between when the parked thread sets `state` to `PARKED` (or last
// checked `state` in the case of a spurious wakeup) and when it actually waits on `cvar`.
// If we were to notify during this period it would be ignored and then when the parked
// thread went to sleep it would never wake up. Fortunately, it has `lock` locked at this
// stage so we can acquire `lock` to wait until it is ready to receive the notification.
//
// Releasing `lock` before the call to `notify_one` means that when the parked thread wakes
// it doesn't get woken only to have to wait for us to release `lock`.
drop(self.lock.lock());
self.cvar.notify_one();
}
}
+6 -17
View File
@@ -1,12 +1,12 @@
use crate::executor::blocking::PoolWaiter;
use crate::executor::task::JoinHandle;
use crate::executor::thread_pool::{shutdown, Builder, Spawner};
use crate::executor::thread_pool::{shutdown, Spawner};
use std::fmt;
use std::future::Future;
/// Work-stealing based thread pool for executing futures.
pub struct ThreadPool {
pub(crate) struct ThreadPool {
spawner: Spawner,
/// Shutdown waiter
@@ -17,11 +17,6 @@ pub struct ThreadPool {
}
impl ThreadPool {
/// Create a new ThreadPool with default configuration
pub fn new() -> ThreadPool {
Builder::new().build()
}
pub(super) fn from_parts(
spawner: Spawner,
shutdown_rx: shutdown::Receiver,
@@ -38,12 +33,12 @@ impl ThreadPool {
///
/// The `Spawner` handle can be cloned and enables spawning tasks from other
/// threads.
pub fn spawner(&self) -> &Spawner {
pub(crate) fn spawner(&self) -> &Spawner {
&self.spawner
}
/// Spawn a task
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
pub(crate) fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
@@ -55,7 +50,7 @@ impl ThreadPool {
///
/// The future will execute on the current thread, but all spawned tasks
/// will be executed on the thread pool.
pub fn block_on<F>(&self, future: F) -> F::Output
pub(crate) fn block_on<F>(&self, future: F) -> F::Output
where
F: Future,
{
@@ -69,7 +64,7 @@ impl ThreadPool {
}
/// Shutdown the thread pool.
pub fn shutdown_now(&mut self) {
pub(crate) fn shutdown_now(&mut self) {
if self.spawner.workers().close() {
self.shutdown_rx.wait();
}
@@ -77,12 +72,6 @@ impl ThreadPool {
}
}
impl Default for ThreadPool {
fn default() -> ThreadPool {
ThreadPool::new()
}
}
impl fmt::Debug for ThreadPool {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("ThreadPool").finish()
+2 -2
View File
@@ -19,7 +19,7 @@ use std::future::Future;
///
/// [`ThreadPool::spawner`]: struct.ThreadPool.html#method.spawner
#[derive(Clone)]
pub struct Spawner {
pub(crate) struct Spawner {
workers: Arc<worker::Set<Box<dyn Unpark>>>,
}
@@ -29,7 +29,7 @@ impl Spawner {
}
/// Spawn a future onto the thread pool
pub fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
pub(crate) fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
@@ -1,16 +1,20 @@
use crate::executor::loom::sync::atomic::Ordering::{Acquire, Relaxed, Release};
use crate::executor::loom::sync::atomic::{AtomicBool, AtomicUsize};
use crate::executor::loom::sync::{Arc, Mutex};
use crate::executor::park::{Park, Unpark};
use crate::executor::tests::loom_oneshot as oneshot;
use crate::executor::thread_pool::{self, Builder, ThreadPool};
use crate::executor::thread_pool::{self, Builder};
use crate::spawn;
use loom::sync::Notify;
use std::future::Future;
use std::time::Duration;
#[test]
fn pool_multi_spawn() {
loom::model(|| {
let pool = ThreadPool::new();
let pool = Builder::new().build(|_| LoomPark::new());
let c1 = Arc::new(AtomicUsize::new(0));
@@ -44,7 +48,7 @@ fn pool_multi_spawn() {
#[test]
fn only_blocking() {
loom::model(|| {
let mut pool = Builder::new().num_threads(1).build();
let mut pool = Builder::new().num_threads(1).build(|_| LoomPark::new());
let (block_tx, block_rx) = oneshot::channel();
pool.spawn(async move {
@@ -62,7 +66,7 @@ fn only_blocking() {
fn blocking_and_regular() {
const NUM: usize = 3;
loom::model(|| {
let mut pool = Builder::new().num_threads(1).build();
let mut pool = Builder::new().num_threads(1).build(|_| LoomPark::new());
let cnt = Arc::new(AtomicUsize::new(0));
let (block_tx, block_rx) = oneshot::channel();
@@ -96,7 +100,7 @@ fn blocking_and_regular() {
#[test]
fn pool_multi_notify() {
loom::model(|| {
let pool = ThreadPool::new();
let pool = Builder::new().build(|_| LoomPark::new());
let c1 = Arc::new(AtomicUsize::new(0));
@@ -132,7 +136,7 @@ fn pool_multi_notify() {
#[test]
fn pool_shutdown() {
loom::model(|| {
let pool = ThreadPool::new();
let pool = Builder::new().build(|_| LoomPark::new());
pool.spawn(async move {
gated2(true).await;
@@ -149,7 +153,7 @@ fn pool_shutdown() {
#[test]
fn complete_block_on_under_load() {
loom::model(|| {
let pool = ThreadPool::new();
let pool = Builder::new().build(|_| LoomPark::new());
pool.block_on(async {
// Spin hard
@@ -187,9 +191,7 @@ fn gated() -> impl Future<Output = &'static str> {
fn gated2(thread: bool) -> impl Future<Output = &'static str> {
use crate::executor::loom::thread;
use futures_util::future::poll_fn;
use std::sync::Arc;
use std::task::Poll;
let gate = Arc::new(AtomicBool::new(false));
let mut fired = false;
@@ -223,3 +225,46 @@ fn gated2(thread: bool) -> impl Future<Output = &'static str> {
}
})
}
struct LoomPark {
notify: Arc<Notify>,
}
struct LoomUnpark {
notify: Arc<Notify>,
}
impl LoomPark {
fn new() -> LoomPark {
LoomPark {
notify: Arc::new(Notify::new()),
}
}
}
impl Park for LoomPark {
type Unpark = LoomUnpark;
type Error = ();
fn unpark(&self) -> LoomUnpark {
let notify = self.notify.clone();
LoomUnpark { notify }
}
fn park(&mut self) -> Result<(), Self::Error> {
self.notify.wait();
Ok(())
}
fn park_timeout(&mut self, _duration: Duration) -> Result<(), Self::Error> {
self.notify.wait();
Ok(())
}
}
impl Unpark for LoomUnpark {
fn unpark(&self) {
self.notify.notify();
}
}
+10 -12
View File
@@ -63,17 +63,15 @@ fn eagerly_drops_futures() {
let (park_tx, park_rx) = mpsc::sync_channel(0);
let (unpark_tx, unpark_rx) = mpsc::sync_channel(0);
let pool = thread_pool::Builder::new()
.num_threads(4)
.build_with_park(move |_| {
let (tx, rx) = mpsc::channel();
MyPark {
tx: Mutex::new(tx),
rx,
park_tx: park_tx.clone(),
unpark_tx: unpark_tx.clone(),
}
});
let pool = thread_pool::Builder::new().num_threads(4).build(move |_| {
let (tx, rx) = mpsc::channel();
MyPark {
tx: Mutex::new(tx),
rx,
park_tx: park_tx.clone(),
unpark_tx: unpark_tx.clone(),
}
});
struct MyTask {
task_tx: Option<mpsc::Sender<Waker>>,
@@ -166,7 +164,7 @@ fn park_called_at_interval() {
// Use 1 thread to ensure the worker stays busy.
let pool = thread_pool::Builder::new()
.num_threads(1)
.build_with_park(move |idx| {
.build(move |idx| {
assert_eq!(idx, 0);
MyPark {
park_light: park_light_2.clone(),
+1 -18
View File
@@ -16,25 +16,8 @@ thread_local! {
static ON_BLOCK: Cell<Option<*mut dyn FnMut()>> = Cell::new(None)
}
/// 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::executor::thread_pool::blocking(move || {
/// // do some compute-heavy work or call synchronous code
/// });
/// # }
/// ```
#[cfg(feature = "blocking")]
pub fn blocking<F, R>(f: F) -> R
pub(crate) fn blocking<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
+1 -1
View File
@@ -349,7 +349,7 @@ impl Builder {
}
})
})
.build_with_park(move |index| timers[index].lock().unwrap().take().unwrap())
.build(move |index| timers[index].lock().unwrap().take().unwrap())
};
Ok(Runtime {
+1 -7
View File
@@ -41,7 +41,7 @@ pub(crate) use self::registration::Registration;
mod stack;
use self::stack::Stack;
use crate::executor::park::{Park, ParkThread, Unpark};
use crate::executor::park::{Park, Unpark};
use crate::timer::atomic::AtomicU64;
use crate::timer::clock::Clock;
use crate::timer::wheel;
@@ -333,12 +333,6 @@ where
}
}
impl Default for Timer<ParkThread> {
fn default() -> Self {
Timer::new(ParkThread::new())
}
}
impl<T> Park for Timer<T>
where
T: Park,
-17
View File
@@ -1,17 +0,0 @@
#![warn(rust_2018_idioms)]
#[test]
fn block_on_ready() {
let mut enter = tokio::executor::enter().unwrap();
let val = enter.block_on(async { 123 });
assert_eq!(val, 123);
}
#[test]
fn block_on_pending() {
let mut enter = tokio::executor::enter().unwrap();
let val = enter.block_on(async { 123 });
assert_eq!(val, 123);
}
-2
View File
@@ -61,8 +61,6 @@ fn test_drop_on_notify() {
}
}));
let _enter = tokio::executor::enter().unwrap();
{
let handle = reactor.handle();
let _reactor = tokio::net::driver::set_default(&handle);
+3 -5
View File
@@ -58,7 +58,7 @@ fn many_multishot_futures() {
const TRACKS: usize = 50;
for _ in 0..50 {
let rt = rt();
let mut rt = rt();
let mut start_txs = Vec::with_capacity(TRACKS);
let mut final_rxs = Vec::with_capacity(TRACKS);
@@ -102,9 +102,7 @@ fn many_multishot_futures() {
}
{
let mut e = tokio::executor::enter().unwrap();
e.block_on(async move {
rt.block_on(async move {
for mut start_tx in start_txs {
start_tx.send("ping").await.unwrap();
}
@@ -265,7 +263,7 @@ fn blocking() {
for _ in 0..4 {
let block = block.clone();
rt.spawn(async move {
tokio::executor::thread_pool::blocking(move || {
tokio::executor::blocking::in_place(move || {
block.wait();
block.wait();
})