use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::task::Wake;
use alloc::vec::Vec;
use core::future::Future;
use core::pin::Pin;
use core::sync::atomic::{AtomicBool, Ordering};
use core::task::{Context, Poll, Waker};
use crate::core::error::Result;
use crate::core::reactor::{Idle, Reactor, Turn};
use crate::core::time::{Clock, DurNs};
struct Flag(AtomicBool);
impl Wake for Flag {
fn wake(self: Arc<Self>) {
self.0.store(true, Ordering::Release);
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.store(true, Ordering::Release);
}
}
struct Task<F> {
fut: Option<Pin<Box<F>>>,
flag: Arc<Flag>,
waker: Waker,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct RunStats {
pub polls: u64,
pub woke: u64,
pub slept: u64,
pub parked: u64,
pub idle_ns: u64,
}
pub struct Executor<F: Future<Output = ()>> {
tasks: Vec<Task<F>>,
}
impl<F: Future<Output = ()>> Default for Executor<F> {
fn default() -> Self {
Self::new()
}
}
impl<F: Future<Output = ()>> Executor<F> {
#[must_use]
pub fn new() -> Self {
Self { tasks: Vec::new() }
}
pub fn spawn(&mut self, fut: F) {
let flag = Arc::new(Flag(AtomicBool::new(true)));
let waker = Waker::from(Arc::clone(&flag));
self.tasks.push(Task {
fut: Some(Box::pin(fut)),
flag,
waker,
});
}
#[must_use]
pub fn unfinished(&self) -> usize {
self.tasks.iter().filter(|t| t.fut.is_some()).count()
}
pub fn run<C: Clock, I: Idle>(
&mut self,
reactor: &Reactor<'_, C>,
idle: &mut I,
park_bound: DurNs,
) -> Result<RunStats> {
let mut stats = RunStats::default();
loop {
reactor.fire();
let mut progressed = false;
for t in &mut self.tasks {
let Some(fut) = t.fut.as_mut() else { continue };
if !t.flag.0.swap(false, Ordering::Acquire) {
continue;
}
stats.polls += 1;
progressed = true;
if fut
.as_mut()
.poll(&mut Context::from_waker(&t.waker))
.is_ready()
{
t.fut = None;
}
}
let left = self.unfinished();
if left == 0 {
self.tasks.clear();
return Ok(stats);
}
if progressed {
continue;
}
let t0 = reactor.clock().monotonic_ns();
match reactor.turn(idle, park_bound, left)? {
Turn::Woke => stats.woke += 1,
Turn::Slept => stats.slept += 1,
Turn::Parked => stats.parked += 1,
}
stats.idle_ns += reactor.clock().monotonic_ns().since(t0).0;
}
}
}
pub async fn yield_now() {
let mut yielded = false;
core::future::poll_fn(|cx| {
if yielded {
Poll::Ready(())
} else {
yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
})
.await;
}
pub fn block_on<T, F, C, I>(
fut: F,
reactor: &Reactor<'_, C>,
idle: &mut I,
park_bound: DurNs,
) -> Result<(T, RunStats)>
where
F: Future<Output = T>,
C: Clock,
I: Idle,
{
let mut out = None;
let stats = {
let mut ex = Executor::new();
ex.spawn(async {
out = Some(fut.await);
});
ex.run(reactor, idle, park_bound)?
};
match out {
Some(v) => Ok((v, stats)),
None => Err(crate::error!(Stalled, 1)),
}
}
pub fn run_inline<F: Future>(fut: F, site: &str) -> Result<F::Output> {
let mut fut = core::pin::pin!(fut);
match fut.as_mut().poll(&mut Context::from_waker(Waker::noop())) {
Poll::Ready(v) => Ok(v),
Poll::Pending => Err(crate::error!(PendedInline, site)),
}
}
#[cfg(test)]
#[path = "fill_executor_tests.rs"]
mod tests;