use std::rc::Rc;
use crate::{CycleOutcome, ExecutorBuilder, ExecutorCore, TaskSet};
#[derive(Debug)]
pub struct Executor {
core: Rc<ExecutorCore>,
}
impl Executor {
#[cfg_attr(test, mutants::skip)] pub fn builder() -> ExecutorBuilder {
ExecutorBuilder::new()
}
#[must_use]
pub(crate) fn new(core: ExecutorCore) -> Self {
Self { core: Rc::new(core) }
}
#[must_use]
pub fn tasks(&self) -> TaskSet {
TaskSet::new(&self.core)
}
#[must_use]
pub fn has_tasks(&self) -> bool {
self.core.has_tasks()
}
#[must_use]
pub fn execute_cycle(&self) -> CycleOutcome {
self.core.execute_cycle()
}
#[cfg_attr(test, mutants::skip)] pub fn begin_shutdown(&self) {
self.core.begin_shutdown();
}
}
impl Drop for Executor {
fn drop(&mut self) {
self.core.owner_dropped();
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use std::cell::{Cell, RefCell};
use std::future::{Ready, poll_fn, ready};
use std::pin::Pin;
use std::sync::{Arc, atomic};
use std::task::{Context, Poll, Waker};
use static_assertions::assert_not_impl_any;
use testing_aids::YieldFuture;
use super::*;
use crate::testing::{TestSubjectFuture, TestWaker, new_guarded_executor};
use crate::{AWAKENED_CAPACITY, JoinHandle, TaskOptions};
trait TestTaskSetExt {
fn add_test<F, R>(&self, future: F) -> JoinHandle<R>
where
F: IntoFuture<Output = R> + 'static,
R: 'static;
}
impl TestTaskSetExt for TaskSet {
fn add_test<F, R>(&self, future: F) -> JoinHandle<R>
where
F: IntoFuture<Output = R> + 'static,
R: 'static,
{
self.add(future, TaskOptions::default())
}
}
struct DropOwnerDuringConversion {
owner: Rc<RefCell<Option<Executor>>>,
tasks: TaskSet,
existing_dropped: Rc<Cell<bool>>,
nested_rejected: Rc<Cell<bool>>,
}
struct DropSignalFuture(Rc<Cell<bool>>);
struct DropOwnerOnPoll {
owner: Rc<RefCell<Option<Executor>>>,
}
impl Future for DropSignalFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}
impl Drop for DropSignalFuture {
fn drop(&mut self) {
self.0.set(true);
}
}
impl Future for DropOwnerOnPoll {
type Output = ();
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
drop(self.owner.borrow_mut().take());
Poll::Pending
}
}
impl IntoFuture for DropOwnerDuringConversion {
type Output = usize;
type IntoFuture = Ready<usize>;
fn into_future(self) -> Self::IntoFuture {
drop(self.owner.borrow_mut().take());
assert!(self.existing_dropped.get(), "owner drop must synchronously cancel existing tasks");
let mut nested = self.tasks.add_test(async { 42 });
let mut cx = Context::from_waker(Waker::noop());
self.nested_rejected.set(matches!(
Pin::new(&mut nested).poll(&mut cx),
Poll::Ready(Err(error)) if error.is_shutdown()
));
ready(7)
}
}
#[test]
fn owner_drop_during_conversion_cancels_tasks_and_rejects_nested_submission() {
let owner = Rc::new(RefCell::new(Some(Executor::builder().build())));
let tasks = owner.borrow().as_ref().unwrap().tasks();
let existing_dropped = Rc::new(Cell::new(false));
let mut existing = tasks.add_test(DropSignalFuture(Rc::clone(&existing_dropped)));
let nested_rejected = Rc::new(Cell::new(false));
let mut outer = tasks.add_test(DropOwnerDuringConversion {
owner: Rc::clone(&owner),
tasks: tasks.clone(),
existing_dropped: Rc::clone(&existing_dropped),
nested_rejected: Rc::clone(&nested_rejected),
});
assert!(owner.borrow().is_none());
assert!(existing_dropped.get());
let mut cx = Context::from_waker(Waker::noop());
assert!(matches!(Pin::new(&mut existing).poll(&mut cx), Poll::Ready(Err(error)) if error.is_shutdown()));
assert!(matches!(Pin::new(&mut outer).poll(&mut cx), Poll::Ready(Err(error)) if error.is_shutdown()));
assert!(nested_rejected.get());
}
#[test]
fn owner_drop_during_active_cycle_defers_cancellation_to_the_cycle() {
let owner = Rc::new(RefCell::new(Some(Executor::builder().build())));
let executor_core = Rc::clone(&owner.borrow().as_ref().unwrap().core);
let mut task = owner
.borrow()
.as_ref()
.unwrap()
.tasks()
.add_test(DropOwnerOnPoll { owner: Rc::clone(&owner) });
assert_eq!(executor_core.execute_cycle(), CycleOutcome::Shutdown);
assert!(owner.borrow().is_none());
let mut cx = Context::from_waker(Waker::noop());
assert!(matches!(Pin::new(&mut task).poll(&mut cx), Poll::Ready(Err(error)) if error.is_shutdown()));
}
#[test]
fn smoke_test() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let task_completed = Rc::new(Cell::new(false));
let future = {
let task_completed = Rc::clone(&task_completed);
async move {
task_completed.set(true);
42
}
};
let mut join_handle = tasks.add_test(future);
let outcome = executor.execute_cycle();
assert!(task_completed.get());
assert_eq!(outcome, CycleOutcome::Suspend);
let mut cx = Context::from_waker(Waker::noop());
let result = Pin::new(&mut join_handle).poll(&mut cx);
assert!(matches!(result, Poll::Ready(Ok(42))));
}
#[test]
fn noop_test() {
let executor = new_guarded_executor(Waker::noop().clone());
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
}
#[test]
fn two_tasks_complete_together() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future1 = async move { 42 };
let future2 = async move { 43 };
let mut join_handle1 = tasks.add_test(future1);
let mut join_handle2 = tasks.add_test(future2);
let outcome = executor.execute_cycle();
assert_eq!(outcome, CycleOutcome::Suspend);
let mut cx = Context::from_waker(Waker::noop());
let result1 = Pin::new(&mut join_handle1).poll(&mut cx);
let result2 = Pin::new(&mut join_handle2).poll(&mut cx);
assert!(matches!(result1, Poll::Ready(Ok(42))));
assert!(matches!(result2, Poll::Ready(Ok(43))));
}
#[test]
fn outcome_continue_on_self_awaken() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future = TestSubjectFuture::new();
let wakes_self = future.wakes_self_on_next_poll();
let _join_handle = tasks.add_test(future);
wakes_self.set(true);
assert_eq!(executor.execute_cycle(), CycleOutcome::Continue);
wakes_self.set(false);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
}
#[test]
fn outcome_continue_on_neighbor_awaken() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future1 = TestSubjectFuture::new();
let future2 = TestSubjectFuture::new();
let first_waker = Rc::new(Cell::new(None::<Waker>));
let on_poll = {
let first_waker = Rc::clone(&first_waker);
move |cx: &mut Context<'_>| match first_waker.take() {
None => first_waker.set(Some(cx.waker().clone())),
Some(waker) => waker.wake(),
}
};
future1.on_poll(on_poll.clone());
future2.on_poll(on_poll);
_ = tasks.add_test(future1);
_ = tasks.add_test(future2);
assert_eq!(executor.execute_cycle(), CycleOutcome::Continue);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
}
#[test]
fn task_completes_after_external_awaken() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future = TestSubjectFuture::new();
let future_waker = future.waker();
let future_complete = future.completes_on_next_poll();
let mut join_handle = tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
future_waker.borrow().as_ref().unwrap().wake_by_ref();
future_complete.set(true);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
let mut cx = Context::from_waker(Waker::noop());
let result = Pin::new(&mut join_handle).poll(&mut cx);
assert!(matches!(result, Poll::Ready(Ok(()))));
}
#[test]
fn mass_awaken() {
const AWAKENED_TASK_COUNT: usize = AWAKENED_CAPACITY + 1;
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let mut join_handles = Vec::with_capacity(AWAKENED_TASK_COUNT);
let mut future_wakers = Vec::with_capacity(AWAKENED_TASK_COUNT);
let mut future_completes = Vec::with_capacity(AWAKENED_TASK_COUNT);
for _ in 0..AWAKENED_TASK_COUNT {
let future = TestSubjectFuture::new();
future_wakers.push(future.waker());
future_completes.push(future.completes_on_next_poll());
join_handles.push(tasks.add_test(future));
}
let inactive_future = TestSubjectFuture::new();
let mut inactive_join_handle = tasks.add_test(inactive_future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
for future_waker in future_wakers {
future_waker.borrow().as_ref().unwrap().wake_by_ref();
}
for future_complete in future_completes {
future_complete.set(true);
}
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
let mut cx = Context::from_waker(Waker::noop());
for mut join_handle in join_handles {
let result = Pin::new(&mut join_handle).poll(&mut cx);
assert!(matches!(result, Poll::Ready(Ok(()))));
}
assert!(matches!(Pin::new(&mut inactive_join_handle).poll(&mut cx), Poll::Pending));
}
#[test]
fn task_not_polled_when_inactive() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future = TestSubjectFuture::new();
let poll_count = Rc::new(Cell::new(0));
future.on_poll({
let poll_count = Rc::clone(&poll_count);
move |_| {
poll_count.set(poll_count.get() + 1);
}
});
_ = tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
assert_eq!(poll_count.get(), 1);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
assert_eq!(poll_count.get(), 1);
}
#[test]
fn shutdown_drops_futures() {
struct SetSignalOnDrop(Rc<Cell<bool>>);
impl Drop for SetSignalOnDrop {
fn drop(&mut self) {
self.0.set(true);
}
}
impl Future for SetSignalOnDrop {
type Output = ();
#[cfg_attr(coverage_nightly, coverage(off))] fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Pending
}
}
let drop_signal = Rc::new(Cell::new(false));
let executor = Executor::builder().build();
let tasks = executor.tasks();
let signal_setter = SetSignalOnDrop(Rc::clone(&drop_signal));
tasks.add_test(signal_setter);
assert!(!drop_signal.get());
executor.begin_shutdown();
assert!(drop_signal.get());
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn shutdown_without_tasks_is_immediate() {
let executor = Executor::builder().build();
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn shutdown_with_unreferenced_tasks_is_immediate() {
let executor = Executor::builder().build();
let tasks = executor.tasks();
tasks.add_test(TestSubjectFuture::new());
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn join_handle_reports_shutdown_without_delaying_shutdown() {
let executor = Executor::builder().build();
let tasks = executor.tasks();
let mut join_handle = tasks.add_test(TestSubjectFuture::new());
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
let mut cx = Context::from_waker(Waker::noop());
let result = Pin::new(&mut join_handle).poll(&mut cx);
assert!(matches!(result, Poll::Ready(Err(error)) if error.is_shutdown()));
}
#[test]
fn enqueue_after_shutdown_reports_shutdown() {
let executor = Executor::builder().build();
let tasks = executor.tasks();
executor.begin_shutdown();
let mut handle = tasks.add_test(async { 42 });
let result = Pin::new(&mut handle).poll(&mut Context::from_waker(Waker::noop()));
assert!(matches!(result, Poll::Ready(Err(error)) if error.is_shutdown()));
}
#[test]
fn direct_drop_cancels_tasks_safely() {
let executor = Executor::builder().build();
let mut handle = executor.tasks().add_test(std::future::pending::<()>());
drop(executor);
let result = Pin::new(&mut handle).poll(&mut Context::from_waker(Waker::noop()));
assert!(matches!(result, Poll::Ready(Err(error)) if error.is_shutdown()));
}
#[test]
fn task_wake_also_wakes_owner() {
let owner_waker = Arc::new(TestWaker::new());
let executor = new_guarded_executor(Arc::clone(&owner_waker).into());
let tasks = executor.tasks();
let future = TestSubjectFuture::new();
let future_waker = future.waker();
tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
assert!(!owner_waker.awakened.load(atomic::Ordering::Relaxed));
future_waker.borrow().as_ref().unwrap().wake_by_ref();
assert!(owner_waker.awakened.load(atomic::Ordering::Relaxed));
}
#[test]
fn task_awaits_yield_future() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let future = async {
let yield_future = YieldFuture::default();
yield_future.await;
};
_ = tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Continue);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
}
#[test]
fn reentrant_task_addition() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let inner_task_completed = Rc::new(Cell::new(false));
let future = {
let inner_task_completed = Rc::clone(&inner_task_completed);
let tasks = tasks.clone();
async move {
tasks.add_test(async move {
inner_task_completed.set(true);
});
}
};
tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Continue);
assert!(!inner_task_completed.get());
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
assert!(inner_task_completed.get());
}
#[test]
fn reentrant_tasks_wait_for_next_cycle_in_registration_order() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let polls = Rc::new(RefCell::new(Vec::new()));
let mut cycles = Vec::new();
for batch in 0..2 {
for index in 0..2 {
let tasks_for_child = tasks.clone();
let polls = Rc::clone(&polls);
tasks.add_test(async move {
polls.borrow_mut().push(batch * 4 + index);
tasks_for_child.add_test(async move {
polls.borrow_mut().push(batch * 4 + index + 2);
});
});
}
cycles.push((executor.execute_cycle(), polls.borrow().clone()));
cycles.push((executor.execute_cycle(), polls.borrow().clone()));
}
assert_eq!(
cycles,
[
(CycleOutcome::Continue, vec![0, 1]),
(CycleOutcome::Suspend, vec![0, 1, 2, 3]),
(CycleOutcome::Continue, vec![0, 1, 2, 3, 4, 5]),
(CycleOutcome::Suspend, vec![0, 1, 2, 3, 4, 5, 6, 7]),
]
);
}
#[test]
fn new_tasks_precede_awakened_tasks() {
let executor = new_guarded_executor(Waker::noop().clone());
let tasks = executor.tasks();
let polls = Rc::new(RefCell::new(Vec::new()));
let future = TestSubjectFuture::new();
let future_waker = future.waker();
let completes = future.completes_on_next_poll();
future.on_poll({
let polls = Rc::clone(&polls);
move |_| polls.borrow_mut().push(2)
});
tasks.add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
polls.borrow_mut().clear();
future_waker.borrow().as_ref().unwrap().wake_by_ref();
completes.set(true);
for index in 0..2 {
let polls = Rc::clone(&polls);
tasks.add_test(async move {
polls.borrow_mut().push(index);
});
}
assert_eq!(
(executor.execute_cycle(), polls.borrow().clone()),
(CycleOutcome::Suspend, vec![0, 1, 2])
);
}
#[test]
fn completed_task_waker_is_noop_after_executor_shutdown() {
let executor = Executor::builder().build();
let waker = Rc::new(RefCell::new(None));
let polls = Rc::new(Cell::new(0));
executor.tasks().add_test(poll_fn({
let waker = Rc::clone(&waker);
let polls = Rc::clone(&polls);
move |cx| {
polls.set(polls.get() + 1);
*waker.borrow_mut() = Some(cx.waker().clone());
Poll::Ready(())
}
}));
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
drop(executor);
waker.borrow().as_ref().unwrap().wake_by_ref();
assert_eq!(polls.get(), 1);
}
#[test]
fn duplicate_wakes_keep_first_notification_order() {
let executor = new_guarded_executor(Waker::noop().clone());
let polls = Rc::new(RefCell::new(Vec::new()));
let mut wakers = Vec::new();
for index in 0..3 {
let future = TestSubjectFuture::new();
wakers.push(future.waker());
future.on_poll({
let polls = Rc::clone(&polls);
move |_| polls.borrow_mut().push(index)
});
executor.tasks().add_test(future);
}
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
for _ in 0..3 {
polls.borrow_mut().clear();
for index in [2, 0, 2, 1] {
wakers[index].borrow().as_ref().unwrap().wake_by_ref();
}
assert_eq!(
(executor.execute_cycle(), polls.borrow().clone()),
(CycleOutcome::Suspend, vec![2, 0, 1])
);
}
}
#[test]
fn cross_thread_waker_clone_wake_and_drop() {
let executor = new_guarded_executor(Waker::noop().clone());
let future = TestSubjectFuture::new();
let future_waker = future.waker();
let completes = future.completes_on_next_poll();
let mut handle = executor.tasks().add_test(future);
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
let waker = future_waker.borrow().as_ref().unwrap().clone();
std::thread::spawn(move || {
let clone = waker.clone();
drop(waker);
clone.wake();
})
.join()
.unwrap();
completes.set(true);
let outcome = executor.execute_cycle();
let result = Pin::new(&mut handle).poll(&mut Context::from_waker(Waker::noop()));
assert_eq!((outcome, result), (CycleOutcome::Suspend, Poll::Ready(Ok(()))));
}
#[test]
fn retained_waiter_does_not_delay_shutdown() {
let executor = Executor::builder().build();
let tasks = executor.tasks();
let retained_waiter = Rc::new(RefCell::new(None));
let task = tasks.add_test({
let retained_waiter = Rc::clone(&retained_waiter);
poll_fn(move |cx| {
*retained_waiter.borrow_mut() = Some(cx.waker().clone());
Poll::Pending::<usize>
})
});
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
let retained_waiter = retained_waiter.borrow_mut().take().unwrap();
drop(task);
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
drop(executor);
retained_waiter.wake_by_ref();
retained_waiter.wake();
}
#[test]
fn thread_safety() {
assert_not_impl_any!(Executor: Send, Sync);
}
}