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]
#[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);
}
}