Merge branch 'master' into stream_from_iter
commit
2f3c867d44
@ -0,0 +1,42 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_std::sync::Mutex;
|
||||
use async_std::task;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn create(b: &mut Bencher) {
|
||||
b.iter(|| Mutex::new(()));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn contention(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(run(10, 1000)));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn no_contention(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(run(1, 10000)));
|
||||
}
|
||||
|
||||
async fn run(task: usize, iter: usize) {
|
||||
let m = Arc::new(Mutex::new(()));
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for _ in 0..task {
|
||||
let m = m.clone();
|
||||
tasks.push(task::spawn(async move {
|
||||
for _ in 0..iter {
|
||||
let _ = m.lock().await;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for t in tasks {
|
||||
t.await;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use async_std::task;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn block_on(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(async {}));
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::fuse::Fuse;
|
||||
use crate::future::Future;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::Stream;
|
||||
use crate::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
// Lexicographically compares the elements of this `Stream` with those
|
||||
// of another.
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct EqFuture<L: Stream, R: Stream> {
|
||||
#[pin]
|
||||
l: Fuse<L>,
|
||||
#[pin]
|
||||
r: Fuse<R>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Stream, R: Stream> EqFuture<L, R>
|
||||
where
|
||||
L::Item: PartialEq<R::Item>,
|
||||
{
|
||||
pub(super) fn new(l: L, r: R) -> Self {
|
||||
EqFuture {
|
||||
l: l.fuse(),
|
||||
r: r.fuse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Stream, R: Stream> Future for EqFuture<L, R>
|
||||
where
|
||||
L: Stream + Sized,
|
||||
R: Stream + Sized,
|
||||
L::Item: PartialEq<R::Item>,
|
||||
{
|
||||
type Output = bool;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
||||
loop {
|
||||
let l_val = futures_core::ready!(this.l.as_mut().poll_next(cx));
|
||||
let r_val = futures_core::ready!(this.r.as_mut().poll_next(cx));
|
||||
|
||||
if this.l.done && this.r.done {
|
||||
return Poll::Ready(true);
|
||||
}
|
||||
|
||||
match (l_val, r_val) {
|
||||
(Some(l), Some(r)) if l != r => {
|
||||
return Poll::Ready(false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
use std::cmp::{Ord, Ordering};
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::stream::Stream;
|
||||
use crate::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct MinFuture<S, F, T> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
_compare: PhantomData<F>,
|
||||
min: Option<T>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F, T> MinFuture<S, F, T> {
|
||||
pub(super) fn new(stream: S) -> Self {
|
||||
Self {
|
||||
stream,
|
||||
_compare: PhantomData,
|
||||
min: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, F> Future for MinFuture<S, F, S::Item>
|
||||
where
|
||||
S: Stream,
|
||||
S::Item: Ord,
|
||||
F: FnMut(&S::Item, &S::Item) -> Ordering,
|
||||
{
|
||||
type Output = Option<S::Item>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
let next = futures_core::ready!(this.stream.poll_next(cx));
|
||||
|
||||
match next {
|
||||
Some(new) => {
|
||||
cx.waker().wake_by_ref();
|
||||
match this.min.take() {
|
||||
None => *this.min = Some(new),
|
||||
|
||||
Some(old) => match new.cmp(&old) {
|
||||
Ordering::Less => *this.min = Some(new),
|
||||
_ => *this.min = Some(old),
|
||||
},
|
||||
}
|
||||
Poll::Pending
|
||||
}
|
||||
None => Poll::Ready(this.min.take()),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::fuse::Fuse;
|
||||
use crate::future::Future;
|
||||
use crate::prelude::*;
|
||||
use crate::stream::Stream;
|
||||
use crate::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
// Lexicographically compares the elements of this `Stream` with those
|
||||
// of another.
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct NeFuture<L: Stream, R: Stream> {
|
||||
#[pin]
|
||||
l: Fuse<L>,
|
||||
#[pin]
|
||||
r: Fuse<R>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Stream, R: Stream> NeFuture<L, R>
|
||||
where
|
||||
L::Item: PartialEq<R::Item>,
|
||||
{
|
||||
pub(super) fn new(l: L, r: R) -> Self {
|
||||
Self {
|
||||
l: l.fuse(),
|
||||
r: r.fuse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: Stream, R: Stream> Future for NeFuture<L, R>
|
||||
where
|
||||
L: Stream + Sized,
|
||||
R: Stream + Sized,
|
||||
L::Item: PartialEq<R::Item>,
|
||||
{
|
||||
type Output = bool;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
||||
loop {
|
||||
let l_val = futures_core::ready!(this.l.as_mut().poll_next(cx));
|
||||
let r_val = futures_core::ready!(this.r.as_mut().poll_next(cx));
|
||||
|
||||
if this.l.done || this.r.done {
|
||||
return Poll::Ready(false);
|
||||
}
|
||||
|
||||
match (l_val, r_val) {
|
||||
(Some(l), Some(r)) if l == r => {
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
return Poll::Ready(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
use std::pin::Pin;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::stream::Stream;
|
||||
use crate::task::{Context, Poll};
|
||||
|
||||
pin_project! {
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct PositionFuture<S, P> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
predicate: P,
|
||||
index:usize,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P> PositionFuture<S, P> {
|
||||
pub(super) fn new(stream: S, predicate: P) -> Self {
|
||||
PositionFuture {
|
||||
stream,
|
||||
predicate,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, P> Future for PositionFuture<S, P>
|
||||
where
|
||||
S: Stream,
|
||||
P: FnMut(&S::Item) -> bool,
|
||||
{
|
||||
type Output = Option<usize>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
let next = futures_core::ready!(this.stream.poll_next(cx));
|
||||
|
||||
match next {
|
||||
Some(v) if (this.predicate)(&v) => Poll::Ready(Some(*this.index)),
|
||||
Some(_) => {
|
||||
cx.waker().wake_by_ref();
|
||||
*this.index += 1;
|
||||
Poll::Pending
|
||||
}
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
//! A common utility for building synchronization primitives.
|
||||
//!
|
||||
//! When an async operation is blocked, it needs to register itself somewhere so that it can be
|
||||
//! notified later on. The `WakerSet` type helps with keeping track of such async operations and
|
||||
//! notifying them when they may make progress.
|
||||
|
||||
use std::cell::UnsafeCell;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crossbeam_utils::Backoff;
|
||||
use slab::Slab;
|
||||
|
||||
use crate::task::{Context, Waker};
|
||||
|
||||
/// Set when the entry list is locked.
|
||||
#[allow(clippy::identity_op)]
|
||||
const LOCKED: usize = 1 << 0;
|
||||
|
||||
/// Set when there are tasks for `notify_one()` to wake.
|
||||
const NOTIFY_ONE: usize = 1 << 1;
|
||||
|
||||
/// Set when there are tasks for `notify_all()` to wake.
|
||||
const NOTIFY_ALL: usize = 1 << 2;
|
||||
|
||||
/// Inner representation of `WakerSet`.
|
||||
struct Inner {
|
||||
/// A list of entries in the set.
|
||||
///
|
||||
/// Each entry has an optional waker associated with the task that is executing the operation.
|
||||
/// If the waker is set to `None`, that means the task has been woken up but hasn't removed
|
||||
/// itself from the `WakerSet` yet.
|
||||
///
|
||||
/// The key of each entry is its index in the `Slab`.
|
||||
entries: Slab<Option<Waker>>,
|
||||
|
||||
/// The number of entries that have the waker set to `None`.
|
||||
none_count: usize,
|
||||
}
|
||||
|
||||
/// A set holding wakers.
|
||||
pub struct WakerSet {
|
||||
/// Holds three bits: `LOCKED`, `NOTIFY_ONE`, and `NOTIFY_ALL`.
|
||||
flag: AtomicUsize,
|
||||
|
||||
/// A set holding wakers.
|
||||
inner: UnsafeCell<Inner>,
|
||||
}
|
||||
|
||||
impl WakerSet {
|
||||
/// Creates a new `WakerSet`.
|
||||
#[inline]
|
||||
pub fn new() -> WakerSet {
|
||||
WakerSet {
|
||||
flag: AtomicUsize::new(0),
|
||||
inner: UnsafeCell::new(Inner {
|
||||
entries: Slab::new(),
|
||||
none_count: 0,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts a waker for a blocked operation and returns a key associated with it.
|
||||
pub fn insert(&self, cx: &Context<'_>) -> usize {
|
||||
let w = cx.waker().clone();
|
||||
self.lock().entries.insert(Some(w))
|
||||
}
|
||||
|
||||
/// Updates the waker of a previously inserted entry.
|
||||
pub fn update(&self, key: usize, cx: &Context<'_>) {
|
||||
let mut inner = self.lock();
|
||||
|
||||
match &mut inner.entries[key] {
|
||||
None => {
|
||||
// Fill in the waker.
|
||||
let w = cx.waker().clone();
|
||||
inner.entries[key] = Some(w);
|
||||
inner.none_count -= 1;
|
||||
}
|
||||
Some(w) => {
|
||||
// Replace the waker if the existing one is different.
|
||||
if !w.will_wake(cx.waker()) {
|
||||
*w = cx.waker().clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the waker of a completed operation.
|
||||
pub fn complete(&self, key: usize) {
|
||||
let mut inner = self.lock();
|
||||
if inner.entries.remove(key).is_none() {
|
||||
inner.none_count -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the waker of a cancelled operation.
|
||||
pub fn cancel(&self, key: usize) {
|
||||
let mut inner = self.lock();
|
||||
if inner.entries.remove(key).is_none() {
|
||||
inner.none_count -= 1;
|
||||
|
||||
// The operation was cancelled and notified so notify another operation instead.
|
||||
if let Some((_, opt_waker)) = inner.entries.iter_mut().next() {
|
||||
// If there is no waker in this entry, that means it was already woken.
|
||||
if let Some(w) = opt_waker.take() {
|
||||
w.wake();
|
||||
inner.none_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifies one blocked operation.
|
||||
#[inline]
|
||||
pub fn notify_one(&self) {
|
||||
// Use `SeqCst` ordering to synchronize with `Lock::drop()`.
|
||||
if self.flag.load(Ordering::SeqCst) & NOTIFY_ONE != 0 {
|
||||
self.notify(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifies all blocked operations.
|
||||
// TODO: Delete this attribute when `crate::sync::channel()` is stabilized.
|
||||
#[cfg(feature = "unstable")]
|
||||
#[inline]
|
||||
pub fn notify_all(&self) {
|
||||
// Use `SeqCst` ordering to synchronize with `Lock::drop()`.
|
||||
if self.flag.load(Ordering::SeqCst) & NOTIFY_ALL != 0 {
|
||||
self.notify(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Notifies blocked operations, either one or all of them.
|
||||
fn notify(&self, all: bool) {
|
||||
let mut inner = &mut *self.lock();
|
||||
|
||||
for (_, opt_waker) in inner.entries.iter_mut() {
|
||||
// If there is no waker in this entry, that means it was already woken.
|
||||
if let Some(w) = opt_waker.take() {
|
||||
w.wake();
|
||||
inner.none_count += 1;
|
||||
}
|
||||
if !all {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Locks the list of entries.
|
||||
#[cold]
|
||||
fn lock(&self) -> Lock<'_> {
|
||||
let backoff = Backoff::new();
|
||||
while self.flag.fetch_or(LOCKED, Ordering::Acquire) & LOCKED != 0 {
|
||||
backoff.snooze();
|
||||
}
|
||||
Lock { waker_set: self }
|
||||
}
|
||||
}
|
||||
|
||||
/// A guard holding a `WakerSet` locked.
|
||||
struct Lock<'a> {
|
||||
waker_set: &'a WakerSet,
|
||||
}
|
||||
|
||||
impl Drop for Lock<'_> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
let mut flag = 0;
|
||||
|
||||
// If there is at least one entry and all are `Some`, then `notify_one()` has work to do.
|
||||
if !self.entries.is_empty() && self.none_count == 0 {
|
||||
flag |= NOTIFY_ONE;
|
||||
}
|
||||
|
||||
// If there is at least one `Some` entry, then `notify_all()` has work to do.
|
||||
if self.entries.len() - self.none_count > 0 {
|
||||
flag |= NOTIFY_ALL;
|
||||
}
|
||||
|
||||
// Use `SeqCst` ordering to synchronize with `WakerSet::lock_to_notify()`.
|
||||
self.waker_set.flag.store(flag, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Lock<'_> {
|
||||
type Target = Inner;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Inner {
|
||||
unsafe { &*self.waker_set.inner.get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Lock<'_> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Inner {
|
||||
unsafe { &mut *self.waker_set.inner.get() }
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
use crate::task::Task;
|
||||
|
||||
/// Returns a handle to the current task.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if not called within the context of a task created by [`block_on`],
|
||||
/// [`spawn`], or [`Builder::spawn`].
|
||||
///
|
||||
/// [`block_on`]: fn.block_on.html
|
||||
/// [`spawn`]: fn.spawn.html
|
||||
/// [`Builder::spawn`]: struct.Builder.html#method.spawn
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// println!("The name of this task is {:?}", task::current().name());
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn current() -> Task {
|
||||
Task::get_current(|t| t.clone())
|
||||
.expect("`task::current()` called outside the context of a task")
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
//! Task executor.
|
||||
//!
|
||||
//! API bindings between `crate::task` and this module are very simple:
|
||||
//!
|
||||
//! * The only export is the `schedule` function.
|
||||
//! * The only import is the `crate::task::Runnable` type.
|
||||
|
||||
pub(crate) use pool::schedule;
|
||||
|
||||
use sleepers::Sleepers;
|
||||
|
||||
mod pool;
|
||||
mod sleepers;
|
@ -0,0 +1,140 @@
|
||||
use std::cell::UnsafeCell;
|
||||
use std::iter;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossbeam_deque::{Injector, Stealer, Worker};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::task::executor::Sleepers;
|
||||
use crate::task::Runnable;
|
||||
use crate::utils::{abort_on_panic, random};
|
||||
|
||||
/// The state of an executor.
|
||||
struct Pool {
|
||||
/// The global queue of tasks.
|
||||
injector: Injector<Runnable>,
|
||||
|
||||
/// Handles to local queues for stealing work from worker threads.
|
||||
stealers: Vec<Stealer<Runnable>>,
|
||||
|
||||
/// Used for putting idle workers to sleep and notifying them when new tasks come in.
|
||||
sleepers: Sleepers,
|
||||
}
|
||||
|
||||
/// Global executor that runs spawned tasks.
|
||||
static POOL: Lazy<Pool> = Lazy::new(|| {
|
||||
let num_threads = num_cpus::get().max(1);
|
||||
let mut stealers = Vec::new();
|
||||
|
||||
// Spawn worker threads.
|
||||
for _ in 0..num_threads {
|
||||
let worker = Worker::new_fifo();
|
||||
stealers.push(worker.stealer());
|
||||
|
||||
thread::Builder::new()
|
||||
.name("async-std/executor".to_string())
|
||||
.spawn(|| abort_on_panic(|| main_loop(worker)))
|
||||
.expect("cannot start a thread driving tasks");
|
||||
}
|
||||
|
||||
Pool {
|
||||
injector: Injector::new(),
|
||||
stealers,
|
||||
sleepers: Sleepers::new(),
|
||||
}
|
||||
});
|
||||
|
||||
thread_local! {
|
||||
/// Local task queue associated with the current worker thread.
|
||||
static QUEUE: UnsafeCell<Option<Worker<Runnable>>> = UnsafeCell::new(None);
|
||||
}
|
||||
|
||||
/// Schedules a new runnable task for execution.
|
||||
pub(crate) fn schedule(task: Runnable) {
|
||||
QUEUE.with(|queue| {
|
||||
let local = unsafe { (*queue.get()).as_ref() };
|
||||
|
||||
// If the current thread is a worker thread, push the task into its local task queue.
|
||||
// Otherwise, push it into the global task queue.
|
||||
match local {
|
||||
None => POOL.injector.push(task),
|
||||
Some(q) => q.push(task),
|
||||
}
|
||||
});
|
||||
|
||||
// Notify a sleeping worker that new work just came in.
|
||||
POOL.sleepers.notify_one();
|
||||
}
|
||||
|
||||
/// Main loop running a worker thread.
|
||||
fn main_loop(local: Worker<Runnable>) {
|
||||
// Initialize the local task queue.
|
||||
QUEUE.with(|queue| unsafe { *queue.get() = Some(local) });
|
||||
|
||||
// The number of times the thread didn't find work in a row.
|
||||
let mut step = 0;
|
||||
|
||||
loop {
|
||||
// Try to find a runnable task.
|
||||
match find_runnable() {
|
||||
Some(task) => {
|
||||
// Found. Now run the task.
|
||||
task.run();
|
||||
step = 0;
|
||||
}
|
||||
None => {
|
||||
// Yield the current thread or put it to sleep.
|
||||
match step {
|
||||
0..=2 => {
|
||||
thread::yield_now();
|
||||
step += 1;
|
||||
}
|
||||
3 => {
|
||||
thread::sleep(Duration::from_micros(10));
|
||||
step += 1;
|
||||
}
|
||||
_ => {
|
||||
POOL.sleepers.wait();
|
||||
step = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the next runnable task.
|
||||
fn find_runnable() -> Option<Runnable> {
|
||||
let pool = &*POOL;
|
||||
|
||||
QUEUE.with(|queue| {
|
||||
let local = unsafe { (*queue.get()).as_ref().unwrap() };
|
||||
|
||||
// Pop a task from the local queue, if not empty.
|
||||
local.pop().or_else(|| {
|
||||
// Otherwise, we need to look for a task elsewhere.
|
||||
iter::repeat_with(|| {
|
||||
// Try stealing a batch of tasks from the global queue.
|
||||
pool.injector
|
||||
.steal_batch_and_pop(&local)
|
||||
// Or try stealing a batch of tasks from one of the other threads.
|
||||
.or_else(|| {
|
||||
// First, pick a random starting point in the list of local queues.
|
||||
let len = pool.stealers.len();
|
||||
let start = random(len as u32) as usize;
|
||||
|
||||
// Try stealing a batch of tasks from each local queue starting from the
|
||||
// chosen point.
|
||||
let (l, r) = pool.stealers.split_at(start);
|
||||
let rotated = r.iter().chain(l.iter());
|
||||
rotated.map(|s| s.steal_batch_and_pop(&local)).collect()
|
||||
})
|
||||
})
|
||||
// Loop while no task was stolen and any steal operation needs to be retried.
|
||||
.find(|s| !s.is_retry())
|
||||
// Extract the stolen task, if there is one.
|
||||
.and_then(|s| s.success())
|
||||
})
|
||||
})
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::task::{Context, Poll, Task};
|
||||
|
||||
/// A handle that awaits the result of a task.
|
||||
///
|
||||
/// Dropping a [`JoinHandle`] will detach the task, meaning that there is no longer
|
||||
/// a handle to the task and no way to `join` on it.
|
||||
///
|
||||
/// Created when a task is [spawned].
|
||||
///
|
||||
/// [spawned]: fn.spawn.html
|
||||
#[derive(Debug)]
|
||||
pub struct JoinHandle<T>(async_task::JoinHandle<T, Task>);
|
||||
|
||||
unsafe impl<T> Send for JoinHandle<T> {}
|
||||
unsafe impl<T> Sync for JoinHandle<T> {}
|
||||
|
||||
impl<T> JoinHandle<T> {
|
||||
/// Creates a new `JoinHandle`.
|
||||
pub(crate) fn new(inner: async_task::JoinHandle<T, Task>) -> JoinHandle<T> {
|
||||
JoinHandle(inner)
|
||||
}
|
||||
|
||||
/// Returns a handle to the underlying task.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let handle = task::spawn(async {
|
||||
/// 1 + 2
|
||||
/// });
|
||||
/// println!("id = {}", handle.task().id());
|
||||
/// #
|
||||
/// # })
|
||||
pub fn task(&self) -> &Task {
|
||||
self.0.tag()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for JoinHandle<T> {
|
||||
type Output = T;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match Pin::new(&mut self.0).poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => panic!("cannot await the result of a panicked task"),
|
||||
Poll::Ready(Some(val)) => Poll::Ready(val),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
use std::iter;
|
||||
use std::thread;
|
||||
|
||||
use crossbeam_deque::{Injector, Stealer, Worker};
|
||||
use kv_log_macro::trace;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use super::sleepers::Sleepers;
|
||||
use super::task;
|
||||
use super::task_local;
|
||||
use super::worker;
|
||||
use super::{Builder, JoinHandle};
|
||||
use crate::future::Future;
|
||||
use crate::utils::abort_on_panic;
|
||||
|
||||
/// Spawns a task.
|
||||
///
|
||||
/// This function is similar to [`std::thread::spawn`], except it spawns an asynchronous task.
|
||||
///
|
||||
/// [`std::thread`]: https://doc.rust-lang.org/std/thread/fn.spawn.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let handle = task::spawn(async {
|
||||
/// 1 + 2
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(handle.await, 3);
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn spawn<F, T>(future: F) -> JoinHandle<T>
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
Builder::new().spawn(future).expect("cannot spawn future")
|
||||
}
|
||||
|
||||
pub(crate) struct Pool {
|
||||
pub injector: Injector<task::Runnable>,
|
||||
pub stealers: Vec<Stealer<task::Runnable>>,
|
||||
pub sleepers: Sleepers,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
/// Spawn a future onto the pool.
|
||||
pub fn spawn<F, T>(&self, future: F, builder: Builder) -> JoinHandle<T>
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let tag = task::Tag::new(builder.name);
|
||||
|
||||
// Log this `spawn` operation.
|
||||
let child_id = tag.task_id().as_u64();
|
||||
let parent_id = worker::get_task(|t| t.id().as_u64()).unwrap_or(0);
|
||||
|
||||
trace!("spawn", {
|
||||
parent_id: parent_id,
|
||||
child_id: child_id,
|
||||
});
|
||||
|
||||
// Wrap the future into one that drops task-local variables on exit.
|
||||
let future = unsafe { task_local::add_finalizer(future) };
|
||||
|
||||
// Wrap the future into one that logs completion on exit.
|
||||
let future = async move {
|
||||
let res = future.await;
|
||||
trace!("spawn completed", {
|
||||
parent_id: parent_id,
|
||||
child_id: child_id,
|
||||
});
|
||||
res
|
||||
};
|
||||
|
||||
let (task, handle) = async_task::spawn(future, worker::schedule, tag);
|
||||
task.schedule();
|
||||
JoinHandle::new(handle)
|
||||
}
|
||||
|
||||
/// Find the next runnable task to run.
|
||||
pub fn find_task(&self, local: &Worker<task::Runnable>) -> Option<task::Runnable> {
|
||||
// Pop a task from the local queue, if not empty.
|
||||
local.pop().or_else(|| {
|
||||
// Otherwise, we need to look for a task elsewhere.
|
||||
iter::repeat_with(|| {
|
||||
// Try stealing a batch of tasks from the injector queue.
|
||||
self.injector
|
||||
.steal_batch_and_pop(local)
|
||||
// Or try stealing a bach of tasks from one of the other threads.
|
||||
.or_else(|| {
|
||||
self.stealers
|
||||
.iter()
|
||||
.map(|s| s.steal_batch_and_pop(local))
|
||||
.collect()
|
||||
})
|
||||
})
|
||||
// Loop while no task was stolen and any steal operation needs to be retried.
|
||||
.find(|s| !s.is_retry())
|
||||
// Extract the stolen task, if there is one.
|
||||
.and_then(|s| s.success())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn get() -> &'static Pool {
|
||||
lazy_static! {
|
||||
static ref POOL: Pool = {
|
||||
let num_threads = num_cpus::get().max(1);
|
||||
let mut stealers = Vec::new();
|
||||
|
||||
// Spawn worker threads.
|
||||
for _ in 0..num_threads {
|
||||
let worker = Worker::new_fifo();
|
||||
stealers.push(worker.stealer());
|
||||
|
||||
thread::Builder::new()
|
||||
.name("async-task-driver".to_string())
|
||||
.spawn(|| abort_on_panic(|| worker::main_loop(worker)))
|
||||
.expect("cannot start a thread driving tasks");
|
||||
}
|
||||
|
||||
Pool {
|
||||
injector: Injector::new(),
|
||||
stealers,
|
||||
sleepers: Sleepers::new(),
|
||||
}
|
||||
};
|
||||
}
|
||||
&*POOL
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
use crate::future::Future;
|
||||
use crate::task::{Builder, JoinHandle};
|
||||
|
||||
/// Spawns a task.
|
||||
///
|
||||
/// This function is similar to [`std::thread::spawn`], except it spawns an asynchronous task.
|
||||
///
|
||||
/// [`std::thread`]: https://doc.rust-lang.org/std/thread/fn.spawn.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// let handle = task::spawn(async {
|
||||
/// 1 + 2
|
||||
/// });
|
||||
///
|
||||
/// assert_eq!(handle.await, 3);
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn spawn<F, T>(future: F) -> JoinHandle<T>
|
||||
where
|
||||
F: Future<Output = T> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
Builder::new().spawn(future).expect("cannot spawn task")
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// A unique identifier for a task.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// task::block_on(async {
|
||||
/// println!("id = {:?}", task::current().id());
|
||||
/// })
|
||||
/// ```
|
||||
#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)]
|
||||
pub struct TaskId(pub(crate) u64);
|
||||
|
||||
impl TaskId {
|
||||
/// Generates a new `TaskId`.
|
||||
pub(crate) fn generate() -> TaskId {
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
if id > u64::max_value() / 2 {
|
||||
std::process::abort();
|
||||
}
|
||||
TaskId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TaskId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
|
||||
use crossbeam_deque::Worker;
|
||||
|
||||
use super::pool;
|
||||
use super::task;
|
||||
use super::Task;
|
||||
use crate::utils::abort_on_panic;
|
||||
|
||||
/// Returns a handle to the current task.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if not called within the context of a task created by [`block_on`],
|
||||
/// [`spawn`], or [`Builder::spawn`].
|
||||
///
|
||||
/// [`block_on`]: fn.block_on.html
|
||||
/// [`spawn`]: fn.spawn.html
|
||||
/// [`Builder::spawn`]: struct.Builder.html#method.spawn
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// println!("The name of this task is {:?}", task::current().name());
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
pub fn current() -> Task {
|
||||
get_task(|task| task.clone()).expect("`task::current()` called outside the context of a task")
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static TAG: Cell<*const task::Tag> = Cell::new(ptr::null_mut());
|
||||
}
|
||||
|
||||
pub(crate) fn set_tag<F, R>(tag: *const task::Tag, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
struct ResetTag<'a>(&'a Cell<*const task::Tag>);
|
||||
|
||||
impl Drop for ResetTag<'_> {
|
||||
fn drop(&mut self) {
|
||||
self.0.set(ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
TAG.with(|t| {
|
||||
t.set(tag);
|
||||
let _guard = ResetTag(t);
|
||||
|
||||
f()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_task<F, R>(f: F) -> Option<R>
|
||||
where
|
||||
F: FnOnce(&Task) -> R,
|
||||
{
|
||||
let res = TAG.try_with(|tag| unsafe { tag.get().as_ref().map(task::Tag::task).map(f) });
|
||||
|
||||
match res {
|
||||
Ok(Some(val)) => Some(val),
|
||||
Ok(None) | Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static IS_WORKER: Cell<bool> = Cell::new(false);
|
||||
static QUEUE: Cell<Option<Worker<task::Runnable>>> = Cell::new(None);
|
||||
}
|
||||
|
||||
pub(crate) fn is_worker() -> bool {
|
||||
IS_WORKER.with(|is_worker| is_worker.get())
|
||||
}
|
||||
|
||||
fn get_queue<F: FnOnce(&Worker<task::Runnable>) -> T, T>(f: F) -> T {
|
||||
QUEUE.with(|queue| {
|
||||
let q = queue.take().unwrap();
|
||||
let ret = f(&q);
|
||||
queue.set(Some(q));
|
||||
ret
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn schedule(task: task::Runnable) {
|
||||
if is_worker() {
|
||||
get_queue(|q| q.push(task));
|
||||
} else {
|
||||
pool::get().injector.push(task);
|
||||
}
|
||||
pool::get().sleepers.notify_one();
|
||||
}
|
||||
|
||||
pub(crate) fn main_loop(worker: Worker<task::Runnable>) {
|
||||
IS_WORKER.with(|is_worker| is_worker.set(true));
|
||||
QUEUE.with(|queue| queue.set(Some(worker)));
|
||||
|
||||
loop {
|
||||
match get_queue(|q| pool::get().find_task(q)) {
|
||||
Some(task) => set_tag(task.tag(), || abort_on_panic(|| task.run())),
|
||||
None => pool::get().sleepers.wait(),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue