use std::collections::VecDeque;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex, MutexGuard};
use std::task::{Context, Poll};
use async_task::Runnable;
use inset_embedder::Dispatcher;
use crate::background;
const POLL_BUDGET: usize = 100_000;
type ReadyQueue = Arc<Mutex<VecDeque<Runnable>>>;
pub(crate) struct ForegroundExecutor {
shared: ExecutorHandle,
}
impl ForegroundExecutor {
pub(crate) fn new(dispatcher: Arc<dyn Dispatcher>) -> ForegroundExecutor {
ForegroundExecutor {
shared: ExecutorHandle {
ready: Arc::default(),
dispatcher,
},
}
}
pub(crate) fn spawn<R: 'static>(&self, future: impl Future<Output = R> + 'static) -> Task<R> {
self.shared.spawn(future)
}
pub(crate) fn run_in_background<T: Send + 'static>(
&self,
work: impl FnOnce() -> T + Send + 'static,
) -> Task<T> {
self.shared.run_in_background(work)
}
pub(crate) fn handle(&self) -> ExecutorHandle {
self.shared.clone()
}
}
impl Drop for ForegroundExecutor {
fn drop(&mut self) {
loop {
let queued: Vec<_> = lock(&self.shared.ready).drain(..).collect();
if queued.is_empty() {
break;
}
drop(queued);
}
}
}
#[derive(Clone)]
pub(crate) struct ExecutorHandle {
ready: ReadyQueue,
dispatcher: Arc<dyn Dispatcher>,
}
impl ExecutorHandle {
pub(crate) fn spawn<R: 'static>(&self, future: impl Future<Output = R> + 'static) -> Task<R> {
spawn_on(&self.ready, &self.dispatcher, future)
}
pub(crate) fn run_in_background<T: Send + 'static>(
&self,
work: impl FnOnce() -> T + Send + 'static,
) -> Task<T> {
self.spawn(background::run(&self.dispatcher, work))
}
pub(crate) fn poll_ready_tasks(&self) -> usize {
let mut polled = 0usize;
loop {
let next = lock(&self.ready).pop_front();
let Some(runnable) = next else {
break;
};
polled += 1;
assert!(
polled <= POLL_BUDGET,
"tasks did not converge after {POLL_BUDGET} polls; are two tasks waking each \
other in a cycle?"
);
runnable.run();
}
polled
}
}
fn spawn_on<R: 'static>(
ready: &ReadyQueue,
dispatcher: &Arc<dyn Dispatcher>,
future: impl Future<Output = R> + 'static,
) -> Task<R> {
let ready = Arc::clone(ready);
let host = Arc::clone(dispatcher);
let schedule = move |runnable| {
lock(&ready).push_back(runnable);
host.wake_now();
};
let (runnable, task) = async_task::spawn_local(future, schedule);
runnable.schedule();
Task::spawned(task)
}
fn lock(ready: &ReadyQueue) -> MutexGuard<'_, VecDeque<Runnable>> {
ready
.lock()
.expect("the ready queue is never poisoned: nothing runs under its lock")
}
pub struct Task<T> {
inner: TaskInner<T>,
}
enum TaskInner<T> {
Ready(Option<T>),
Spawned(Option<async_task::Task<T>>),
}
impl<T> Task<T> {
pub fn ready(value: T) -> Task<T> {
Task {
inner: TaskInner::Ready(Some(value)),
}
}
pub(crate) fn spawned(task: async_task::Task<T>) -> Task<T> {
Task {
inner: TaskInner::Spawned(Some(task)),
}
}
}
impl<T> Unpin for Task<T> {}
impl<T> Future for Task<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
match &mut self.get_mut().inner {
TaskInner::Ready(value) => Poll::Ready(
value
.take()
.expect("a Task is polled until it completes, then never again"),
),
TaskInner::Spawned(task) => {
let task = task
.as_mut()
.expect("a Task is polled until it completes, then never again");
Pin::new(task).poll(cx)
}
}
}
}
impl<T> Drop for Task<T> {
fn drop(&mut self) {
if let TaskInner::Spawned(task) = &mut self.inner
&& let Some(task) = task.take()
{
task.detach();
}
}
}
#[cfg(test)]
mod tests {
use std::cell::Cell;
use std::future::poll_fn;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::task::Waker;
use inset_embedder::Instant;
use super::*;
struct CountingDispatcher(AtomicUsize);
impl Dispatcher for CountingDispatcher {
fn wake_at(&self, _timer_wakeup: Option<Instant>) {}
fn wake_now(&self) {
self.0.fetch_add(1, Ordering::AcqRel);
}
fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
work();
}
}
fn executor() -> (ForegroundExecutor, Arc<CountingDispatcher>) {
let host = Arc::new(CountingDispatcher(AtomicUsize::new(0)));
let executor = ForegroundExecutor::new(Arc::clone(&host) as Arc<dyn Dispatcher>);
(executor, host)
}
fn turns_asked(host: &CountingDispatcher) -> usize {
host.0.load(Ordering::Acquire)
}
#[test]
fn a_spawn_asks_the_host_for_a_turn_and_the_task_runs_in_it() {
let (executor, host) = executor();
drop(executor.spawn(async {}));
assert_eq!(turns_asked(&host), 1);
assert_eq!(executor.handle().poll_ready_tasks(), 1);
}
#[test]
fn a_task_woken_from_another_thread_asks_for_a_turn_and_runs_in_it() {
let (executor, host) = executor();
let parked: Arc<Mutex<Option<Waker>>> = Arc::default();
let done = Arc::new(AtomicBool::new(false));
let ran = Rc::new(Cell::new(false));
let (slot, flag, seen) = (Arc::clone(&parked), Arc::clone(&done), Rc::clone(&ran));
drop(executor.spawn(async move {
poll_fn(|cx| {
if flag.load(Ordering::Acquire) {
Poll::Ready(())
} else {
*slot.lock().unwrap() = Some(cx.waker().clone());
Poll::Pending
}
})
.await;
seen.set(true);
}));
let handle = executor.handle();
handle.poll_ready_tasks();
assert_eq!(
turns_asked(&host),
1,
"the spawn asked for a wake, which parked the task"
);
std::thread::spawn(move || {
done.store(true, Ordering::Release);
let waker = parked.lock().unwrap().take();
waker.expect("the wake parked the waker").wake();
})
.join()
.unwrap();
assert_eq!(
turns_asked(&host),
2,
"the worker's wake asks the host for another wake"
);
assert!(!ran.get(), "nothing runs outside the main thread's wake");
handle.poll_ready_tasks();
assert!(ran.get());
}
}