use super::*;
use crate::core::allocator::TrackingGuard;
use std::sync::OnceLock;
pub(crate) fn global_tracker() -> &'static Tracker {
static GLOBAL: OnceLock<Tracker> = OnceLock::new();
GLOBAL.get_or_init(|| {
let _suppression = TrackingGuard::acquire();
Tracker {
inner: crate::capture::backends::core_tracker::get_global_tracker(),
event_store: Arc::new(EventStore::new()),
recorded_bindings: Arc::new(DashMap::new()),
config: Arc::new(Mutex::new(TrackerConfig {
sampling: SamplingConfig::default(),
auto_export_on_drop: false,
export_path: None,
})),
start_time: Instant::now(),
system_snapshots: Arc::new(Mutex::new(Vec::new())),
}
})
}
#[derive(Clone, Copy)]
struct AllocatorCharge {
source: usize,
serial: u64,
outcome: crate::task_registry::ChargeOutcome,
}
fn charged_addresses() -> &'static DashMap<usize, AllocatorCharge> {
static CHARGED: OnceLock<DashMap<usize, AllocatorCharge>> = OnceLock::new();
CHARGED.get_or_init(DashMap::new)
}
pub(crate) fn allocator_charge(ptr: usize) -> Option<crate::task_registry::ChargeOutcome> {
charged_addresses().get(&ptr).map(|mark| mark.outcome)
}
impl Tracker {
fn charge_source(&self) -> usize {
Arc::as_ptr(&self.recorded_bindings) as usize
}
pub(super) fn clear_allocator_charge(&self, ptr: usize, serial: u64) {
charged_addresses().remove_if(&ptr, |_, mark| {
mark.source == self.charge_source() && mark.serial == serial
});
}
pub(super) fn migrate_allocator_charge(&self, old_ptr: usize, new_ptr: usize, serial: u64) {
if old_ptr == new_ptr {
return;
}
if let Some((_, mark)) = charged_addresses().remove_if(&old_ptr, |_, mark| {
mark.source == self.charge_source() && mark.serial == serial
}) {
charged_addresses().insert(new_ptr, mark);
}
}
pub(crate) fn record_allocator_allocation(&self, ptr: usize, size: usize) {
let _gate = crate::core::allocator::capture_gate::acquire(ptr);
let slot = self.recorded_slot(ptr);
let mut guard = slot.lock().unwrap_or_else(|error| error.into_inner());
if self.inner.track_allocation(ptr, size).is_err() {
return;
}
let Some(serial) = self.inner.binding_serial(ptr) else {
return;
};
if guard
.get(&serial)
.is_some_and(|binding| binding.size == size)
{
return;
}
let mut event = MemoryEvent::allocate(ptr, size, crate::utils::current_thread_id_u64());
event.binding_serial = Some(serial);
let outcome = crate::task_registry::global_registry().try_record_allocation(size);
event.charge_outcome = Some(outcome);
self.event_store.record(event);
charged_addresses().insert(
ptr,
AllocatorCharge {
source: self.charge_source(),
serial,
outcome,
},
);
guard.insert(
serial,
RecordedBinding {
size,
typed: false,
charged: true,
},
);
}
pub(crate) fn allocator_reallocation(
&self,
old_ptr: usize,
old_size: usize,
new_size: usize,
reallocate: impl FnOnce() -> *mut u8,
) -> *mut u8 {
let _gate = crate::core::allocator::capture_gate::acquire(old_ptr);
let slot = self.recorded_slot(old_ptr);
let mut old_guard = slot.lock().unwrap_or_else(|error| error.into_inner());
let new_ptr = reallocate();
if new_ptr.is_null() {
return new_ptr;
}
let address = new_ptr as usize;
if address == old_ptr {
if let Some((serial, binding)) =
self.record_reallocation(old_ptr, address, old_size, new_size)
{
old_guard.insert(serial, binding);
}
} else {
let new_slot = self.recorded_slot(address);
let mut new_guard = new_slot.lock().unwrap_or_else(|error| error.into_inner());
let binding = self.record_reallocation(old_ptr, address, old_size, new_size);
if let Some((serial, binding)) = binding {
old_guard.remove(&serial);
new_guard.insert(serial, binding);
}
drop(old_guard);
drop(new_guard);
drop(slot);
drop(new_slot);
if binding.is_some() {
self.reclaim_slot(old_ptr);
}
}
new_ptr
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn failed_reallocation_keeps_original_binding() {
let tracker = Tracker::new();
tracker.record_allocator_allocation(4096, 64);
let before = tracker.events();
let result = tracker.allocator_reallocation(4096, 64, usize::MAX, std::ptr::null_mut);
assert!(result.is_null(), "injected failure must reach the caller");
assert_eq!(
tracker.events().len(),
before.len(),
"failure must not publish a lifecycle transition"
);
assert_eq!(
tracker.inner.get_allocation_size(4096),
Some(64),
"original block remains live after failure"
);
assert_eq!(
tracker.inner.binding_serial(4096),
before[0].binding_serial,
"failure cannot replace identity"
);
}
#[test]
fn repeated_allocator_report_does_not_duplicate_the_event() {
let tracker = Tracker::new();
let ptr = 0x2000usize;
tracker.record_allocator_allocation(ptr, 64);
tracker.record_allocator_allocation(ptr, 64);
let allocations = |tracker: &Tracker| {
tracker
.events()
.into_iter()
.filter(|event| {
event.ptr == ptr
&& event.event_type == crate::event_store::MemoryEventType::Allocate
})
.count()
};
assert_eq!(
allocations(&tracker),
1,
"a repeated report of the same binding must not publish another event"
);
assert!(
tracker
.track_deallocation(ptr)
.expect("release must succeed"),
"the block must have been tracked before its release"
);
tracker.record_allocator_allocation(ptr, 128);
assert_eq!(
allocations(&tracker),
2,
"a new binding at the reused address must be published"
);
}
}
#[cfg(test)]
mod charge_tests;