use crate::trace::event::{TraceData, TraceEvent, TraceEventKind};
use crate::types::{ObligationId, RegionId, TaskId};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Resource {
Task(TaskId),
Region(RegionId),
Obligation(ObligationId),
Timer(u64),
IoToken(u64),
GlobalClock,
GlobalRng,
GlobalState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AccessMode {
Read,
Write,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceAccess {
pub resource: Resource,
pub mode: AccessMode,
}
impl ResourceAccess {
#[must_use]
pub fn read(resource: Resource) -> Self {
Self {
resource,
mode: AccessMode::Read,
}
}
#[must_use]
pub fn write(resource: Resource) -> Self {
Self {
resource,
mode: AccessMode::Write,
}
}
}
#[must_use]
pub fn accesses_conflict(a: &ResourceAccess, b: &ResourceAccess) -> bool {
a.resource == b.resource && (a.mode == AccessMode::Write || b.mode == AccessMode::Write)
}
#[must_use]
#[allow(clippy::too_many_lines)]
pub fn resource_footprint(event: &TraceEvent) -> Vec<ResourceAccess> {
use TraceEventKind::{
CancelAck, CancelRequest, ChaosInjection, Checkpoint, Complete, DownDelivered,
ExitDelivered, FuturelockDetected, IoError, IoReady, IoRequested, IoResult, LinkCreated,
LinkDropped, MonitorCreated, MonitorDropped, ObligationAbort, ObligationCommit,
ObligationLeak, ObligationReserve, Poll, RegionCancelled, RegionCloseBegin,
RegionCloseComplete, RegionCreated, RngSeed, RngValue, Schedule, Spawn, TimeAdvance,
TimerCancelled, TimerFired, TimerScheduled, UserTrace, Wake, Yield,
};
match (&event.kind, &event.data) {
(Spawn | Schedule | Yield | Wake | Poll | Complete, TraceData::Task { task, region })
| (CancelAck, TraceData::Cancel { task, region, .. }) => {
vec![
ResourceAccess::write(Resource::Task(*task)),
ResourceAccess::read(Resource::Region(*region)),
]
}
(CancelRequest, TraceData::Cancel { task, region, .. }) => {
vec![
ResourceAccess::write(Resource::Task(*task)),
ResourceAccess::write(Resource::Region(*region)),
]
}
(RegionCreated, TraceData::Region { region, parent }) => {
let mut fp = vec![ResourceAccess::write(Resource::Region(*region))];
if let Some(p) = parent {
fp.push(ResourceAccess::read(Resource::Region(*p)));
}
fp
}
(RegionCloseBegin | RegionCloseComplete, TraceData::Region { region, .. })
| (RegionCancelled, TraceData::RegionCancel { region, .. }) => {
vec![ResourceAccess::write(Resource::Region(*region))]
}
(
ObligationReserve | ObligationCommit | ObligationAbort | ObligationLeak,
TraceData::Obligation {
obligation,
task,
region,
..
},
) => {
vec![
ResourceAccess::write(Resource::Obligation(*obligation)),
ResourceAccess::read(Resource::Task(*task)),
ResourceAccess::read(Resource::Region(*region)),
]
}
(TimeAdvance, _) => {
vec![ResourceAccess::write(Resource::GlobalClock)]
}
(TimerScheduled | TimerFired | TimerCancelled, TraceData::Timer { timer_id, .. }) => {
vec![
ResourceAccess::write(Resource::Timer(*timer_id)),
ResourceAccess::read(Resource::GlobalClock),
]
}
(IoRequested, TraceData::IoRequested { token, .. })
| (IoReady, TraceData::IoReady { token, .. })
| (IoResult, TraceData::IoResult { token, .. })
| (IoError, TraceData::IoError { token, .. }) => {
vec![ResourceAccess::write(Resource::IoToken(*token))]
}
(RngSeed | RngValue, _) => {
vec![ResourceAccess::write(Resource::GlobalRng)]
}
(Checkpoint, _) => {
vec![ResourceAccess::read(Resource::GlobalState)]
}
(FuturelockDetected, TraceData::Futurelock { task, region, .. }) => {
vec![
ResourceAccess::read(Resource::Task(*task)),
ResourceAccess::read(Resource::Region(*region)),
]
}
(ChaosInjection, TraceData::Chaos { task, .. }) => {
let mut fp = vec![ResourceAccess::write(Resource::GlobalState)];
if let Some(t) = task {
fp.push(ResourceAccess::write(Resource::Task(*t)));
}
fp
}
(UserTrace, _) => {
vec![]
}
(
MonitorCreated | MonitorDropped,
TraceData::Monitor {
watcher,
watcher_region,
monitored,
..
},
) => vec![
ResourceAccess::write(Resource::Task(*watcher)),
ResourceAccess::read(Resource::Region(*watcher_region)),
ResourceAccess::read(Resource::Task(*monitored)),
],
(
DownDelivered,
TraceData::Down {
watcher, monitored, ..
},
) => vec![
ResourceAccess::write(Resource::Task(*watcher)),
ResourceAccess::read(Resource::Task(*monitored)),
ResourceAccess::read(Resource::GlobalClock),
],
(
LinkCreated | LinkDropped,
TraceData::Link {
task_a,
region_a,
task_b,
region_b,
..
},
) => vec![
ResourceAccess::write(Resource::Task(*task_a)),
ResourceAccess::read(Resource::Region(*region_a)),
ResourceAccess::write(Resource::Task(*task_b)),
ResourceAccess::read(Resource::Region(*region_b)),
],
(ExitDelivered, TraceData::Exit { from, to, .. }) => vec![
ResourceAccess::read(Resource::Task(*from)),
ResourceAccess::write(Resource::Task(*to)),
ResourceAccess::read(Resource::GlobalClock),
],
_ => {
vec![ResourceAccess::write(Resource::GlobalState)]
}
}
}
#[must_use]
pub fn independent(a: &TraceEvent, b: &TraceEvent) -> bool {
if a.seq == b.seq {
return false;
}
let fa = resource_footprint(a);
let fb = resource_footprint(b);
if fa.is_empty() || fb.is_empty() {
return true;
}
for ra in &fa {
for rb in &fb {
if accesses_conflict(ra, rb) {
return false;
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use crate::record::ObligationKind;
use crate::types::{CancelReason, Time};
fn tid(n: u32) -> TaskId {
TaskId::new_for_test(n, 0)
}
fn rid(n: u32) -> RegionId {
RegionId::new_for_test(n, 0)
}
fn oid(n: u32) -> ObligationId {
ObligationId::new_for_test(n, 0)
}
#[test]
fn irreflexive_spawn() {
let e = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
assert!(!independent(&e, &e));
}
#[test]
fn irreflexive_user_trace() {
let e = TraceEvent::user_trace(1, Time::ZERO, "hello");
assert!(!independent(&e, &e));
}
#[test]
fn symmetry_dependent() {
let a = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::complete(2, Time::ZERO, tid(1), rid(1));
assert_eq!(independent(&a, &b), independent(&b, &a));
assert!(!independent(&a, &b));
}
#[test]
fn symmetry_independent() {
let a = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
assert_eq!(independent(&a, &b), independent(&b, &a));
assert!(independent(&a, &b));
}
#[test]
fn same_task_events_are_dependent() {
let a = TraceEvent::schedule(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::complete(2, Time::ZERO, tid(1), rid(1));
assert!(!independent(&a, &b));
}
#[test]
fn different_tasks_different_regions_are_independent() {
let a = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
assert!(independent(&a, &b));
}
#[test]
fn different_tasks_same_region_are_independent() {
let a = TraceEvent::schedule(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::schedule(2, Time::ZERO, tid(2), rid(1));
assert!(independent(&a, &b));
}
#[test]
fn cancel_request_conflicts_with_task_in_same_region() {
let a =
TraceEvent::cancel_request(1, Time::ZERO, tid(1), rid(1), CancelReason::user("test"));
let b = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(1));
assert!(!independent(&a, &b));
}
#[test]
fn cancel_request_independent_of_different_region() {
let a =
TraceEvent::cancel_request(1, Time::ZERO, tid(1), rid(1), CancelReason::user("test"));
let b = TraceEvent::spawn(2, Time::ZERO, tid(2), rid(2));
assert!(independent(&a, &b));
}
#[test]
fn region_create_conflicts_with_spawn_in_same_region() {
let a = TraceEvent::region_created(1, Time::ZERO, rid(1), None);
let b = TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1));
assert!(!independent(&a, &b));
}
#[test]
fn region_create_independent_of_different_region_task() {
let a = TraceEvent::region_created(1, Time::ZERO, rid(1), None);
let b = TraceEvent::spawn(2, Time::ZERO, tid(1), rid(2));
assert!(independent(&a, &b));
}
#[test]
fn child_region_create_depends_on_parent_region_events() {
let a = TraceEvent::region_created(1, Time::ZERO, rid(2), Some(rid(1)));
let b = TraceEvent::region_cancelled(2, Time::ZERO, rid(1), CancelReason::user("test"));
assert!(!independent(&a, &b));
}
#[test]
fn same_obligation_events_are_dependent() {
let a = TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::SendPermit,
);
let b = TraceEvent::obligation_commit(
2,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::SendPermit,
1000,
);
assert!(!independent(&a, &b));
}
#[test]
fn different_obligation_events_are_independent() {
let a = TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::SendPermit,
);
let b = TraceEvent::obligation_reserve(
2,
Time::ZERO,
oid(2),
tid(2),
rid(2),
ObligationKind::Ack,
);
assert!(independent(&a, &b));
}
#[test]
fn obligation_conflicts_with_task_write_on_same_task() {
let a = TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::SendPermit,
);
let b = TraceEvent::complete(2, Time::ZERO, tid(1), rid(1));
assert!(!independent(&a, &b));
}
#[test]
fn time_advance_conflicts_with_timer() {
let a = TraceEvent::time_advance(1, Time::ZERO, Time::ZERO, Time::from_nanos(1000));
let b = TraceEvent::timer_fired(2, Time::ZERO, 42);
assert!(!independent(&a, &b));
}
#[test]
fn different_timers_are_independent() {
let a = TraceEvent::timer_scheduled(1, Time::ZERO, 1, Time::from_nanos(1000));
let b = TraceEvent::timer_scheduled(2, Time::ZERO, 2, Time::from_nanos(2000));
assert!(independent(&a, &b));
}
#[test]
fn same_timer_events_are_dependent() {
let a = TraceEvent::timer_scheduled(1, Time::ZERO, 42, Time::from_nanos(1000));
let b = TraceEvent::timer_fired(2, Time::ZERO, 42);
assert!(!independent(&a, &b));
}
#[test]
fn different_io_tokens_are_independent() {
let a = TraceEvent::io_ready(1, Time::ZERO, 10, 0x01);
let b = TraceEvent::io_ready(2, Time::ZERO, 20, 0x02);
assert!(independent(&a, &b));
}
#[test]
fn same_io_token_events_are_dependent() {
let a = TraceEvent::io_requested(1, Time::ZERO, 10, 0x01);
let b = TraceEvent::io_result(2, Time::ZERO, 10, 512);
assert!(!independent(&a, &b));
}
#[test]
fn rng_events_are_dependent() {
let a = TraceEvent::rng_seed(1, Time::ZERO, 0xDEAD);
let b = TraceEvent::rng_value(2, Time::ZERO, 42);
assert!(!independent(&a, &b));
}
#[test]
fn rng_independent_of_task_events() {
let a = TraceEvent::rng_value(1, Time::ZERO, 42);
let b = TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1));
assert!(independent(&a, &b));
}
#[test]
fn user_trace_independent_of_everything() {
let u = TraceEvent::user_trace(1, Time::ZERO, "annotation");
let events = [
TraceEvent::spawn(2, Time::ZERO, tid(1), rid(1)),
TraceEvent::time_advance(3, Time::ZERO, Time::ZERO, Time::from_nanos(1)),
TraceEvent::rng_value(4, Time::ZERO, 42),
TraceEvent::io_ready(5, Time::ZERO, 1, 0x01),
TraceEvent::timer_fired(6, Time::ZERO, 1),
TraceEvent::checkpoint(7, Time::ZERO, 1, 10, 5),
];
for e in &events {
assert!(
independent(&u, e),
"UserTrace should be independent of {:?}",
e.kind
);
assert!(
independent(e, &u),
"Symmetry: {:?} should be independent of UserTrace",
e.kind
);
}
}
#[test]
fn checkpoints_are_independent_of_each_other() {
let a = TraceEvent::checkpoint(1, Time::ZERO, 1, 10, 5);
let b = TraceEvent::checkpoint(2, Time::ZERO, 2, 12, 6);
assert!(independent(&a, &b));
}
#[test]
fn checkpoint_conflicts_with_chaos_injection() {
let a = TraceEvent::checkpoint(1, Time::ZERO, 1, 10, 5);
let b = TraceEvent::new(
2,
Time::ZERO,
TraceEventKind::ChaosInjection,
TraceData::Chaos {
kind: "delay".into(),
task: None,
detail: "test".into(),
},
);
assert!(!independent(&a, &b));
}
#[test]
fn task_event_independent_of_unrelated_io() {
let a = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let b = TraceEvent::io_ready(2, Time::ZERO, 99, 0x01);
assert!(independent(&a, &b));
}
#[test]
fn timer_independent_of_obligation() {
let a = TraceEvent::timer_fired(1, Time::ZERO, 1);
let b = TraceEvent::obligation_reserve(
2,
Time::ZERO,
oid(1),
tid(1),
rid(1),
ObligationKind::Lease,
);
assert!(independent(&a, &b));
}
#[test]
fn spawn_footprint() {
let e = TraceEvent::spawn(1, Time::ZERO, tid(1), rid(1));
let fp = resource_footprint(&e);
assert_eq!(fp.len(), 2);
assert_eq!(fp[0], ResourceAccess::write(Resource::Task(tid(1))));
assert_eq!(fp[1], ResourceAccess::read(Resource::Region(rid(1))));
}
#[test]
fn user_trace_empty_footprint() {
let e = TraceEvent::user_trace(1, Time::ZERO, "no resources");
let fp = resource_footprint(&e);
assert!(fp.is_empty());
}
#[test]
fn obligation_footprint_has_three_accesses() {
let e = TraceEvent::obligation_reserve(
1,
Time::ZERO,
oid(5),
tid(3),
rid(2),
ObligationKind::Lease,
);
let fp = resource_footprint(&e);
assert_eq!(fp.len(), 3);
assert_eq!(fp[0], ResourceAccess::write(Resource::Obligation(oid(5))));
assert_eq!(fp[1], ResourceAccess::read(Resource::Task(tid(3))));
assert_eq!(fp[2], ResourceAccess::read(Resource::Region(rid(2))));
}
#[test]
fn two_reads_do_not_conflict() {
let a = ResourceAccess::read(Resource::Task(tid(1)));
let b = ResourceAccess::read(Resource::Task(tid(1)));
assert!(!accesses_conflict(&a, &b));
}
#[test]
fn read_write_conflict() {
let a = ResourceAccess::read(Resource::Task(tid(1)));
let b = ResourceAccess::write(Resource::Task(tid(1)));
assert!(accesses_conflict(&a, &b));
}
#[test]
fn write_write_conflict() {
let a = ResourceAccess::write(Resource::Task(tid(1)));
let b = ResourceAccess::write(Resource::Task(tid(1)));
assert!(accesses_conflict(&a, &b));
}
#[test]
fn different_resources_no_conflict() {
let a = ResourceAccess::write(Resource::Task(tid(1)));
let b = ResourceAccess::write(Resource::Task(tid(2)));
assert!(!accesses_conflict(&a, &b));
}
#[test]
fn access_mode_debug_clone_copy_eq_hash() {
use std::collections::HashSet;
let a = AccessMode::Read;
let b = a; let c = a;
assert_eq!(a, b);
assert_eq!(a, c);
assert_ne!(a, AccessMode::Write);
let dbg = format!("{a:?}");
assert!(dbg.contains("Read"));
let mut set = HashSet::new();
set.insert(a);
assert!(set.contains(&b));
}
}