mod join;
pub use join::{JoinHandle, YieldNow, until_stalled, yield_now};
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::future::Future;
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::task::{Context, Poll, Wake, Waker};
use futures::FutureExt;
use parking_lot::Mutex;
use rand::{RngExt, SeedableRng};
use rand_chacha::ChaCha8Rng;
const EXEC_RNG_SALT: u64 = 0x6578_6563_7363_6864;
const DRAIN_POLL_BOUND: u64 = 1_000_000;
#[derive(Debug)]
pub(crate) struct TaskMeta {
pub(crate) id: u64,
pub(crate) name: Arc<str>,
}
pub(crate) type ExecRunnable = async_task::Runnable<TaskMeta>;
thread_local! {
static CURRENT: RefCell<Option<Handle>> = const { RefCell::new(None) };
static IN_TASK: Cell<bool> = const { Cell::new(false) };
}
pub(crate) fn in_task() -> bool {
IN_TASK.with(Cell::get)
}
struct Shared {
queue: Mutex<Vec<ExecRunnable>>,
wakers: Mutex<HashMap<u64, Waker>>,
next_id: AtomicU64,
}
struct Unregister {
id: u64,
shared: Arc<Shared>,
}
impl Drop for Unregister {
fn drop(&mut self) {
self.shared.wakers.lock().remove(&self.id);
}
}
struct DriverWaker {
woken: AtomicBool,
}
impl Wake for DriverWaker {
fn wake(self: Arc<Self>) {
self.wake_by_ref();
}
fn wake_by_ref(self: &Arc<Self>) {
self.woken.store(true, Ordering::Release);
}
}
#[derive(Clone)]
struct Handle {
shared: Arc<Shared>,
}
impl Handle {
fn spawn<T, F>(&self, name: &str, future: F) -> JoinHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
let id = self.shared.next_id.fetch_add(1, Ordering::Relaxed);
let meta = TaskMeta {
id,
name: Arc::from(name),
};
let caught = AssertUnwindSafe(future).catch_unwind();
let unregister = Unregister {
id,
shared: Arc::clone(&self.shared),
};
let wrapped = async move {
let _unregister = unregister;
caught.await
};
let schedule_shared = Arc::clone(&self.shared);
let schedule = move |runnable: ExecRunnable| {
schedule_shared.queue.lock().push(runnable);
};
let (runnable, task) = async_task::Builder::new()
.metadata(meta)
.spawn(move |_| wrapped, schedule);
self.shared.wakers.lock().insert(id, runnable.waker());
runnable.schedule();
JoinHandle::new(task.fallible())
}
}
struct CurrentGuard;
impl CurrentGuard {
fn install(handle: Handle) -> Self {
CURRENT.with(|current| {
let mut current = current.borrow_mut();
assert!(
current.is_none(),
"Executor::block_on is not reentrant: an executor is already running on this thread"
);
*current = Some(handle);
});
Self
}
}
impl Drop for CurrentGuard {
fn drop(&mut self) {
CURRENT.with(|current| current.borrow_mut().take());
}
}
pub fn spawn<T, F>(name: &str, future: F) -> JoinHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
CURRENT.with(|current| {
current
.borrow()
.as_ref()
.expect("executor::spawn called outside Executor::block_on")
.spawn(name, future)
})
}
pub struct Executor {
shared: Arc<Shared>,
rng: ChaCha8Rng,
seed: u64,
}
impl Executor {
#[must_use]
pub fn new(seed: u64) -> Self {
Self {
shared: Arc::new(Shared {
queue: Mutex::new(Vec::new()),
wakers: Mutex::new(HashMap::new()),
next_id: AtomicU64::new(0),
}),
rng: ChaCha8Rng::seed_from_u64(seed ^ EXEC_RNG_SALT),
seed,
}
}
pub fn block_on<F: Future>(&mut self, main: F) -> F::Output {
let _guard = CurrentGuard::install(Handle {
shared: Arc::clone(&self.shared),
});
let mut main = std::pin::pin!(main);
let driver_waker = Arc::new(DriverWaker {
woken: AtomicBool::new(false),
});
let waker = Waker::from(Arc::clone(&driver_waker));
let mut cx = Context::from_waker(&waker);
loop {
driver_waker.woken.store(false, Ordering::Relaxed);
if let Poll::Ready(output) = main.as_mut().poll(&mut cx) {
return output;
}
self.run_until_stalled();
assert!(
driver_waker.woken.load(Ordering::Acquire) || !self.shared.queue.lock().is_empty(),
"deterministic executor stalled (seed {}): the driver future is not \
woken and no task is runnable, so every remaining future awaits a \
wake that can never arrive (note: the executor is single-threaded; \
a wake signaled from another OS thread is unsupported and can race \
this check)",
self.seed
);
}
}
fn run_until_stalled(&mut self) {
let mut polls: u64 = 0;
loop {
let runnable = {
let mut queue = self.shared.queue.lock();
if queue.is_empty() {
break;
}
let index = if queue.len() == 1 {
0
} else {
self.rng.random_range(0..queue.len())
};
queue.swap_remove(index)
};
polls += 1;
assert!(
polls < DRAIN_POLL_BOUND,
"executor drain exceeded {} polls (seed {}): task '{}' appears to \
busy-yield forever without an external wake",
DRAIN_POLL_BOUND,
self.seed,
runnable.metadata().name,
);
tracing::trace!(
task_id = runnable.metadata().id,
task = %runnable.metadata().name,
"executor poll"
);
IN_TASK.with(|flag| flag.set(true));
runnable.run();
IN_TASK.with(|flag| flag.set(false));
}
}
}
impl Drop for Executor {
fn drop(&mut self) {
let installed = CURRENT.with(|current| {
let mut current = current.borrow_mut();
if current.is_none() {
*current = Some(Handle {
shared: Arc::clone(&self.shared),
});
true
} else {
false
}
});
let wakers: Vec<Waker> = self
.shared
.wakers
.lock()
.drain()
.map(|(_, waker)| waker)
.collect();
for waker in wakers {
waker.wake();
}
loop {
let runnable = self.shared.queue.lock().pop();
match runnable {
Some(runnable) => drop(runnable),
None => break,
}
}
if installed {
CURRENT.with(|current| current.borrow_mut().take());
}
}
}
impl std::fmt::Debug for Executor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Executor")
.field("seed", &self.seed)
.field("ready_tasks", &self.shared.queue.lock().len())
.field("live_tasks", &self.shared.wakers.lock().len())
.finish_non_exhaustive()
}
}