kovan-map 0.2.3

Lock-free concurrent hash maps using kovan memory reclamation
Documentation
//! Test-only stops at the points a resize or a displacement interleaving turns on. A thread
//! armed for a point meets the test there once: it reports its arrival and waits until the test
//! lets it go, so a test replays an interleaving step by step instead of hoping a scheduler
//! produces it. Compiled only under `cfg(test)`; the calls in the map's code vanish from every
//! other build.

extern crate std;

use std::cell::RefCell;
use std::sync::mpsc::{Receiver, SyncSender};

/// A point in the map's code a test can stop one thread at.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Point {
    /// An insert holds its home bucket's writer guard, has scanned for its key and has not
    /// claimed a slot yet.
    InGuardBeforeClaim,
    /// An insert's write landed and its home guard is released; it has not returned yet.
    AfterLanding,
    /// A resize or a clear found a home bucket's writer guard held by a writer.
    ResizerMetHeldGuard,
    /// An insert or a remove found its home bucket's writer guard held by another writer.
    WriterMetHeldGuard,
    /// A lookup read its home's hop bits and has not read a slot yet.
    LookupReadHops,
    /// A writer's scan under its home guard loaded the word of a slot the home's bits name,
    /// without protecting the entry it names, and has not read the entry yet.
    HeldScanLoaded,
    /// A displacement linked the entry it moves at its new slot and published the new slot's
    /// hop bit and the advanced move stamp; the entry is still in its old slot too.
    MoveLinkedTwice,
    /// A displacement unlinked the moved entry from its old slot, whose hop bit is still set.
    MoveUnlinked,
    /// A compute of an absent key holds its home guard and reserved the slot its entry would
    /// take; its closure has not run yet.
    ComputeReserved,
}

/// Where an armed thread stops, and the two channels it meets the test on.
pub(super) struct Stop {
    pub(super) point: Point,
    /// Signalled when the thread reaches `point`.
    pub(super) arrived: SyncSender<()>,
    /// Received from before the thread goes on.
    pub(super) go: Receiver<()>,
}

std::thread_local! {
    static ARMED: RefCell<Option<Stop>> = const { RefCell::new(None) };
}

/// Stop this thread at `stop.point` the next time it gets there.
pub(super) fn arm(stop: Stop) {
    ARMED.with(|armed| *armed.borrow_mut() = Some(stop));
}

/// Called by the map at `point`: a thread armed for it reports its arrival and waits to be let
/// go, once; any other thread, or a thread armed for another point, passes straight through.
pub(super) fn at(point: Point) {
    let stop = ARMED.with(|armed| {
        let mut armed = armed.borrow_mut();
        if armed.as_ref().is_some_and(|stop| stop.point == point) {
            armed.take()
        } else {
            None
        }
    });
    if let Some(stop) = stop {
        // A test that failed first has dropped its ends of the channels. This thread then runs
        // its operation to the end instead of unwinding from inside it: a panic at a point
        // inside a move would leave the entry linked in two slots, and the table's drop would
        // free it twice.
        if stop.arrived.send(()).is_ok() {
            let _ = stop.go.recv();
        }
    }
}