use crate::core::estimator::Outcome;
use crate::core::plan::{Park, WaitPlan};
use crate::core::time::{Clock, DurNs, MonoNs, Timer};
#[expect(
async_fn_in_trait,
reason = "the waiter is single-threaded by design; no Send bound is wanted"
)]
pub trait CompletionPort {
type Error;
fn query(&mut self) -> Result<bool, Self::Error>;
async fn block(&mut self) -> Result<(), Self::Error>;
async fn poll(&mut self) -> Result<(), Self::Error>;
fn arm(&mut self) -> Result<(), Self::Error>;
async fn park(&mut self, bound: DurNs) -> Result<bool, Self::Error>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WaitKind {
Blocked,
Immediate,
Late,
Parked,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WaitReport {
pub kind: WaitKind,
pub outcome: Option<Outcome>,
pub wait_ns: u64,
pub slept_ns: u64,
pub parks: u64,
pub backstop_queries: u64,
}
pub async fn wait<P, C, T>(
plan: &WaitPlan,
sleep_ns: Option<u64>,
port: &mut P,
clock: &C,
timer: &T,
) -> Result<WaitReport, P::Error>
where
P: CompletionPort,
C: Clock,
T: Timer,
{
let t0 = clock.monotonic_ns();
let since = |c: &C| c.monotonic_ns().since(t0).0;
let mut report = WaitReport {
kind: WaitKind::Blocked,
outcome: None,
wait_ns: 0,
slept_ns: 0,
parks: 0,
backstop_queries: 0,
};
if plan.park == Park::Block {
port.block().await?;
report.wait_ns = since(clock);
return Ok(report);
}
if port.query()? {
report.wait_ns = since(clock);
report.kind = WaitKind::Immediate;
report.outcome = Some(Outcome::Exact {
residual_ns: report.wait_ns,
});
return Ok(report);
}
if matches!(plan.park, Park::FlagWait { .. }) {
port.arm()?;
}
if let Some(s) = plan.predict.and(sleep_ns) {
timer.sleep_until(MonoNs(t0.0.saturating_add(s))).await;
report.slept_ns = since(clock);
if port.query()? {
report.wait_ns = since(clock);
report.kind = WaitKind::Late;
report.outcome = Some(Outcome::Late {
woke_ns: report.slept_ns,
});
return Ok(report);
}
}
if let Park::FlagWait {
park_ns,
backstop_ns,
} = plan.park
{
let mut last_query = clock.monotonic_ns();
loop {
report.parks += 1;
if port.park(DurNs(park_ns)).await? {
break;
}
let now = clock.monotonic_ns();
if now.since(last_query).0 >= backstop_ns {
report.backstop_queries += 1;
last_query = now;
if port.query()? {
break;
}
}
}
}
port.poll().await?;
report.wait_ns = since(clock);
report.kind = WaitKind::Parked;
report.outcome = Some(Outcome::Exact {
residual_ns: report.wait_ns,
});
Ok(report)
}
#[cfg(test)]
#[path = "wait_tests.rs"]
mod tests;