Skip to main content

Crate refslots

Crate refslots 

Source
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

  • Registry maintains an atomic strong count for each slot, keyed by a slotmap::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 with Registry::take_released.
  • Handle is 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 metadata M, such as a TypeId for dynamically typed entities. The metadata is cloned with the handle and doesn’t take part in equality, ordering, or hashing.
  • WeakHandle is a weak reference to a slot. It does not contribute to the strong reference count and does not keep its slot alive. WeakHandle::upgrade returns 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(&registry, ());
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);

Structs§

Handle
Strong, reference-counted handle to a slot in a Registry.
Registry
Tracks strong reference counts for slots identified by a Key.
WeakHandle
Weak handle to a slot in Registry.