use crate::cx::Cx;
use crate::time::{Elapsed, Sleep, sleep_until};
use crate::types::{Budget, Time};
use std::future::Future;
use std::marker::Unpin;
use std::time::Duration;
pub trait BudgetTimeExt {
fn remaining_duration(&self, now: Time) -> Option<Duration>;
fn deadline_sleep(&self) -> Option<Sleep>;
fn deadline_elapsed(&self, now: Time) -> bool;
}
impl BudgetTimeExt for Budget {
#[inline]
fn remaining_duration(&self, now: Time) -> Option<Duration> {
self.deadline.map(|d| {
if now >= d {
Duration::ZERO
} else {
Duration::from_nanos(d.as_nanos() - now.as_nanos())
}
})
}
#[inline]
fn deadline_sleep(&self) -> Option<Sleep> {
self.deadline.map(sleep_until)
}
#[inline]
fn deadline_elapsed(&self, now: Time) -> bool {
self.deadline.is_some_and(|d| d <= now)
}
}
pub async fn budget_sleep(cx: &Cx, duration: Duration, now: Time) -> Result<(), Elapsed> {
let budget = cx.budget();
let remaining = BudgetTimeExt::remaining_duration(&budget, now);
let effective_duration = match remaining {
Some(rem) if rem < duration => rem,
_ => duration,
};
if effective_duration.is_zero() && BudgetTimeExt::deadline_elapsed(&budget, now) {
let deadline = budget.deadline.unwrap_or(now);
return Err(Elapsed::new(deadline));
}
crate::time::sleep(now, effective_duration).await;
if effective_duration < duration {
let deadline = budget.deadline.unwrap_or(now);
return Err(Elapsed::new(deadline));
}
Ok(())
}
pub async fn budget_timeout<F: Future + Unpin>(
cx: &Cx,
duration: Duration,
future: F,
now: Time,
) -> Result<F::Output, Elapsed> {
let budget = cx.budget();
let remaining = BudgetTimeExt::remaining_duration(&budget, now);
let effective_timeout = match remaining {
Some(rem) if rem < duration => rem,
_ => duration,
};
crate::time::timeout(now, effective_timeout, future).await
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cx::Cx;
use crate::test_utils::init_test_logging;
use crate::types::{Budget, RegionId, TaskId};
use crate::util::ArenaIndex;
use std::time::Duration;
fn init_test(name: &str) {
init_test_logging();
crate::test_phase!(name);
}
fn test_cx(budget: Budget) -> Cx {
Cx::new(
RegionId::from_arena(ArenaIndex::new(0, 0)),
TaskId::from_arena(ArenaIndex::new(0, 0)),
budget,
)
}
#[test]
fn test_budget_sleep() {
init_test("test_budget_sleep");
let now = Time::ZERO;
let deadline = now.saturating_add_nanos(5_000_000); let budget = Budget::new().with_deadline(deadline);
let cx = test_cx(budget);
futures_lite::future::block_on(async {
let result = budget_sleep(&cx, Duration::from_secs(10), now).await;
let is_err = result.is_err();
crate::assert_with_log!(is_err, "budget sleep errors", true, is_err);
});
crate::test_complete!("test_budget_sleep");
}
}