pub use backtrace::Backtrace;
use slotmap::Key;
use std::{collections::HashMap, fmt::Debug, thread};
pub mod assert;
mod backtrace;
mod messages;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct HandleId(u64);
impl HandleId {
pub(crate) const DETACHED: HandleId = HandleId(u64::MAX);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SlotSnapshot {
pub label: Option<&'static str>,
pub handle_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RecorderSnapshot<K: Key> {
pub slots: HashMap<K, SlotSnapshot>,
next_handle_id: u64,
}
#[derive(Debug, Clone)]
struct SlotRecord {
label: Option<&'static str>,
handles: HashMap<HandleId, Backtrace>,
}
impl SlotRecord {
fn new(label: Option<&'static str>) -> Self {
Self { label, handles: HashMap::default() }
}
fn snapshot(&self) -> SlotSnapshot {
SlotSnapshot { label: self.label, handle_count: self.handles.len() }
}
}
#[derive(Debug, Clone)]
pub struct Recorder<K: Key> {
slots: HashMap<K, SlotRecord>,
next_handle_id: u64,
}
impl<K: Key> Recorder<K> {
pub fn record_create_handle(
&mut self,
key: K,
label: Option<&'static str>,
backtrace: Backtrace,
) -> HandleId {
let handle_id = self.next_handle_id();
let record = self.slots.entry(key).or_insert_with(|| SlotRecord::new(label));
record.handles.insert(handle_id, backtrace);
handle_id
}
pub fn record_drop_handle(&mut self, key: K, handle_id: HandleId) {
let Some(record) = self.slots.get_mut(&key) else { return };
record.handles.remove(&handle_id);
if record.handles.is_empty() {
self.slots.remove(&key);
}
}
pub fn snapshot(&self) -> RecorderSnapshot<K> {
let mut slots = HashMap::with_capacity(self.slots.len());
for (key, record) in &self.slots {
slots.insert(*key, record.snapshot());
}
RecorderSnapshot { slots, next_handle_id: self.next_handle_id }
}
fn next_handle_id(&mut self) -> HandleId {
let id = self.next_handle_id;
self.next_handle_id += 1;
HandleId(id)
}
}
impl<K: Key> Default for Recorder<K> {
fn default() -> Self {
Self { slots: HashMap::default(), next_handle_id: 0 }
}
}
impl<K: Key> Drop for Recorder<K> {
fn drop(&mut self) {
if !thread::panicking() {
assert::all_handles_released(self);
}
}
}
fn collect_sorted<K: Ord, T>(map: &HashMap<K, T>) -> Vec<(&K, &T)> {
let mut entries = map.iter().collect::<Vec<_>>();
entries.sort_by_key(|(key, _)| *key);
entries
}
#[cfg(test)]
pub mod tests {
use crate::recorder::{Backtrace, HandleId};
use slotmap::{DefaultKey, KeyData};
pub type Recorder = super::Recorder<DefaultKey>;
mod create_handle {
use super::*;
#[test]
fn record_one() {
let mut recorder = Recorder::default();
let key = make_key(1);
record_create(&mut recorder, key);
assert!(recorder.slots.contains_key(&key));
let snapshot = recorder.snapshot();
assert_eq!(snapshot.slots.get(&key).map(|slot| slot.handle_count), Some(1));
shutdown(&mut recorder);
}
#[test]
fn record_many() {
let mut recorder = Recorder::default();
let key = make_key(1);
record_create(&mut recorder, key);
record_create(&mut recorder, key);
assert!(recorder.slots.contains_key(&key));
let snapshot = recorder.snapshot();
assert_eq!(snapshot.slots.get(&key).map(|slot| slot.handle_count), Some(2));
shutdown(&mut recorder);
}
}
mod drop_handle {
use super::*;
#[test]
fn record_one() {
let mut recorder = Recorder::default();
let key = make_key(1);
let handle_id = record_create(&mut recorder, key);
recorder.record_drop_handle(key, handle_id);
assert!(!recorder.slots.contains_key(&key));
let snapshot = recorder.snapshot();
assert!(snapshot.slots.is_empty());
}
#[test]
fn record_many() {
let mut recorder = Recorder::default();
let key = make_key(1);
let handle_id = record_create(&mut recorder, key);
record_create(&mut recorder, key);
recorder.record_drop_handle(key, handle_id);
assert!(recorder.slots.contains_key(&key));
let snapshot = recorder.snapshot();
assert_eq!(snapshot.slots.get(&key).map(|slot| slot.handle_count), Some(1));
shutdown(&mut recorder);
}
}
#[test]
fn drop_released() {
let mut recorder = Recorder::default();
let key = make_key(1);
let handle_id = record_create(&mut recorder, key);
recorder.record_drop_handle(key, handle_id);
}
#[test]
#[should_panic(expected = "1 slots with undropped handles")]
fn drop_unreleased() {
let mut recorder = Recorder::default();
let key = make_key(1);
record_create(&mut recorder, key);
}
#[test]
fn snapshot_empty() {
let recorder = Recorder::default();
assert!(recorder.snapshot().slots.is_empty());
}
pub fn make_key(value: u64) -> DefaultKey {
KeyData::from_ffi(value).into()
}
pub fn record_create(recorder: &mut Recorder, key: DefaultKey) -> HandleId {
recorder.record_create_handle(key, None, Backtrace::new_unresolved())
}
pub fn shutdown(recorder: &mut Recorder) {
recorder.slots.clear();
}
}