use serde::{Deserialize, Serialize};
pub const DEADLOCK_RADAR_SCHEMA_VERSION: &str = "asupersync.deadlock-radar.v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadlockRadarLockRank {
Config,
Instrumentation,
Regions,
Tasks,
Obligations,
}
impl DeadlockRadarLockRank {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Config => "E",
Self::Instrumentation => "D",
Self::Regions => "B",
Self::Tasks => "A",
Self::Obligations => "C",
}
}
const fn order_index(self) -> u8 {
match self {
Self::Config => 10,
Self::Instrumentation => 20,
Self::Regions => 30,
Self::Tasks => 40,
Self::Obligations => 50,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadlockRadarHazardClass {
LockOrderInversion,
AwaitHoldingLock,
ReaderUpgrade,
CondvarPredicate,
LostNotification,
CounterUnderflow,
QueuePublicationMismatch,
OptimisticFlagToctou,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum DeadlockRadarEvidence {
LockOrder {
acquisitions: Vec<DeadlockRadarLockRank>,
},
AwaitHoldingLock {
guard_dropped_before_await: bool,
},
ReaderUpgrade {
read_guard_dropped_before_write: bool,
},
CondvarPredicate {
predicate_checked_before_wait: bool,
predicate_rechecked_after_wake: bool,
},
LostNotification {
level_triggered_state: bool,
waiter_registered_before_notify: bool,
},
CounterUnderflow {
checked_decrement: bool,
saturating_decrement: bool,
},
QueuePublication {
enqueue_before_count_publish: bool,
cancel_rolls_back_count: bool,
},
OptimisticFlag {
writer_requires_mut: bool,
lock_rechecks_condition: bool,
stale_false_is_safe: bool,
},
}
impl DeadlockRadarEvidence {
fn hazard_class(&self) -> DeadlockRadarHazardClass {
match self {
Self::LockOrder { .. } => DeadlockRadarHazardClass::LockOrderInversion,
Self::AwaitHoldingLock { .. } => DeadlockRadarHazardClass::AwaitHoldingLock,
Self::ReaderUpgrade { .. } => DeadlockRadarHazardClass::ReaderUpgrade,
Self::CondvarPredicate { .. } => DeadlockRadarHazardClass::CondvarPredicate,
Self::LostNotification { .. } => DeadlockRadarHazardClass::LostNotification,
Self::CounterUnderflow { .. } => DeadlockRadarHazardClass::CounterUnderflow,
Self::QueuePublication { .. } => DeadlockRadarHazardClass::QueuePublicationMismatch,
Self::OptimisticFlag { .. } => DeadlockRadarHazardClass::OptimisticFlagToctou,
}
}
fn is_hazardous(&self) -> bool {
match self {
Self::LockOrder { acquisitions } => acquisitions
.windows(2)
.any(|window| window[1].order_index() < window[0].order_index()),
Self::AwaitHoldingLock {
guard_dropped_before_await,
} => !guard_dropped_before_await,
Self::ReaderUpgrade {
read_guard_dropped_before_write,
} => !read_guard_dropped_before_write,
Self::CondvarPredicate {
predicate_checked_before_wait,
predicate_rechecked_after_wake,
} => !(*predicate_checked_before_wait && *predicate_rechecked_after_wake),
Self::LostNotification {
level_triggered_state,
waiter_registered_before_notify,
} => !(*level_triggered_state || *waiter_registered_before_notify),
Self::CounterUnderflow {
checked_decrement,
saturating_decrement,
} => !(*checked_decrement || *saturating_decrement),
Self::QueuePublication {
enqueue_before_count_publish,
cancel_rolls_back_count,
} => !(*enqueue_before_count_publish && *cancel_rolls_back_count),
Self::OptimisticFlag {
writer_requires_mut,
lock_rechecks_condition,
stale_false_is_safe,
} => !(*writer_requires_mut && *lock_rechecks_condition && *stale_false_is_safe),
}
}
fn false_positive_reason(&self) -> &'static str {
match self {
Self::LockOrder { .. } => "lock acquisitions preserve E-D-B-A-C order",
Self::AwaitHoldingLock { .. } => "guard is dropped before await",
Self::ReaderUpgrade { .. } => "read guard is dropped before write acquisition",
Self::CondvarPredicate { .. } => "predicate is checked before wait and after wake",
Self::LostNotification { .. } => {
"notification is protected by level-triggered state or prior registration"
}
Self::CounterUnderflow { .. } => "counter decrement is checked or saturating",
Self::QueuePublication { .. } => {
"queue entry and advisory count publish/rollback together"
}
Self::OptimisticFlag { .. } => {
"optimistic flag is only a hint and the locked path is authoritative"
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeadlockRadarInterleavingStep {
pub actor: String,
pub action: String,
pub held_locks: Vec<String>,
pub waits_for: Option<String>,
pub note: String,
}
impl DeadlockRadarInterleavingStep {
#[must_use]
pub fn new(
actor: impl Into<String>,
action: impl Into<String>,
held_locks: impl IntoIterator<Item = impl Into<String>>,
waits_for: Option<impl Into<String>>,
note: impl Into<String>,
) -> Self {
Self {
actor: actor.into(),
action: action.into(),
held_locks: held_locks.into_iter().map(Into::into).collect(),
waits_for: waits_for.map(Into::into),
note: note.into(),
}
}
fn is_concrete(&self) -> bool {
!self.actor.trim().is_empty()
&& !self.action.trim().is_empty()
&& !self.note.trim().is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeadlockRadarCandidate {
pub id: String,
pub surface: String,
pub source_refs: Vec<String>,
pub evidence: DeadlockRadarEvidence,
pub interleaving: Vec<DeadlockRadarInterleavingStep>,
pub suggested_owner_bead: Option<String>,
}
impl DeadlockRadarCandidate {
#[must_use]
pub fn new(
id: impl Into<String>,
surface: impl Into<String>,
evidence: DeadlockRadarEvidence,
) -> Self {
Self {
id: id.into(),
surface: surface.into(),
source_refs: Vec::new(),
evidence,
interleaving: Vec::new(),
suggested_owner_bead: None,
}
}
#[must_use]
pub fn with_source_refs(mut self, refs: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.source_refs = refs.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn with_interleaving(
mut self,
steps: impl IntoIterator<Item = DeadlockRadarInterleavingStep>,
) -> Self {
self.interleaving = steps.into_iter().collect();
self
}
#[must_use]
pub fn with_suggested_owner_bead(mut self, bead: impl Into<String>) -> Self {
self.suggested_owner_bead = Some(bead.into());
self
}
fn has_concrete_interleaving(&self) -> bool {
!self.interleaving.is_empty()
&& self
.interleaving
.iter()
.all(DeadlockRadarInterleavingStep::is_concrete)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadlockRadarProofStatus {
ProvenInterleaving,
FalsePositive,
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeadlockRadarFinding {
pub id: String,
pub surface: String,
pub hazard_class: DeadlockRadarHazardClass,
pub proof_status: DeadlockRadarProofStatus,
pub reason: String,
pub source_refs: Vec<String>,
pub interleaving: Vec<DeadlockRadarInterleavingStep>,
pub suggested_owner_bead: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadlockRadarVerdict {
Pass,
Finding,
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeadlockRadarReport {
pub schema_version: String,
pub candidates_examined: usize,
pub findings: Vec<DeadlockRadarFinding>,
pub false_positives: Vec<DeadlockRadarFinding>,
pub incomplete: Vec<DeadlockRadarFinding>,
pub verdict: DeadlockRadarVerdict,
}
#[must_use]
pub fn run_deadlock_radar(candidates: &[DeadlockRadarCandidate]) -> DeadlockRadarReport {
let mut findings = Vec::new();
let mut false_positives = Vec::new();
let mut incomplete = Vec::new();
for candidate in candidates {
let hazard_class = candidate.evidence.hazard_class();
let row = if candidate.evidence.is_hazardous() {
if candidate.has_concrete_interleaving() {
DeadlockRadarFinding {
id: candidate.id.clone(),
surface: candidate.surface.clone(),
hazard_class,
proof_status: DeadlockRadarProofStatus::ProvenInterleaving,
reason: "hazardous pattern has concrete interleaving proof".to_string(),
source_refs: candidate.source_refs.clone(),
interleaving: candidate.interleaving.clone(),
suggested_owner_bead: candidate.suggested_owner_bead.clone(),
}
} else {
DeadlockRadarFinding {
id: candidate.id.clone(),
surface: candidate.surface.clone(),
hazard_class,
proof_status: DeadlockRadarProofStatus::Incomplete,
reason: "hazardous pattern lacks concrete interleaving proof".to_string(),
source_refs: candidate.source_refs.clone(),
interleaving: Vec::new(),
suggested_owner_bead: candidate.suggested_owner_bead.clone(),
}
}
} else {
DeadlockRadarFinding {
id: candidate.id.clone(),
surface: candidate.surface.clone(),
hazard_class,
proof_status: DeadlockRadarProofStatus::FalsePositive,
reason: candidate.evidence.false_positive_reason().to_string(),
source_refs: candidate.source_refs.clone(),
interleaving: Vec::new(),
suggested_owner_bead: candidate.suggested_owner_bead.clone(),
}
};
match row.proof_status {
DeadlockRadarProofStatus::ProvenInterleaving => findings.push(row),
DeadlockRadarProofStatus::FalsePositive => false_positives.push(row),
DeadlockRadarProofStatus::Incomplete => incomplete.push(row),
}
}
let verdict = if !findings.is_empty() {
DeadlockRadarVerdict::Finding
} else if !incomplete.is_empty() {
DeadlockRadarVerdict::Incomplete
} else {
DeadlockRadarVerdict::Pass
};
DeadlockRadarReport {
schema_version: DEADLOCK_RADAR_SCHEMA_VERSION.to_string(),
candidates_examined: candidates.len(),
findings,
false_positives,
incomplete,
verdict,
}
}