veridian_kernel/sync/hazard.rs
1//! Hazard Pointer Registry
2//!
3//! Hazard pointers provide safe memory reclamation for lock-free data
4//! structures. When a thread reads a shared pointer, it publishes the
5//! address in a per-CPU hazard pointer slot. Before freeing memory,
6//! the reclaimer checks all hazard pointers to ensure no thread is
7//! actively referencing the object.
8//!
9//! This is simpler than RCU for single-object protection and is used
10//! alongside RCU for different access patterns:
11//! - RCU: read-heavy data structures with infrequent updates
12//! - Hazard pointers: fine-grained per-object protection in lock-free
13//! containers (queues, stacks, hash maps)
14
15use core::sync::atomic::{AtomicUsize, Ordering};
16
17// ---------------------------------------------------------------------------
18// Configuration
19// ---------------------------------------------------------------------------
20
21/// Maximum number of CPUs (matches smp::MAX_CPUS).
22const MAX_CPUS: usize = 16;
23
24/// Number of hazard pointer slots per CPU.
25const SLOTS_PER_CPU: usize = 4;
26
27/// Total number of hazard pointer slots.
28const TOTAL_SLOTS: usize = MAX_CPUS * SLOTS_PER_CPU;
29
30/// Sentinel value indicating an unused hazard pointer slot.
31const HP_EMPTY: usize = 0;
32
33// ---------------------------------------------------------------------------
34// Global Hazard Pointer Array
35// ---------------------------------------------------------------------------
36
37/// Global hazard pointer slots. Each slot holds the address of a memory
38/// object that a thread is actively referencing. Set to HP_EMPTY when
39/// not in use.
40#[allow(clippy::declare_interior_mutable_const)]
41static HAZARD_POINTERS: [AtomicUsize; TOTAL_SLOTS] = {
42 const INIT: AtomicUsize = AtomicUsize::new(HP_EMPTY);
43 [INIT; TOTAL_SLOTS]
44};
45
46// ---------------------------------------------------------------------------
47// API
48// ---------------------------------------------------------------------------
49
50/// A guard that holds a hazard pointer slot. When dropped, the slot is
51/// cleared (set to HP_EMPTY).
52pub struct HazardGuard {
53 slot_index: usize,
54}
55
56impl Drop for HazardGuard {
57 fn drop(&mut self) {
58 HAZARD_POINTERS[self.slot_index].store(HP_EMPTY, Ordering::Release);
59 }
60}
61
62/// Protect a pointer by publishing it in a hazard pointer slot.
63///
64/// Returns a `HazardGuard` that clears the slot when dropped. The caller
65/// must keep the guard alive as long as the pointer is being accessed.
66///
67/// `slot` must be 0..SLOTS_PER_CPU-1 (per-CPU local slot index).
68pub fn protect(ptr: usize, slot: usize) -> HazardGuard {
69 debug_assert!(slot < SLOTS_PER_CPU, "slot index out of range");
70 let cpu = crate::sched::smp::current_cpu_id() as usize;
71 let global_slot = cpu * SLOTS_PER_CPU + slot;
72
73 HAZARD_POINTERS[global_slot].store(ptr, Ordering::Release);
74
75 // Memory barrier ensures the hazard pointer is visible to reclaimers
76 // before we proceed to access the protected object.
77 core::sync::atomic::fence(Ordering::SeqCst);
78
79 HazardGuard {
80 slot_index: global_slot,
81 }
82}
83
84/// Check whether a given address is currently protected by any hazard pointer.
85///
86/// Used by reclaimers before freeing memory to ensure no thread is actively
87/// referencing the object.
88pub fn is_protected(addr: usize) -> bool {
89 for slot in &HAZARD_POINTERS {
90 if slot.load(Ordering::Acquire) == addr {
91 return true;
92 }
93 }
94 false
95}
96
97/// Scan all hazard pointer slots and return a list of currently protected
98/// addresses. Used for batch reclamation: collect a retire list, then filter
99/// out any addresses that appear in the hazard set.
100pub fn collect_protected() -> [usize; TOTAL_SLOTS] {
101 let mut result = [HP_EMPTY; TOTAL_SLOTS];
102 for (i, slot) in HAZARD_POINTERS.iter().enumerate() {
103 result[i] = slot.load(Ordering::Acquire);
104 }
105 result
106}
107
108/// Clear all hazard pointer slots for a given CPU.
109///
110/// Called during CPU shutdown or thread exit to release all protections.
111pub fn clear_cpu_slots(cpu_id: u8) {
112 let base = (cpu_id as usize) * SLOTS_PER_CPU;
113 for i in 0..SLOTS_PER_CPU {
114 HAZARD_POINTERS[base + i].store(HP_EMPTY, Ordering::Release);
115 }
116}
117
118/// Get the number of active (non-empty) hazard pointer slots.
119pub fn active_count() -> usize {
120 HAZARD_POINTERS
121 .iter()
122 .filter(|slot| slot.load(Ordering::Relaxed) != HP_EMPTY)
123 .count()
124}