use crate::core::detector::GLOBAL_DETECTOR;
use crate::core::detector::deadlock_handling;
use crate::core::logger;
use crate::core::types::{CondvarId, DeadlockInfo, LockId, ThreadId};
use crate::core::{Detector, Events, WaitIntent, WaitMode, get_current_thread_id};
use std::collections::VecDeque;
impl Detector {
pub fn create_condvar(&mut self, condvar_id: CondvarId) {
self.cv_waiters.insert(condvar_id, VecDeque::new());
logger::log_lock_event(
condvar_id,
Some(get_current_thread_id()),
Events::CondvarSpawn,
);
}
pub fn destroy_condvar(&mut self, condvar_id: CondvarId) {
self.cv_waiters.remove(&condvar_id);
self.thread_wait_cv
.retain(|_, &mut (cv_id, _)| cv_id != condvar_id);
logger::log_lock_event(condvar_id, None, Events::CondvarExit);
}
pub fn begin_wait(&mut self, thread_id: ThreadId, condvar_id: CondvarId, mutex_id: LockId) {
if let Some(queue) = self.cv_waiters.get_mut(&condvar_id) {
queue.push_back((thread_id, mutex_id));
} else {
self.cv_waiters
.insert(condvar_id, VecDeque::from([(thread_id, mutex_id)]));
}
self.thread_wait_cv
.insert(thread_id, (condvar_id, mutex_id));
logger::log_interaction_event(thread_id, condvar_id, Events::CondvarWaitBegin);
}
pub fn notify_one(
&mut self,
condvar_id: CondvarId,
notifier_id: ThreadId,
) -> Vec<DeadlockInfo> {
let (woken_thread_info, deadlocks) = if let Some(queue) =
self.cv_waiters.get_mut(&condvar_id)
&& let Some((waiter_thread, mutex_id)) = queue.pop_front()
{
self.cv_woken.insert(waiter_thread);
let deadlocks = self.on_mutex_attempt_synthetic_immediate(waiter_thread, mutex_id);
(Some((waiter_thread, mutex_id)), deadlocks)
} else {
(None, Vec::new())
};
let woken_thread_id = woken_thread_info.map(|(t, _)| t);
logger::log_condvar_notify_event(
notifier_id,
condvar_id,
Events::CondvarNotifyOne,
woken_thread_id,
);
if let Some((thread_id, mutex_id)) = woken_thread_info {
logger::log_interaction_event(thread_id, mutex_id, Events::MutexAttempt);
}
deadlocks
}
pub fn notify_all(
&mut self,
condvar_id: CondvarId,
notifier_id: ThreadId,
) -> Vec<DeadlockInfo> {
let waiters_to_wake: Vec<(ThreadId, LockId)> =
if let Some(queue) = self.cv_waiters.get_mut(&condvar_id) {
queue.drain(..).collect()
} else {
Vec::new()
};
let woken_threads: Vec<ThreadId> = waiters_to_wake.iter().map(|(t, _)| *t).collect();
let mut all_deadlocks = Vec::new();
for (waiter_thread, mutex_id) in &waiters_to_wake {
self.cv_woken.insert(*waiter_thread);
let deadlocks = self.on_mutex_attempt_synthetic_immediate(*waiter_thread, *mutex_id);
all_deadlocks.extend(deadlocks);
}
logger::log_condvar_notify_event(
notifier_id,
condvar_id,
Events::CondvarNotifyAll,
woken_threads.first().copied(),
);
for (waiter_thread, mutex_id) in waiters_to_wake {
logger::log_interaction_event(waiter_thread, mutex_id, Events::MutexAttempt);
}
all_deadlocks
}
pub fn end_wait(&mut self, thread_id: ThreadId, condvar_id: CondvarId, mutex_id: LockId) {
if let Some(waiters) = self.cv_waiters.get_mut(&condvar_id) {
waiters.retain(|(waiter, lock)| *waiter != thread_id || *lock != mutex_id);
}
self.thread_wait_cv.remove(&thread_id);
self.cv_woken.remove(&thread_id);
self.clear_wait_intent(thread_id);
}
fn on_mutex_attempt_synthetic_immediate(
&mut self,
thread_id: ThreadId,
lock_id: LockId,
) -> Vec<DeadlockInfo> {
let mut deadlocks = Vec::new();
#[cfg(feature = "lock-order-graph")]
let lock_order_violation = if self.lock_order_graph.is_some()
&& self.thread_holds.get(&thread_id).map_or(0, |h| h.len()) >= 1
{
self.check_lock_order_violation(thread_id, lock_id)
} else {
None
};
#[cfg(not(feature = "lock-order-graph"))]
let _lock_order_violation: Option<Vec<LockId>> = None;
if self.mutex_owners.contains_key(&lock_id)
&& let Some(cycle) =
self.register_wait(thread_id, WaitIntent::new(lock_id, WaitMode::Mutex))
{
let filtered_cycle = self.filter_cycle_by_common_locks(&cycle);
if !filtered_cycle.is_empty()
&& let Some(info) = self.validated_deadlock_info(cycle)
{
deadlocks.push(info);
}
}
#[cfg(feature = "lock-order-graph")]
if let Some(lock_cycle) = lock_order_violation {
deadlocks.push(self.extract_lock_order_violation_info(thread_id, lock_id, lock_cycle));
}
deadlocks
}
}
pub fn create_condvar(condvar_id: CondvarId) {
let mut detector = GLOBAL_DETECTOR.lock();
detector.create_condvar(condvar_id);
}
pub fn destroy_condvar(condvar_id: CondvarId) {
let mut detector = GLOBAL_DETECTOR.lock();
detector.destroy_condvar(condvar_id);
}
pub fn begin_wait(thread_id: ThreadId, condvar_id: CondvarId, mutex_id: LockId) {
let mut detector = GLOBAL_DETECTOR.lock();
detector.begin_wait(thread_id, condvar_id, mutex_id);
}
pub fn notify_one(condvar_id: CondvarId, notifier_id: ThreadId) {
let deadlocks = {
let mut detector = GLOBAL_DETECTOR.lock();
detector.notify_one(condvar_id, notifier_id)
};
for info in deadlocks {
deadlock_handling::process_deadlock(info);
}
}
pub fn notify_all(condvar_id: CondvarId, notifier_id: ThreadId) {
let deadlocks = {
let mut detector = GLOBAL_DETECTOR.lock();
detector.notify_all(condvar_id, notifier_id)
};
for info in deadlocks {
deadlock_handling::process_deadlock(info);
}
}
pub fn end_wait(thread_id: ThreadId, condvar_id: CondvarId, mutex_id: LockId) {
let mut detector = GLOBAL_DETECTOR.lock();
detector.end_wait(thread_id, condvar_id, mutex_id);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn end_wait_removes_exact_condvar_waiter() {
let mut detector = Detector::new();
detector.create_condvar(20);
detector.begin_wait(1, 20, 10);
detector.end_wait(1, 20, 10);
assert!(detector.cv_waiters[&20].is_empty());
assert!(!detector.thread_wait_cv.contains_key(&1));
assert!(!detector.cv_woken.contains(&1));
}
#[test]
fn notify_without_mutex_owner_does_not_invent_dependency() {
let mut detector = Detector::new();
detector.create_condvar(20);
detector.begin_wait(1, 20, 10);
let deadlocks = detector.notify_one(20, 9);
assert!(deadlocks.is_empty());
assert!(!detector.thread_waits_for.contains_key(&1));
}
}