use slotmap::{Key, SlotMap};
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
#[cfg(any(test, feature = "recorder"))]
use crate::recorder::{self, Backtrace, HandleId, Recorder, RecorderSnapshot};
#[derive(Debug)]
pub struct Registry<K: Key> {
counters: SlotMap<K, AtomicUsize>,
release_queue: Vec<K>,
#[cfg(any(test, feature = "recorder"))]
recorder: Recorder<K>,
}
impl<K: Key> Registry<K> {
pub fn insert(&mut self) -> K {
self.counters.insert(AtomicUsize::new(1))
}
pub fn increment(&self, key: K) -> usize {
let counter = self.counter(key);
let mut count = counter.load(SeqCst);
loop {
assert_ne!(count, 0, "handle: {key:?} over-release detected");
match counter.compare_exchange_weak(count, count + 1, SeqCst, SeqCst) {
Ok(prev) => return prev,
Err(actual) => count = actual,
}
}
}
pub fn upgrade(&self, key: K) -> bool {
let Some(counter) = self.counters.get(key) else { return false };
let mut count = counter.load(SeqCst);
while count > 0 {
match counter.compare_exchange_weak(count, count + 1, SeqCst, SeqCst) {
Ok(_) => return true,
Err(actual) => count = actual,
}
}
false
}
pub fn decrement(&self, key: K) -> usize {
let counter = self.counter(key);
let mut count = counter.load(SeqCst);
loop {
assert_ne!(count, 0, "handle: {key:?} over-release detected");
match counter.compare_exchange_weak(count, count - 1, SeqCst, SeqCst) {
Ok(prev) => return prev,
Err(actual) => count = actual,
}
}
}
pub fn release(&mut self, key: K) {
let count = self.counter(key).load(SeqCst);
assert_eq!(count, 0, "handle: {key:?} released with count: {count} ≠0");
self.counters.remove(key);
self.release_queue.push(key);
}
pub fn take_released(&mut self) -> impl Iterator<Item = K> + '_ {
self.release_queue.drain(..)
}
pub fn strong_count(&self, key: K) -> Option<usize> {
Some(self.counters.get(key)?.load(SeqCst))
}
fn counter(&self, key: K) -> &AtomicUsize {
self.counters.get(key).unwrap_or_else(|| {
panic!("handle: {key:?} already released");
})
}
}
#[cfg(any(test, feature = "recorder"))]
impl<K: Key> Registry<K> {
pub(crate) fn record_create_handle(
registry: &std::sync::Weak<parking_lot::RwLock<Self>>,
key: K,
label: Option<&'static str>,
) -> HandleId {
let Some(registry) = registry.upgrade() else { return HandleId::DETACHED };
let backtrace = Backtrace::new_unresolved();
registry.write().recorder.record_create_handle(key, label, backtrace)
}
pub(crate) fn record_drop_handle(
registry: &std::sync::Weak<parking_lot::RwLock<Self>>,
key: K,
handle_id: HandleId,
) {
let Some(registry) = registry.upgrade() else { return };
registry.write().recorder.record_drop_handle(key, handle_id);
}
pub fn snapshot(&self) -> RecorderSnapshot<K> {
self.recorder.snapshot()
}
pub fn assert_handles_released(&self, key: K) {
recorder::assert::handles_released(&self.recorder, key);
}
pub fn assert_handles_released_since(&self, snapshot: &RecorderSnapshot<K>) {
recorder::assert::handles_released_since(&self.recorder, snapshot);
}
}
impl<K: Key> Default for Registry<K> {
fn default() -> Self {
Self {
counters: SlotMap::with_key(),
release_queue: vec![],
#[cfg(any(test, feature = "recorder"))]
recorder: Recorder::default(),
}
}
}
#[cfg(test)]
mod tests {
use slotmap::DefaultKey;
type Registry = super::Registry<DefaultKey>;
#[test]
fn insert() {
let mut registry = Registry::default();
let key = registry.insert();
assert_eq!(registry.strong_count(key), Some(1));
assert!(registry.release_queue.is_empty());
}
mod increment {
use super::*;
use std::{panic, panic::AssertUnwindSafe};
#[test]
fn live() {
let mut registry = Registry::default();
let key = registry.insert();
registry.increment(key);
assert_eq!(registry.strong_count(key), Some(2));
}
#[test]
fn unlive() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
registry.increment(key);
}));
assert!(result.is_err());
assert_eq!(registry.strong_count(key), Some(0));
assert!(!registry.upgrade(key));
}
#[test]
#[should_panic(expected = "already released")]
fn released() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
registry.increment(key);
}
}
mod upgrade {
use super::*;
#[test]
fn upgrade() {
let mut registry = Registry::default();
let key = registry.insert();
assert!(registry.upgrade(key));
assert_eq!(registry.strong_count(key), Some(2));
}
#[test]
fn unlive() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
assert!(!registry.upgrade(key));
}
#[test]
fn released() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
assert!(!registry.upgrade(key));
}
}
mod decrement {
use super::*;
use std::{panic, panic::AssertUnwindSafe};
#[test]
fn live() {
let mut registry = Registry::default();
let key = registry.insert();
assert_eq!(registry.decrement(key), 1);
assert!(registry.release_queue.is_empty());
}
#[test]
fn unlive() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
let result = panic::catch_unwind(AssertUnwindSafe(|| {
registry.decrement(key);
}));
assert!(result.is_err());
assert_eq!(registry.strong_count(key), Some(0));
assert!(!registry.upgrade(key));
}
#[test]
#[should_panic(expected = "already released")]
fn released() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
registry.decrement(key);
}
}
mod release {
use super::*;
#[test]
#[should_panic(expected = "released with count")]
fn live() {
let mut registry = Registry::default();
let key = registry.insert();
registry.release(key);
}
#[test]
fn unlive() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
assert!(registry.strong_count(key).is_none());
assert_eq!(registry.release_queue.len(), 1);
}
#[test]
#[should_panic(expected = "already released")]
fn released() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
registry.release(key);
}
}
mod take_released {
use super::*;
#[test]
fn drain() {
let mut registry = Registry::default();
let keys = (0..3).map(|_| registry.insert()).collect::<Vec<_>>();
registry.decrement(keys[0]);
registry.release(keys[0]);
registry.decrement(keys[1]);
registry.release(keys[1]);
let released = registry.take_released().collect::<Vec<_>>();
assert_eq!(released, [keys[0], keys[1]]);
assert_eq!(registry.strong_count(keys[2]), Some(1));
}
#[test]
fn clear() {
let mut registry = Registry::default();
let key = registry.insert();
registry.decrement(key);
registry.release(key);
assert_eq!(registry.take_released().count(), 1);
assert_eq!(registry.take_released().count(), 0);
}
#[test]
fn empty() {
let mut registry = Registry::default();
registry.insert();
assert_eq!(registry.take_released().count(), 0);
}
}
#[test]
fn stale_key() {
let mut registry = Registry::default();
let key1 = registry.insert();
registry.decrement(key1);
registry.release(key1);
let key2 = registry.insert(); assert_ne!(key1, key2);
assert!(!registry.upgrade(key1));
assert!(registry.strong_count(key1).is_none());
assert_eq!(registry.strong_count(key2), Some(1))
}
}