#[cfg(debug_assertions)]
use super::safety_counters::HNSW_COUNTERS;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub(crate) enum LockRank {
#[cfg_attr(not(feature = "gpu"), allow(dead_code))]
GpuVectorsSnapshot = 5,
Vectors = 10,
#[allow(dead_code)] Columnar = 15,
Layers = 20,
#[allow(dead_code)] Neighbors = 30,
}
#[cfg(debug_assertions)]
use std::cell::RefCell;
#[cfg(debug_assertions)]
thread_local! {
static LOCK_RANK_STACK: RefCell<Vec<LockRank>> = const { RefCell::new(Vec::new()) };
}
#[inline]
pub(crate) fn record_lock_acquire(rank: LockRank) {
#[cfg(debug_assertions)]
{
LOCK_RANK_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if let Some(&highest) = stack.last() {
if rank <= highest {
HNSW_COUNTERS.record_invariant_violation();
tracing::warn!(
acquired = ?rank,
highest_held = ?highest,
"HNSW lock-order violation: acquiring {:?} while holding {:?}",
rank,
highest,
);
}
}
stack.push(rank);
});
}
#[cfg(not(debug_assertions))]
let _ = rank;
}
#[inline]
pub(crate) fn record_lock_release(rank: LockRank) {
#[cfg(debug_assertions)]
{
LOCK_RANK_STACK.with(|stack| {
let mut stack = stack.borrow_mut();
if let Some(top) = stack.pop() {
if top != rank {
HNSW_COUNTERS.record_corruption();
}
}
});
}
#[cfg(not(debug_assertions))]
let _ = rank;
}
#[allow(dead_code)] #[cfg(all(test, debug_assertions))]
pub(crate) fn lock_depth() -> usize {
LOCK_RANK_STACK.with(|stack| stack.borrow().len())
}
#[cfg_attr(not(any(feature = "gpu", test)), allow(dead_code))]
#[cfg(debug_assertions)]
pub(crate) fn holds_lock(rank: LockRank) -> bool {
LOCK_RANK_STACK.with(|stack| stack.borrow().contains(&rank))
}
#[cfg_attr(not(any(feature = "gpu", test)), allow(dead_code))]
#[cfg(not(debug_assertions))]
#[inline]
pub(crate) fn holds_lock(_rank: LockRank) -> bool {
true
}
#[cfg(all(test, debug_assertions))]
mod tests {
use super::*;
#[test]
fn holds_lock_reports_currently_held_rank() {
assert!(!holds_lock(LockRank::Layers));
assert!(!holds_lock(LockRank::Vectors));
record_lock_acquire(LockRank::Layers);
assert!(holds_lock(LockRank::Layers));
assert!(!holds_lock(LockRank::Vectors));
record_lock_release(LockRank::Layers);
assert!(!holds_lock(LockRank::Layers));
}
#[test]
fn gpu_vectors_snapshot_rank_sorts_before_vectors() {
assert!(LockRank::GpuVectorsSnapshot < LockRank::Vectors);
assert!(LockRank::Vectors < LockRank::Columnar);
assert!(LockRank::Columnar < LockRank::Layers);
assert!(LockRank::Layers < LockRank::Neighbors);
}
#[test]
fn nested_acquire_in_declared_order_reports_both_held() {
record_lock_acquire(LockRank::GpuVectorsSnapshot);
record_lock_acquire(LockRank::Vectors);
assert!(holds_lock(LockRank::GpuVectorsSnapshot));
assert!(holds_lock(LockRank::Vectors));
record_lock_release(LockRank::Vectors);
record_lock_release(LockRank::GpuVectorsSnapshot);
assert!(!holds_lock(LockRank::GpuVectorsSnapshot));
assert!(!holds_lock(LockRank::Vectors));
}
}