use crate::types::{Budget, RegionId, Time};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone)]
pub struct DeadlineMonotoneViolation {
pub child: RegionId,
pub child_deadline: Option<Time>,
pub parent: RegionId,
pub parent_deadline: Option<Time>,
pub detected_at: Time,
}
impl fmt::Display for DeadlineMonotoneViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let child_str = self
.child_deadline
.map_or_else(|| "unbounded".to_string(), |d| format!("{d:?}"));
let parent_str = self
.parent_deadline
.map_or_else(|| "unbounded".to_string(), |d| format!("{d:?}"));
write!(
f,
"Deadline monotonicity violated: region {:?} has deadline {} but parent {:?} has deadline {} (child cannot exceed parent)",
self.child, child_str, self.parent, parent_str
)
}
}
impl std::error::Error for DeadlineMonotoneViolation {}
#[derive(Debug, Clone)]
struct RegionDeadlineEntry {
deadline: Option<Time>,
parent: Option<RegionId>,
timestamp: Time,
}
#[derive(Debug, Default)]
pub struct DeadlineMonotoneOracle {
regions: BTreeMap<RegionId, RegionDeadlineEntry>,
violations: Vec<DeadlineMonotoneViolation>,
}
impl DeadlineMonotoneOracle {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
fn is_deadline_monotone(child: Option<Time>, parent: Option<Time>) -> bool {
match (child, parent) {
(Some(c), Some(p)) => c <= p,
(Some(_) | None, None) => true,
(None, Some(_)) => false,
}
}
fn record_violation_if_needed(
&mut self,
child: RegionId,
child_deadline: Option<Time>,
parent: RegionId,
parent_deadline: Option<Time>,
time: Time,
) {
if !Self::is_deadline_monotone(child_deadline, parent_deadline) {
self.violations.push(DeadlineMonotoneViolation {
child,
child_deadline,
parent,
parent_deadline,
detected_at: time,
});
}
}
fn check_existing_children(
&mut self,
parent: RegionId,
parent_deadline: Option<Time>,
time: Time,
) {
let children_to_check: Vec<(RegionId, Option<Time>)> = self
.regions
.iter()
.filter_map(|(region_id, entry)| {
if entry.parent == Some(parent) {
Some((*region_id, entry.deadline))
} else {
None
}
})
.collect();
for (child, child_deadline) in children_to_check {
self.record_violation_if_needed(child, child_deadline, parent, parent_deadline, time);
}
}
pub fn on_region_create(
&mut self,
region: RegionId,
parent: Option<RegionId>,
budget: &Budget,
time: Time,
) {
let deadline = budget.deadline;
if let Some(parent_id) = parent {
if let Some(parent_entry) = self.regions.get(&parent_id) {
self.record_violation_if_needed(
region,
deadline,
parent_id,
parent_entry.deadline,
time,
);
}
}
self.regions.insert(
region,
RegionDeadlineEntry {
deadline,
parent,
timestamp: time,
},
);
self.check_existing_children(region, deadline, time);
}
pub fn on_budget_update(&mut self, region: RegionId, budget: &Budget, time: Time) {
let new_deadline = budget.deadline;
let Some(parent_id) = self.regions.get(®ion).and_then(|entry| entry.parent) else {
if let Some(entry) = self.regions.get_mut(®ion) {
entry.deadline = new_deadline;
entry.timestamp = time;
}
self.check_existing_children(region, new_deadline, time);
return;
};
if let Some(parent_entry) = self.regions.get(&parent_id).cloned() {
self.record_violation_if_needed(
region,
new_deadline,
parent_id,
parent_entry.deadline,
time,
);
}
if let Some(entry) = self.regions.get_mut(®ion) {
entry.deadline = new_deadline;
entry.timestamp = time;
}
self.check_existing_children(region, new_deadline, time);
}
pub fn on_parent_deadline_tightened(&mut self, parent: RegionId, budget: &Budget, time: Time) {
let parent_deadline = budget.deadline;
if let Some(entry) = self.regions.get_mut(&parent) {
entry.deadline = parent_deadline;
entry.timestamp = time;
}
self.check_existing_children(parent, parent_deadline, time);
}
pub fn check(&self) -> Result<(), DeadlineMonotoneViolation> {
if let Some(violation) = self.violations.first() {
return Err(violation.clone());
}
Ok(())
}
#[must_use]
pub fn violations(&self) -> &[DeadlineMonotoneViolation] {
&self.violations
}
pub fn reset(&mut self) {
self.regions.clear();
self.violations.clear();
}
#[must_use]
pub fn region_count(&self) -> usize {
self.regions.len()
}
#[must_use]
pub fn get_deadline(&self, region: RegionId) -> Option<Option<Time>> {
self.regions.get(®ion).map(|e| e.deadline)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::ArenaIndex;
fn region(n: u32) -> RegionId {
RegionId::from_arena(ArenaIndex::new(n, 0))
}
fn t(nanos: u64) -> Time {
Time::from_nanos(nanos)
}
fn budget_with_deadline(deadline: Time) -> Budget {
Budget::new().with_deadline(deadline)
}
fn unbounded_budget() -> Budget {
Budget::new()
}
fn init_test(name: &str) {
crate::test_utils::init_test_logging();
crate::test_phase!(name);
}
#[test]
fn root_region_with_any_deadline_passes() {
init_test("root_region_with_any_deadline_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
oracle.on_region_create(region(1), None, &unbounded_budget(), t(0));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("root_region_with_any_deadline_passes");
}
#[test]
fn child_with_tighter_deadline_passes() {
init_test("child_with_tighter_deadline_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(500)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("child_with_tighter_deadline_passes");
}
#[test]
fn child_with_equal_deadline_passes() {
init_test("child_with_equal_deadline_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(1000)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("child_with_equal_deadline_passes");
}
#[test]
fn child_with_looser_deadline_fails() {
init_test("child_with_looser_deadline_fails");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(500)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(1000)),
t(0),
);
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(1),
"violation child",
region(1),
violation.child
);
crate::assert_with_log!(
violation.child_deadline == Some(t(1000)),
"child deadline",
Some(t(1000)),
violation.child_deadline
);
crate::assert_with_log!(
violation.parent == region(0),
"parent",
region(0),
violation.parent
);
crate::assert_with_log!(
violation.parent_deadline == Some(t(500)),
"parent deadline",
Some(t(500)),
violation.parent_deadline
);
crate::test_complete!("child_with_looser_deadline_fails");
}
#[test]
fn child_created_before_parent_violation_detected_when_parent_arrives() {
init_test("child_created_before_parent_violation_detected_when_parent_arrives");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(1_000)),
t(0),
);
oracle.on_region_create(region(0), None, &budget_with_deadline(t(500)), t(1));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(1),
"violation child",
region(1),
violation.child
);
crate::assert_with_log!(
violation.parent == region(0),
"violation parent",
region(0),
violation.parent
);
crate::assert_with_log!(
violation.detected_at == t(1),
"detected at parent insert",
t(1),
violation.detected_at
);
crate::test_complete!("child_created_before_parent_violation_detected_when_parent_arrives");
}
#[test]
fn child_created_before_parent_valid_deadline_stays_ok() {
init_test("child_created_before_parent_valid_deadline_stays_ok");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(500)),
t(0),
);
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1_000)), t(1));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("child_created_before_parent_valid_deadline_stays_ok");
}
#[test]
fn bounded_child_under_unbounded_parent_passes() {
init_test("bounded_child_under_unbounded_parent_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &unbounded_budget(), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(1000)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("bounded_child_under_unbounded_parent_passes");
}
#[test]
fn unbounded_child_under_unbounded_parent_passes() {
init_test("unbounded_child_under_unbounded_parent_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &unbounded_budget(), t(0));
oracle.on_region_create(region(1), Some(region(0)), &unbounded_budget(), t(0));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("unbounded_child_under_unbounded_parent_passes");
}
#[test]
fn unbounded_child_under_bounded_parent_fails() {
init_test("unbounded_child_under_bounded_parent_fails");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(region(1), Some(region(0)), &unbounded_budget(), t(0));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(1),
"violation child",
region(1),
violation.child
);
crate::assert_with_log!(
violation.child_deadline.is_none(),
"child deadline unbounded",
true,
violation.child_deadline.is_none()
);
crate::assert_with_log!(
violation.parent == region(0),
"parent",
region(0),
violation.parent
);
crate::assert_with_log!(
violation.parent_deadline == Some(t(1000)),
"parent deadline",
Some(t(1000)),
violation.parent_deadline
);
crate::test_complete!("unbounded_child_under_bounded_parent_fails");
}
#[test]
fn deeply_nested_with_progressively_tighter_deadlines_passes() {
init_test("deeply_nested_with_progressively_tighter_deadlines_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(800)),
t(0),
);
oracle.on_region_create(
region(2),
Some(region(1)),
&budget_with_deadline(t(500)),
t(0),
);
oracle.on_region_create(
region(3),
Some(region(2)),
&budget_with_deadline(t(200)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("deeply_nested_with_progressively_tighter_deadlines_passes");
}
#[test]
fn violation_in_deep_hierarchy_detected() {
init_test("violation_in_deep_hierarchy_detected");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(500)),
t(0),
);
oracle.on_region_create(
region(2),
Some(region(1)),
&budget_with_deadline(t(800)),
t(0),
);
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(2),
"violation child",
region(2),
violation.child
);
crate::assert_with_log!(
violation.parent == region(1),
"violation parent",
region(1),
violation.parent
);
crate::test_complete!("violation_in_deep_hierarchy_detected");
}
#[test]
fn grandchild_created_before_intermediate_parent_violation_detected_on_parent_insert() {
init_test(
"grandchild_created_before_intermediate_parent_violation_detected_on_parent_insert",
);
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(
region(2),
Some(region(1)),
&budget_with_deadline(t(700)),
t(0),
);
oracle.on_region_create(region(1), None, &budget_with_deadline(t(600)), t(1));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(2),
"violation child",
region(2),
violation.child
);
crate::assert_with_log!(
violation.parent == region(1),
"violation parent",
region(1),
violation.parent
);
crate::assert_with_log!(
violation.detected_at == t(1),
"detected at parent insert",
t(1),
violation.detected_at
);
crate::test_complete!(
"grandchild_created_before_intermediate_parent_violation_detected_on_parent_insert"
);
}
#[test]
fn multiple_children_one_violating() {
init_test("multiple_children_one_violating");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(500)),
t(0),
);
oracle.on_region_create(
region(2),
Some(region(0)),
&budget_with_deadline(t(900)),
t(0),
);
oracle.on_region_create(
region(3),
Some(region(0)),
&budget_with_deadline(t(1500)),
t(0),
);
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violations = oracle.violations().len();
crate::assert_with_log!(violations == 1, "violations len", 1, violations);
crate::test_complete!("multiple_children_one_violating");
}
#[test]
fn budget_update_tightening_passes() {
init_test("budget_update_tightening_passes");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(800)),
t(0),
);
oracle.on_budget_update(region(1), &budget_with_deadline(t(500)), t(10));
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
crate::test_complete!("budget_update_tightening_passes");
}
#[test]
fn budget_update_loosening_fails() {
init_test("budget_update_loosening_fails");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(500)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(400)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
oracle.on_budget_update(region(1), &budget_with_deadline(t(1000)), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
crate::test_complete!("budget_update_loosening_fails");
}
#[test]
fn parent_deadline_tightened_causes_child_violation() {
init_test("parent_deadline_tightened_causes_child_violation");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(800)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
oracle.on_parent_deadline_tightened(region(0), &budget_with_deadline(t(500)), t(10));
let result = oracle.check();
let err = result.is_err();
crate::assert_with_log!(err, "result err", true, err);
let violation = result.unwrap_err();
crate::assert_with_log!(
violation.child == region(1),
"violation child",
region(1),
violation.child
);
crate::assert_with_log!(
violation.child_deadline == Some(t(800)),
"child deadline",
Some(t(800)),
violation.child_deadline
);
crate::assert_with_log!(
violation.parent_deadline == Some(t(500)),
"parent deadline",
Some(t(500)),
violation.parent_deadline
);
crate::test_complete!("parent_deadline_tightened_causes_child_violation");
}
#[test]
fn parent_budget_update_rechecks_existing_children() {
init_test("parent_budget_update_rechecks_existing_children");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(800)),
t(0),
);
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
oracle.on_budget_update(region(0), &budget_with_deadline(t(500)), t(10));
let violation = oracle
.check()
.expect_err("parent budget updates must re-check existing children");
crate::assert_with_log!(
violation.child == region(1),
"violation child",
region(1),
violation.child
);
crate::assert_with_log!(
violation.child_deadline == Some(t(800)),
"child deadline",
Some(t(800)),
violation.child_deadline
);
crate::assert_with_log!(
violation.parent == region(0),
"parent",
region(0),
violation.parent
);
crate::assert_with_log!(
violation.parent_deadline == Some(t(500)),
"parent deadline",
Some(t(500)),
violation.parent_deadline
);
crate::test_complete!("parent_budget_update_rechecks_existing_children");
}
#[test]
fn reset_clears_state() {
init_test("reset_clears_state");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(100)), t(0));
oracle.on_region_create(
region(1),
Some(region(0)),
&budget_with_deadline(t(500)),
t(0),
);
let err = oracle.check().is_err();
crate::assert_with_log!(err, "oracle err", true, err);
let count = oracle.region_count();
crate::assert_with_log!(count == 2, "region count", 2, count);
oracle.reset();
let ok = oracle.check().is_ok();
crate::assert_with_log!(ok, "oracle ok", true, ok);
let count = oracle.region_count();
crate::assert_with_log!(count == 0, "region count", 0, count);
let empty = oracle.violations().is_empty();
crate::assert_with_log!(empty, "violations empty", true, empty);
crate::test_complete!("reset_clears_state");
}
#[test]
fn get_deadline_returns_tracked_value() {
init_test("get_deadline_returns_tracked_value");
let mut oracle = DeadlineMonotoneOracle::new();
oracle.on_region_create(region(0), None, &budget_with_deadline(t(1000)), t(0));
oracle.on_region_create(region(1), None, &unbounded_budget(), t(0));
let r0 = oracle.get_deadline(region(0));
crate::assert_with_log!(
r0 == Some(Some(t(1000))),
"deadline r0",
Some(Some(t(1000))),
r0
);
let r1 = oracle.get_deadline(region(1));
let r1_unbounded = matches!(r1, Some(None));
crate::assert_with_log!(r1_unbounded, "deadline r1 unbounded", true, r1_unbounded);
let r99 = oracle.get_deadline(region(99));
crate::assert_with_log!(r99.is_none(), "deadline r99", true, r99.is_none());
crate::test_complete!("get_deadline_returns_tracked_value");
}
#[test]
fn violation_display() {
init_test("violation_display");
let violation = DeadlineMonotoneViolation {
child: region(1),
child_deadline: Some(t(1000)),
parent: region(0),
parent_deadline: Some(t(500)),
detected_at: t(100),
};
let s = violation.to_string();
let has_violation = s.contains("monotonicity violated");
crate::assert_with_log!(
has_violation,
"message contains violation",
true,
has_violation
);
let has_parent = s.contains("cannot exceed parent");
crate::assert_with_log!(has_parent, "message contains parent", true, has_parent);
crate::test_complete!("violation_display");
}
#[test]
fn violation_display_with_unbounded() {
init_test("violation_display_with_unbounded");
let violation = DeadlineMonotoneViolation {
child: region(1),
child_deadline: None,
parent: region(0),
parent_deadline: Some(t(500)),
detected_at: t(100),
};
let s = violation.to_string();
let has_unbounded = s.contains("unbounded");
crate::assert_with_log!(
has_unbounded,
"message contains unbounded",
true,
has_unbounded
);
crate::test_complete!("violation_display_with_unbounded");
}
#[test]
fn test_is_deadline_monotone() {
init_test("test_is_deadline_monotone");
let bounded_ok = DeadlineMonotoneOracle::is_deadline_monotone(Some(t(100)), Some(t(200)));
crate::assert_with_log!(bounded_ok, "100 <= 200", true, bounded_ok);
let equal_ok = DeadlineMonotoneOracle::is_deadline_monotone(Some(t(200)), Some(t(200)));
crate::assert_with_log!(equal_ok, "200 <= 200", true, equal_ok);
let looser_bad = DeadlineMonotoneOracle::is_deadline_monotone(Some(t(300)), Some(t(200)));
crate::assert_with_log!(!looser_bad, "300 <= 200", false, looser_bad);
let bounded_unbounded = DeadlineMonotoneOracle::is_deadline_monotone(Some(t(100)), None);
crate::assert_with_log!(
bounded_unbounded,
"bounded under unbounded",
true,
bounded_unbounded
);
let bounded_max = DeadlineMonotoneOracle::is_deadline_monotone(Some(t(u64::MAX)), None);
crate::assert_with_log!(bounded_max, "max under unbounded", true, bounded_max);
let both_unbounded = DeadlineMonotoneOracle::is_deadline_monotone(None, None);
crate::assert_with_log!(
both_unbounded,
"unbounded <= unbounded",
true,
both_unbounded
);
let unbounded_bad = DeadlineMonotoneOracle::is_deadline_monotone(None, Some(t(100)));
crate::assert_with_log!(!unbounded_bad, "unbounded <= 100", false, unbounded_bad);
let unbounded_max = DeadlineMonotoneOracle::is_deadline_monotone(None, Some(t(u64::MAX)));
crate::assert_with_log!(!unbounded_max, "unbounded <= max", false, unbounded_max);
crate::test_complete!("test_is_deadline_monotone");
}
}