use crate::cx::Scope;
use crate::types::{Policy, Time};
use std::time::Duration;
#[inline]
fn duration_to_nanos(duration: Duration) -> u64 {
duration.as_nanos().min(u128::from(u64::MAX)) as u64
}
#[must_use]
#[inline]
pub fn with_deadline<'a, P: Policy>(scope: &Scope<'a, P>, deadline: Time) -> Scope<'a, P> {
let current_budget = scope.budget();
let new_deadline = current_budget
.deadline
.map_or(deadline, |existing| existing.min(deadline));
let new_budget = current_budget.with_deadline(new_deadline);
Scope::new(scope.region_id(), new_budget)
}
#[must_use]
#[inline]
pub fn with_timeout<'a, P: Policy>(
scope: &Scope<'a, P>,
duration: Duration,
now: Time,
) -> Scope<'a, P> {
let deadline = now.saturating_add_nanos(duration_to_nanos(duration));
with_deadline(scope, deadline)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Budget;
use crate::types::policy::FailFast;
use crate::util::ArenaIndex;
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
fn test_region() -> crate::types::RegionId {
crate::types::RegionId::from_arena(ArenaIndex::new(0, 0))
}
#[test]
fn with_deadline_sets_deadline_on_scope_without_one() {
init_test("with_deadline_sets_deadline_on_scope_without_one");
let scope = Scope::<FailFast>::new(test_region(), Budget::INFINITE);
crate::assert_with_log!(
scope.budget().deadline.is_none(),
"no initial deadline",
true,
scope.budget().deadline.is_none()
);
let deadline = Time::from_secs(10);
let new_scope = with_deadline(&scope, deadline);
crate::assert_with_log!(
new_scope.budget().deadline == Some(deadline),
"deadline set",
Some(deadline),
new_scope.budget().deadline
);
crate::assert_with_log!(
new_scope.region_id() == test_region(),
"region preserved",
test_region(),
new_scope.region_id()
);
crate::test_complete!("with_deadline_sets_deadline_on_scope_without_one");
}
#[test]
fn with_deadline_preserves_tighter_existing_deadline() {
init_test("with_deadline_preserves_tighter_existing_deadline");
let budget = Budget::INFINITE.with_deadline(Time::from_secs(5));
let scope = Scope::<FailFast>::new(test_region(), budget);
let new_scope = with_deadline(&scope, Time::from_secs(10));
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::from_secs(5)),
"tighter deadline preserved",
Some(Time::from_secs(5)),
new_scope.budget().deadline
);
crate::test_complete!("with_deadline_preserves_tighter_existing_deadline");
}
#[test]
fn with_deadline_tightens_when_new_is_earlier() {
init_test("with_deadline_tightens_when_new_is_earlier");
let budget = Budget::INFINITE.with_deadline(Time::from_secs(10));
let scope = Scope::<FailFast>::new(test_region(), budget);
let new_scope = with_deadline(&scope, Time::from_secs(3));
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::from_secs(3)),
"tighter deadline applied",
Some(Time::from_secs(3)),
new_scope.budget().deadline
);
crate::test_complete!("with_deadline_tightens_when_new_is_earlier");
}
#[test]
fn with_timeout_computes_absolute_deadline() {
init_test("with_timeout_computes_absolute_deadline");
let scope = Scope::<FailFast>::new(test_region(), Budget::INFINITE);
let now = Time::from_secs(100);
let duration = Duration::from_secs(5);
let new_scope = with_timeout(&scope, duration, now);
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::from_secs(105)),
"deadline = now + duration",
Some(Time::from_secs(105)),
new_scope.budget().deadline
);
crate::test_complete!("with_timeout_computes_absolute_deadline");
}
#[test]
fn with_timeout_respects_existing_tighter_deadline() {
init_test("with_timeout_respects_existing_tighter_deadline");
let budget = Budget::INFINITE.with_deadline(Time::from_secs(102));
let scope = Scope::<FailFast>::new(test_region(), budget);
let now = Time::from_secs(100);
let duration = Duration::from_secs(10);
let new_scope = with_timeout(&scope, duration, now);
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::from_secs(102)),
"existing tighter deadline preserved",
Some(Time::from_secs(102)),
new_scope.budget().deadline
);
crate::test_complete!("with_timeout_respects_existing_tighter_deadline");
}
#[test]
fn with_timeout_saturates_at_time_max_for_huge_duration() {
init_test("with_timeout_saturates_at_time_max_for_huge_duration");
let scope = Scope::<FailFast>::new(test_region(), Budget::INFINITE);
let now = Time::from_secs(1);
let new_scope = with_timeout(&scope, Duration::MAX, now);
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::MAX),
"huge duration saturates to Time::MAX",
Some(Time::MAX),
new_scope.budget().deadline
);
crate::test_complete!("with_timeout_saturates_at_time_max_for_huge_duration");
}
#[test]
fn with_timeout_saturates_when_now_is_near_time_max() {
init_test("with_timeout_saturates_when_now_is_near_time_max");
let scope = Scope::<FailFast>::new(test_region(), Budget::INFINITE);
let now = Time::MAX.saturating_sub_nanos(5);
let new_scope = with_timeout(&scope, Duration::from_nanos(10), now);
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::MAX),
"near-max now plus timeout saturates",
Some(Time::MAX),
new_scope.budget().deadline
);
crate::test_complete!("with_timeout_saturates_when_now_is_near_time_max");
}
#[test]
fn with_deadline_preserves_non_deadline_budget_fields() {
init_test("with_deadline_preserves_non_deadline_budget_fields");
let budget = Budget::new()
.with_deadline(Time::from_secs(10))
.with_poll_quota(7)
.with_cost_quota(11)
.with_priority(222);
let scope = Scope::<FailFast>::new(test_region(), budget);
let new_scope = with_deadline(&scope, Time::from_secs(3));
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::from_secs(3)),
"deadline tightened",
Some(Time::from_secs(3)),
new_scope.budget().deadline
);
crate::assert_with_log!(
new_scope.budget().poll_quota == 7,
"poll quota preserved",
7,
new_scope.budget().poll_quota
);
crate::assert_with_log!(
new_scope.budget().cost_quota == Some(11),
"cost quota preserved",
Some(11),
new_scope.budget().cost_quota
);
crate::assert_with_log!(
new_scope.budget().priority == 222,
"priority preserved",
222,
new_scope.budget().priority
);
crate::test_complete!("with_deadline_preserves_non_deadline_budget_fields");
}
#[test]
fn with_deadline_zero_deadline() {
init_test("with_deadline_zero_deadline");
let scope = Scope::<FailFast>::new(test_region(), Budget::INFINITE);
let new_scope = with_deadline(&scope, Time::ZERO);
crate::assert_with_log!(
new_scope.budget().deadline == Some(Time::ZERO),
"zero deadline set",
Some(Time::ZERO),
new_scope.budget().deadline
);
crate::test_complete!("with_deadline_zero_deadline");
}
}