refslots 0.2.0

Reference-counted handles to slots in a slotmap
Documentation
use parking_lot::RwLock;
use std::{
    sync::{Arc, Barrier},
    thread,
};

const THREADS: usize = 8;
const ITERS: usize = 10_000;

slotmap::new_key_type! {
    struct Id;
}

type Handle = refslots::Handle<Id>;
type RegistryCell = Arc<RwLock<refslots::Registry<Id>>>;

/// Tests that while a strong handle is held, clone, upgrade, and drop from many threads never fails
/// an upgrade, never releases the slot, and never leaks the count.
#[test]
fn clone_upgrade_drop() {
    let registry = RegistryCell::default();
    let primary = Handle::insert(&registry, ());
    let key = primary.key();
    let weak = primary.downgrade();

    thread::scope(|s| {
        for _ in 0..THREADS {
            let weak = weak.clone();
            let held = primary.clone(); // dropped on different thread than created it
            s.spawn(move || {
                for _ in 0..ITERS {
                    // Primary handle is live, so the upgrade can never fail.
                    let strong = weak.upgrade().expect("slot is live");
                    let clone = strong.clone();
                    drop(strong);
                    drop(clone);
                }
                drop(held);
            });
        }
    });

    // Every thread's references are gone. Only the primary handle remains.
    assert_eq!(registry.read().strong_count(key), Some(1));
    assert_eq!(registry.write().take_released().count(), 0);

    drop(primary);
    assert!(registry.read().strong_count(key).is_none());

    let released = registry.write().take_released().collect::<Vec<_>>();
    assert_eq!(released, [key]);
    assert!(weak.upgrade().is_none());
}

const RACE_ROUNDS: usize = 100;

/// Tests the race between the last strong drop and concurrent upgrades. For any interleaving, the
/// slot must be released exactly once, an upgrade must either fully succeed or fail, and no thread
/// should observe a revived slot.
#[test]
fn last_drop_upgrade_race() {
    for _ in 0..RACE_ROUNDS {
        let registry = RegistryCell::default();
        let primary = Handle::insert(&registry, ());
        let key = primary.key();
        let weak = primary.downgrade();

        let barrier = Barrier::new(THREADS + 1);
        thread::scope(|s| {
            for _ in 0..THREADS {
                let weak = weak.clone();
                let barrier = &barrier;
                s.spawn(move || {
                    barrier.wait();
                    for _ in 0..ITERS {
                        let Some(strong) = weak.upgrade() else { continue };
                        assert_eq!(strong.key(), key);
                        drop(strong);
                    }
                });
            }
            barrier.wait();
            drop(primary); // primary handle races against the upgrading threads
        });

        // All handles are gone. The slot should be released exactly once.
        assert!(registry.read().strong_count(key).is_none());

        let released = registry.write().take_released().collect::<Vec<_>>();
        assert_eq!(released, [key]);
        assert!(weak.upgrade().is_none());
    }
}