use async_task::{Builder as TaskBuilder, Runnable, Task};
use educe::Educe;
use futures_util::stream::FuturesUnordered;
use futures_util::task::AtomicWaker;
use futures_util::StreamExt;
use parking_lot::Mutex;
use slab::Slab;
use smol::future;
use std::cmp::Ordering;
use std::marker::PhantomData;
use std::sync::atomic::AtomicUsize;
use std::sync::{atomic, Arc};
use std::task::Waker;
use tracing::warn;
use crate::util::priority::{DynamicPriorityQueue, HasDynamicPriority, HasStaticPriority, PriorityQueue, StaticPriorityQueue};
use crate::worker::{FindWorkError, FindWorkResult, Work, WorkSource, WorkTask, WorkTaskError, WorkerPool};
trait Metadata: Ord + Send + Sync + 'static {
fn increment_attempts(&self);
}
fn priority_eq<P: HasDynamicPriority>(a: &P, b: &P) -> bool {
a.priority().eq(&b.priority())
}
fn priority_cmp<P: HasDynamicPriority>(a: &P, b: &P) -> Ordering {
a.priority().cmp(&b.priority())
}
fn atomic_usize_eq(a: &AtomicUsize, b: &AtomicUsize) -> bool {
a.load(atomic::Ordering::Relaxed).eq(&b.load(atomic::Ordering::Relaxed))
}
fn atomic_usize_cmp(a: &AtomicUsize, b: &AtomicUsize) -> Ordering {
a.load(atomic::Ordering::Relaxed).cmp(&b.load(atomic::Ordering::Relaxed))
}
#[derive(Educe, Debug)]
#[educe(Deref, PartialEq, Eq, PartialOrd, Ord)]
pub struct StaticMetadata<P: HasStaticPriority> {
#[educe(Deref)]
#[educe(Ord(rank = 0))]
priority: P,
#[educe(Eq(method(atomic_usize_eq)))]
#[educe(Ord(rank = 1, method(atomic_usize_cmp)))]
attempts: AtomicUsize,
}
impl<P: HasStaticPriority + Send + Sync + 'static> Metadata for StaticMetadata<P> {
fn increment_attempts(&self) {
self.attempts.fetch_add(1, atomic::Ordering::AcqRel);
}
}
impl<P: HasStaticPriority> StaticMetadata<P> {
fn new(priority: P) -> Self {
Self {
priority,
attempts: AtomicUsize::new(0),
}
}
#[inline]
pub fn into_inner(self) -> P {
self.priority
}
}
#[derive(Educe, Debug)]
#[educe(Deref, PartialEq, Eq, PartialOrd, Ord)]
pub struct DynamicMetadata<P: HasDynamicPriority> {
#[educe(Deref)]
#[educe(Eq(method(priority_eq)))]
#[educe(Ord(rank = 0, method(priority_cmp)))]
priority: P,
#[educe(Eq(method(atomic_usize_eq)))]
#[educe(Ord(rank = 1, method(atomic_usize_cmp)))]
attempts: AtomicUsize,
}
impl<P: HasDynamicPriority + Send + Sync + 'static> Metadata for DynamicMetadata<P> {
fn increment_attempts(&self) {
self.attempts.fetch_add(1, atomic::Ordering::AcqRel);
}
}
impl<P: HasDynamicPriority> DynamicMetadata<P> {
fn new(priority: P) -> Self {
Self {
priority,
attempts: AtomicUsize::new(0),
}
}
#[inline]
pub fn into_inner(self) -> P {
self.priority
}
}
#[derive(Educe, Debug)]
#[educe(PartialEq, Eq, PartialOrd, Ord)]
pub struct FifoMetadata {
#[educe(Eq(method(atomic_usize_eq)))]
#[educe(Ord(method(atomic_usize_cmp)))]
attempts: AtomicUsize,
}
impl Metadata for FifoMetadata {
fn increment_attempts(&self) {
self.attempts.fetch_add(1, atomic::Ordering::AcqRel);
}
}
impl FifoMetadata {
fn new() -> Self {
Self {
attempts: AtomicUsize::new(0),
}
}
}
fn runnable_ord_eq<M: Metadata>(
a: &Runnable<M>,
b: &Runnable<M>,
) -> bool {
a.metadata().eq(b.metadata())
}
fn runnable_ord_cmp<M: Metadata>(
a: &Runnable<M>,
b: &Runnable<M>,
) -> Ordering {
a.metadata().cmp(b.metadata())
}
#[derive(Educe)]
#[educe(PartialEq, Eq, PartialOrd, Ord)]
struct RunnableWrapper<M: Metadata>(
#[educe(Eq(method(runnable_ord_eq)))]
#[educe(Ord(method(runnable_ord_cmp)))]
Runnable<M>
);
impl<P: HasDynamicPriority + Send + Sync + 'static> HasDynamicPriority for RunnableWrapper<DynamicMetadata<P>> {
type Value = P::Value;
#[inline]
fn priority(&self) -> Self::Value {
self.0.metadata().priority()
}
}
impl<M: Metadata> Work for RunnableWrapper<M> {
type Output = ();
#[inline]
fn execute(self: Box<Self>) -> Self::Output {
(*self).0.run();
}
}
trait ExecutorType: Send + Sync + 'static {
type Metadata: Metadata;
type Queue: PriorityQueue<RunnableWrapper<Self::Metadata>>;
fn queue(&mut self) -> &mut Self::Queue;
}
pub struct StaticPriority<P: HasStaticPriority + Send + Sync + 'static> {
queue: StaticPriorityQueue<RunnableWrapper<StaticMetadata<P>>>,
}
impl<P: HasStaticPriority + Send + Sync + 'static> ExecutorType for StaticPriority<P> {
type Metadata = StaticMetadata<P>;
type Queue = StaticPriorityQueue<RunnableWrapper<Self::Metadata>>;
fn queue(&mut self) -> &mut Self::Queue {
&mut self.queue
}
}
pub struct DynamicPriority<P: HasDynamicPriority + Send + Sync + 'static> {
queue: DynamicPriorityQueue<RunnableWrapper<DynamicMetadata<P>>>,
}
impl<P: HasDynamicPriority + Send + Sync + 'static> ExecutorType for DynamicPriority<P> {
type Metadata = DynamicMetadata<P>;
type Queue = DynamicPriorityQueue<RunnableWrapper<Self::Metadata>>;
fn queue(&mut self) -> &mut Self::Queue {
&mut self.queue
}
}
pub struct Fifo {
queue: StaticPriorityQueue<RunnableWrapper<FifoMetadata>>,
}
impl ExecutorType for Fifo {
type Metadata = FifoMetadata;
type Queue = StaticPriorityQueue<RunnableWrapper<Self::Metadata>>;
fn queue(&mut self) -> &mut Self::Queue {
&mut self.queue
}
}
struct StateInner<S: ExecutorType> {
scheduled: S,
active_tasks: Vec<WorkTask<()>>,
}
struct State<S: ExecutorType> {
inner: Mutex<Option<StateInner<S>>>,
waker: AtomicWaker,
}
impl<S: ExecutorType> State<S> {
fn new(inner: S) -> Self {
Self {
inner: Mutex::new(Some(StateInner {
scheduled: inner,
active_tasks: vec![],
})),
waker: AtomicWaker::new(),
}
}
fn push_work(&self, runnable: Runnable<S::Metadata>) {
let mut guard = self.inner.lock();
let inner = match &mut *guard {
Some(inner) => inner,
None => return,
};
inner.scheduled.queue().push(RunnableWrapper(runnable));
self.waker.wake();
}
}
impl<S: ExecutorType> WorkSource for State<S> {
fn find_work(&self) -> FindWorkResult {
let mut guard = self.inner.lock();
let inner = match &mut *guard {
Some(inner) => inner,
None => return Err(FindWorkError::Disconnected),
};
inner.active_tasks = inner.active_tasks.drain(..)
.filter_map(|task| {
match task.try_poll() {
Ok(result) => match result {
Ok(()) => None,
Err(WorkTaskError::Cancelled) => {
warn!("executor work task cancelled before future completed");
None
}
},
Err(task) => Some(task),
}
})
.collect();
inner.scheduled.queue().pop().map(|r| {
let (work, work_task) = WorkTask::spawn(Box::new(r));
inner.active_tasks.push(work_task);
work
}).ok_or(FindWorkError::NoWork)
}
fn set_worker_waker(&self, waker: &Waker) {
self.waker.register(waker);
}
}
#[allow(private_bounds)]
pub struct Executor<'a, S: ExecutorType> {
state: Arc<State<S>>,
active: Arc<Mutex<Slab<Waker>>>,
_marker: PhantomData<&'a ()>,
}
#[allow(private_bounds)]
impl<'a, S: ExecutorType> Executor<'a, S> {
pub fn is_empty(&self) -> bool {
self.active.lock().is_empty()
}
fn spawn_inner<T: Send + 'a>(
&self,
metadata: S::Metadata,
future: impl Future<Output = T> + Send + 'a,
active: &mut Slab<Waker>,
) -> Task<T, S::Metadata> {
let entry = active.vacant_entry();
let idx = entry.key();
let active_arc = self.active.clone();
let future = call_on_drop::AsyncCallOnDrop::new(
future,
move || drop(active_arc.lock().try_remove(idx)),
);
let task_builder = TaskBuilder::new()
.propagate_panic(true)
.metadata(metadata);
let (runnable, task) = unsafe { task_builder
.spawn_unchecked(|_| future, self.schedule()) };
entry.insert(runnable.waker());
runnable.schedule();
task
}
fn schedule(&self) -> impl Fn(Runnable<S::Metadata>) + Send + Sync + 'a {
let state = Arc::downgrade(&self.state);
move |runnable| {
runnable.metadata().increment_attempts();
let state = state.upgrade()
.expect("executor state dropped before scheduled!");
state.push_work(runnable);
}
}
}
impl<'a, P: HasStaticPriority + Send + Sync + 'static> Executor<'a, StaticPriority<P>> {
pub fn new_static_priority<PP>(pool_priority: PP, worker_pool: &WorkerPool<PP>) -> Self
where
PP: HasStaticPriority + Send + Sync + 'static,
{
let state = Arc::new(State::new(StaticPriority {
queue: Default::default(),
}));
worker_pool.insert_source(pool_priority, Arc::downgrade(&state));
Self {
state,
active: Default::default(),
_marker: PhantomData,
}
}
pub fn spawn<T: Send + 'a>(
&self,
priority: P,
future: impl Future<Output = T> + Send + 'a,
) -> Task<T, StaticMetadata<P>> {
let metadata = StaticMetadata::new(priority);
let mut active = self.active.lock();
self.spawn_inner(metadata, future, &mut *active)
}
pub fn spawn_many<T: Send + 'a, F: Future<Output = T> + Send + 'a>(
&self,
futures: impl IntoIterator<Item = (P, F)>,
handles: &mut impl Extend<Task<T, StaticMetadata<P>>>
) {
let mut active = Some(self.active.lock());
let tasks = futures.into_iter().enumerate()
.map(move |(idx, (priority, fut))| {
let metadata = StaticMetadata::new(priority);
let active_ref = &mut **(active.as_mut().unwrap());
let task = self.spawn_inner(metadata, fut, active_ref);
if idx.wrapping_add(1) % 500 == 0 {
drop(active.take());
active = Some(self.active.lock());
}
task
});
handles.extend(tasks);
}
}
impl<'a, P: HasDynamicPriority + Send + Sync + 'static> Executor<'a, DynamicPriority<P>> {
pub fn new_dynamic_priority<PP>(pool_priority: PP, worker_pool: &WorkerPool<PP>) -> Self
where
PP: HasStaticPriority + Send + Sync + 'static,
{
let state = Arc::new(State::new(DynamicPriority {
queue: Default::default(),
}));
worker_pool.insert_source(pool_priority, Arc::downgrade(&state));
Self {
state,
active: Default::default(),
_marker: PhantomData,
}
}
pub fn spawn<T: Send + 'a>(
&self,
priority: P,
future: impl Future<Output = T> + Send + 'a,
) -> Task<T, DynamicMetadata<P>> {
let metadata = DynamicMetadata::new(priority);
let mut active = self.active.lock();
self.spawn_inner(metadata, future, &mut *active)
}
pub fn spawn_many<T: Send + 'a, F: Future<Output = T> + Send + 'a>(
&self,
futures: impl IntoIterator<Item = (P, F)>,
handles: &mut impl Extend<Task<T, DynamicMetadata<P>>>
) {
let mut active = Some(self.active.lock());
let tasks = futures.into_iter().enumerate()
.map(move |(idx, (priority, fut))| {
let active_ref = &mut **(active.as_mut().unwrap());
let metadata = DynamicMetadata::new(priority);
let task = self.spawn_inner(metadata, fut, active_ref);
if idx.wrapping_add(1) % 500 == 0 {
drop(active.take());
active = Some(self.active.lock());
}
task
});
handles.extend(tasks);
}
}
impl<'a> Executor<'a, Fifo> {
pub fn new_fifo<PP>(pool_priority: PP, worker_pool: &WorkerPool<PP>) -> Self
where
PP: HasStaticPriority + Send + Sync + 'static,
{
let state = Arc::new(State::new(Fifo {
queue: Default::default()
}));
worker_pool.insert_source(pool_priority, Arc::downgrade(&state));
Self {
state,
active: Default::default(),
_marker: PhantomData,
}
}
pub fn spawn<T: Send + 'a>(
&self,
future: impl Future<Output = T> + Send + 'a,
) -> Task<T, FifoMetadata> {
let metadata = FifoMetadata::new();
let mut active = self.active.lock();
self.spawn_inner(metadata, future, &mut *active)
}
pub fn spawn_many<T: Send + 'a, F: Future<Output = T> + Send + 'a>(
&self,
futures: impl IntoIterator<Item = F>,
handles: &mut impl Extend<Task<T, FifoMetadata>>
) {
let mut active = Some(self.active.lock());
let tasks = futures.into_iter().enumerate()
.map(move |(idx, fut)| {
let metadata = FifoMetadata::new();
let active_ref = &mut **(active.as_mut().unwrap());
let task = self.spawn_inner(metadata, fut, active_ref);
if idx.wrapping_add(1) % 500 == 0 {
drop(active.take());
active = Some(self.active.lock());
}
task
});
handles.extend(tasks);
}
}
impl<S: ExecutorType> Drop for Executor<'_, S> {
fn drop(&mut self) {
let state = self.state.inner.lock().take();
{
let mut active = self.active.lock();
for w in active.drain() {
w.wake();
}
}
if let Some(mut state) = state {
let queue = state.scheduled.queue();
while queue.pop().is_some() {}
let mut active_tasks = state.active_tasks.into_iter()
.collect::<FuturesUnordered<_>>();
future::block_on(async move {
while let Some(_) = active_tasks.next().await {}
});
}
}
}
pub type StaticPriorityExecutor<'a, P> = Executor<'a, StaticPriority<P>>;
pub type DynamicPriorityExecutor<'a, P> = Executor<'a, DynamicPriority<P>>;
pub type FifoExecutor<'a> = Executor<'a, Fifo>;
mod call_on_drop {
use pin_project_lite::pin_project;
use std::pin::Pin;
use std::task::{Context, Poll};
pub struct CallOnDrop<F: FnMut()>(F);
impl<F: FnMut()> Drop for CallOnDrop<F> {
fn drop(&mut self) {
self.0();
}
}
pin_project! {
pub struct AsyncCallOnDrop<Fut, Cleanup: FnMut()> {
#[pin]
future: Fut,
cleanup: CallOnDrop<Cleanup>,
}
}
impl<Fut, Cleanup: FnMut()> AsyncCallOnDrop<Fut, Cleanup> {
pub fn new(future: Fut, cleanup: Cleanup) -> Self {
Self {
future,
cleanup: CallOnDrop(cleanup),
}
}
}
impl<Fut: Future, Cleanup: FnMut()> Future for AsyncCallOnDrop<Fut, Cleanup> {
type Output = Fut::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.project().future.poll(cx)
}
}
}
fn _ensure_send_and_sync() {
fn is_send<T: Send>(_: T) {}
fn is_sync<T: Sync>(_: T) {}
fn is_static<T: 'static>(_: T) {}
let pool = WorkerPool::<usize>::builder()
.worker_count(1.try_into().unwrap())
.start();
{
is_send::<Executor<'_, StaticPriority<usize>>>(Executor::new_static_priority(0, &pool));
is_sync::<Executor<'_, StaticPriority<usize>>>(Executor::new_static_priority(0, &pool));
let ex = Executor::<StaticPriority<usize>>::new_static_priority(0, &pool);
is_send(ex.schedule());
is_sync(ex.schedule());
is_static(ex.schedule());
}
{
is_send::<Executor<'_, DynamicPriority<usize>>>(Executor::new_dynamic_priority(0, &pool));
is_sync::<Executor<'_, DynamicPriority<usize>>>(Executor::new_dynamic_priority(0, &pool));
let ex = Executor::<DynamicPriority<usize>>::new_dynamic_priority(0, &pool);
is_send(ex.schedule());
is_sync(ex.schedule());
is_static(ex.schedule());
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::FutureExt;
use parking_lot::RwLock;
#[test]
fn test_executor_drops() {
let workers = WorkerPool::<usize>::builder()
.worker_count(1.try_into().unwrap())
.start();
let executor = Executor::new_static_priority(0, &workers);
let active = Arc::downgrade(&executor.active);
let state = Arc::downgrade(&executor.state);
let task = executor.spawn(0, future::pending::<()>());
drop(executor);
assert!(active.upgrade().is_none());
assert!(state.upgrade().is_none());
let result = future::block_on(task.fallible());
assert!(result.is_none());
}
#[test]
fn test_executor_static_priority() {
let worker_lock = Arc::new(RwLock::new(()));
let workers = WorkerPool::<usize>::builder()
.worker_count(1.try_into().unwrap())
.worker_lock(worker_lock.clone())
.start();
let executor = Executor::new_static_priority(0, &workers);
let output = Mutex::new(vec![]);
let mut tasks = vec![];
{
let _guard = worker_lock.write();
executor.spawn_many(
[
(1, async { output.lock().push(1); }.boxed()),
(0, async { output.lock().push(0); }.boxed()),
(2, async { output.lock().push(2); }.boxed()),
],
&mut tasks,
);
}
for task in tasks {
future::block_on(task);
}
assert_eq!(*output.lock(), [2, 1, 0]);
}
#[test]
fn test_executor_dynamic_priority() {
let worker_lock = Arc::new(RwLock::new(()));
let workers = WorkerPool::<usize>::builder()
.worker_count(1.try_into().unwrap())
.worker_lock(worker_lock.clone())
.start();
let executor = Executor::new_dynamic_priority(0, &workers);
let output = Mutex::new(vec![]);
let mut tasks = vec![];
{
let _guard = worker_lock.write();
executor.spawn_many(
[
(1, async { output.lock().push(1); }.boxed()),
(0, async { output.lock().push(0); }.boxed()),
(2, async { output.lock().push(2); }.boxed()),
],
&mut tasks,
);
}
for task in tasks {
future::block_on(task);
}
assert_eq!(*output.lock(), [2, 1, 0]);
}
}