Expand description
Reference-counted handles to slots in a SlotMap.
This crate tracks the strong reference count to each slot and reports slots that have been released when their last reference is dropped. It does not store values, which makes it a building block for data structures that need reference-counted identity: entity maps, focus tracking, resource tables.
§Overview
Registrymaintains an atomic strong count for each slot, keyed by aslotmap::Key. A slot is created with a count of 1. When its count reaches 0, the slot is removed and its key pushed to a release queue to be drained withRegistry::take_released.Handleis a strong reference to a slot. Cloning a handle increments the count and dropping it decrements the count. Dropping the last handle releases the slot. A handle can carry metadataM, such as aTypeIdfor dynamically typed entities. The metadata is cloned with the handle and doesn’t take part in equality, ordering, or hashing.WeakHandleis a weak reference to a slot. It does not contribute to the strong reference count and does not keep its slot alive.WeakHandle::upgradereturns a strong handle only if the slot is still alive. The liveness check and increment are performed as an atomic step so a slot that is being released cannot be revived.
The internal SlotMap uses versioned keys to allow reusing indexes. Therefore, a stale key or
weak handle can never resolve to a newer slot that occupies the same index in the SlotMap.
§Usage
A registry is shared as an Arc<RwLock<Registry<K>>>. Handles hold a weak reference to the
registry that created them. Cloning and dropping a handle attempts to upgrade the weak reference
for the operation. If the upgrade fails then there is no effect on the reference counters.
Values associated with slots are stored by the owners of the registry, for example in a
SecondaryMap indexed by Handle::key. The owner should drain the
release queue periodically, removing data associated with each released key and performing any
cleanup that is required. The entitymap example shows this pattern.
A separate registry should be used for each key type. Handle::check_context compares
registries by pointer identity, which detects a handle being used with a registry other than the
one that created it.
§Locking
Handles are Send and Sync when their metadata is Send and Sync. This allows them to be
cloned and dropped on any thread. Reference counters are atomic, but handles access the registry
through its lock. Cloning and upgrading acquire a read lock. Dropping acquires an upgradable
read lock which is upgraded to a write lock when the last reference is being dropped. Inserting
and draining the release queue acquire a write lock.
Handles must not be cloned, dropped, or inserted while the calling thread holds a guard on the
registry’s lock as this might deadlock. Registry::take_released returns an iterator that
borrows the registry mutably. The iterator should be collected before continuing with operations
that might create or drop handles.
§Leak Detection
When the recorder feature is enabled every handle is given a unique, monotonically increasing
identifier and records a backtrace of where it was created or cloned. (The feature is always
enabled for this crate’s tests.) Registry provides snapshots and assertions for verifying
that handles have been dropped. Dropping a registry that still has live handles panics with a
report listing each leaked handle and its creation backtrace.
Recording is intended for debugging and testing. A backtrace is captured on every creation and clone, and every clone and drop acquires the registry’s write lock. Recording is not indented for use in production.
§Example
use parking_lot::RwLock;
use refslots::{Handle, Registry};
use std::sync::Arc;
slotmap::new_key_type! { struct FocusId; }
let registry = Arc::new(RwLock::new(Registry::<FocusId>::default()));
let handle = Handle::insert(®istry, ());
let weak = handle.downgrade();
drop(handle);
assert!(weak.upgrade().is_none());
let released: Vec<FocusId> = registry.write().take_released().collect();
assert_eq!(released.len(), 1);