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 execute_cycle(&self) -> CycleOutcome {
self.core.execute_cycle()
}
#[cfg_attr(test, mutants::skip)] pub fn begin_shutdown(&self) {
self.core.begin_shutdown();
}
}
#[cfg(test)]
mod tests {
use std::cell::{Cell, RefCell};
use std::future::poll_fn;
use std::pin::Pin;
use std::sync::{Arc, atomic};
use std::task::{Context, Poll, Waker};
use std::time::Duration;
use static_assertions::assert_not_impl_any;
use testing_aids::{YieldFuture, assert_panic};
use super::*;
use crate::testing::{TestSubjectFuture, TestWaker, new_guarded_executor};
use crate::{AWAKENED_CAPACITY, ShutdownTimeoutBehavior};
#[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(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(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(future1);
let mut join_handle2 = tasks.add(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(42)));
assert!(matches!(result2, Poll::Ready(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(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(future1);
_ = tasks.add(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(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(())));
}
#[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(future));
}
let inactive_future = TestSubjectFuture::new();
let mut inactive_join_handle = tasks.add(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(())));
}
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(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 = unsafe { Executor::builder().build() };
let tasks = executor.tasks();
let signal_setter = SetSignalOnDrop(Rc::clone(&drop_signal));
tasks.add(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 = unsafe { Executor::builder().build() };
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
#[should_panic(expected = "shutdown timeout must be representable")]
fn unrepresentable_shutdown_timeout_panics_when_shutdown_begins() {
let executor = unsafe { Executor::builder().shutdown_timeout(Duration::MAX).build() };
executor.begin_shutdown();
}
#[test]
fn shutdown_with_unreferenced_tasks_is_immediate() {
let executor = unsafe { Executor::builder().build() };
let tasks = executor.tasks();
tasks.add(TestSubjectFuture::new());
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn join_handle_delays_shutdown() {
let executor = unsafe { Executor::builder().build() };
let tasks = executor.tasks();
let join_handle = tasks.add(TestSubjectFuture::new());
executor.begin_shutdown();
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
drop(join_handle);
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn join_handle_poll_panics_during_shutdown() {
let executor = unsafe { Executor::builder().build() };
let tasks = executor.tasks();
let mut join_handle = tasks.add(TestSubjectFuture::new());
executor.begin_shutdown();
let mut cx = Context::from_waker(Waker::noop());
assert_panic!(Pin::new(&mut join_handle).poll(&mut cx));
assert_eq!(executor.execute_cycle(), CycleOutcome::Shutdown);
}
#[test]
fn panic_if_enqueue_after_shutdown() {
let executor = unsafe { Executor::builder().build() };
let tasks = executor.tasks();
executor.begin_shutdown();
let future = async { 42 };
assert_panic!(tasks.add(future));
}
#[test]
fn panic_if_dirty_drop() {
let executor = unsafe { Executor::builder().build() };
assert_panic!(drop(executor));
}
#[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(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(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(async move {
inner_task_completed.set(true);
});
}
};
tasks.add(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(async move {
polls.borrow_mut().push(batch * 4 + index);
tasks_for_child.add(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(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(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_survives_until_released() {
let executor = unsafe { Executor::builder().build() };
let waker = Rc::new(RefCell::new(None));
let polls = Rc::new(Cell::new(0));
executor.tasks().add(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::Suspend);
waker.borrow().as_ref().unwrap().wake_by_ref();
assert_eq!(executor.execute_cycle(), CycleOutcome::Suspend);
drop(waker);
assert_eq!((executor.execute_cycle(), polls.get()), (CycleOutcome::Shutdown, 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(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(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(())));
}
#[test]
#[should_panic]
fn shutdown_times_out_with_leaked_join_handle() {
let executor = unsafe {
Executor::builder()
.shutdown_timeout(Duration::ZERO)
.shutdown_timeout_behavior(ShutdownTimeoutBehavior::Panic)
.build()
};
let tasks = executor.tasks();
let leaked_join_handle = tasks.add(async {});
executor.begin_shutdown();
_ = executor.execute_cycle();
drop(leaked_join_handle);
}
#[test]
#[should_panic]
fn shutdown_times_out_with_leaked_waiter() {
let executor = unsafe {
Executor::builder()
.shutdown_timeout(Duration::ZERO)
.shutdown_timeout_behavior(ShutdownTimeoutBehavior::Panic)
.build()
};
let tasks = executor.tasks();
let leaked_waiter = Rc::new(RefCell::new(None));
tasks.add({
let leaked_waiter = Rc::clone(&leaked_waiter);
poll_fn(move |cx| {
*leaked_waiter.borrow_mut() = Some(cx.waker().clone());
Poll::Pending::<usize>
})
});
_ = executor.execute_cycle();
executor.begin_shutdown();
_ = executor.execute_cycle();
drop(leaked_waiter);
}
#[test]
fn thread_safety() {
assert_not_impl_any!(Executor: Send, Sync);
}
}