Skip to main content

kovan_map/
hashmap.rs

1//! High-Performance Lock-Free Concurrent Hash Map (FoldHash + resizable table).
2//!
3//! # Strategy
4//!
5//! 1. **FoldHash**: `foldhash::fast::FixedState` for fast, quality hashing.
6//! 2. **Resizable bucket table**: the bucket array lives in a single
7//!    allocation (header + inline buckets) swapped atomically on resize and
8//!    reclaimed through kovan. The map grows when the load factor exceeds
9//!    3/4 and shrinks below 1/4 (never under its initial capacity), the
10//!    same thresholds as `HopscotchMap`.
11//! 3. **Node layout**: `hash` and `next` side by side, so a walk passing a
12//!    node reads one cache line of it.
13//!
14//! # Architecture
15//! - **Table**: kovan-retired object holding the bucket array (atomic head
16//!   pointers) and its entry count. Readers snapshot the table under a guard
17//!   and never block.
18//! - **Chains**: singly linked, lock-free. Every change of the map's content
19//!   is one CAS on one link word (`node`): an insert links a new node at the
20//!   tail, a remove marks the node's own `next`, a replace marks it naming
21//!   the new node, which names the old successor. That CAS is the
22//!   operation's linearization point, and the answer the operation gives is
23//!   exact: `insert_if_absent` answers `None` exactly when its CAS linked
24//!   the key's node, and otherwise the value of the node it found live.
25//! - **Reclamation**: a node is retired only by the thread whose CAS unlinked
26//!   it, never while a table can reach it, and a walk steps past a deleted
27//!   node only after checking the link it came through still names it
28//!   (`walk`), so every node a walk reads is protected by its guard.
29//! - **Resize**: a single resizer (latch `resizing`) freezes every link of
30//!   the old table in chain order while it copies the live nodes, then
31//!   publishes the new table and retires the old one (`resize`). A write
32//!   whose CAS landed is final; a write that meets a frozen link waits for
33//!   the new table and writes there.
34//!
35//! The protocol is modelled in `tla/chained/ChainedMap.tla` and checked by TLC
36//! with every rule 0.1.20 had put back one at a time (`tla/README.md`).
37
38extern crate alloc;
39
40#[cfg(feature = "std")]
41extern crate std;
42
43use crate::sync::AtomicBool;
44use alloc::boxed::Box;
45use core::borrow::Borrow;
46use core::hash::{BuildHasher, Hash};
47use core::sync::atomic::Ordering;
48use foldhash::fast::FixedState;
49use kovan::{Atomic, Guard, pin, retire};
50use node::{MARK, Node, with, word};
51use table::{TableHeader, TableRef};
52use walk::Found;
53
54pub use iter::{IntoIter, Iter, Keys, Values};
55
56mod iter;
57mod node;
58mod resize;
59mod std_traits;
60mod table;
61mod walk;
62
63/// Default number of buckets for `new()`. Matches the previous fixed-table
64/// sizing (zero collisions for ~100k items, fits in L3); maps created with
65/// `new()` keep exactly the old memory/performance profile and additionally
66/// grow past it on demand. Use [`HashMap::with_capacity`] for small elastic
67/// maps.
68const DEFAULT_CAPACITY: usize = 524_288;
69
70/// Minimum number of buckets; the map never shrinks below this.
71const MIN_CAPACITY: usize = 64;
72
73// Load-factor thresholds (implemented as integer comparisons on the hot
74// paths): grow when count/capacity > 3/4, shrink when count/capacity < 1/4
75// (never below the map's floor capacity). Same thresholds as HopscotchMap.
76
77/// A simple exponential backoff for reducing contention.
78struct Backoff {
79    step: u32,
80}
81
82impl Backoff {
83    #[inline(always)]
84    fn new() -> Self {
85        Self { step: 0 }
86    }
87
88    #[inline(always)]
89    fn spin(&mut self) {
90        for _ in 0..(1 << self.step.min(6)) {
91            core::hint::spin_loop();
92        }
93        if self.step <= 6 {
94            self.step += 1;
95        }
96    }
97}
98
99/// High-Performance Lock-Free Map with automatic grow/shrink.
100pub struct HashMap<K: 'static, V: 'static, S = FixedState> {
101    table: Atomic<TableHeader>,
102    /// Single-resizer latch, held by a resize or a clear from before it
103    /// freezes the first link until after it publishes the new table.
104    resizing: AtomicBool,
105    /// Shrink floor: the initial capacity. The map never shrinks below the
106    /// size it was created with, preserving the caller's sizing intent (and
107    /// the historical fixed-table behavior for `new()`).
108    floor: usize,
109    hasher: S,
110    _marker: core::marker::PhantomData<(K, V)>,
111}
112
113/// The node an insert links: its key and value until an attempt needs the allocation, then that
114/// allocation, carried across the attempts that fail to link it, so a retry neither clones the
115/// key and value nor allocates again, and a losing call drops them exactly once.
116enum Pending<K, V> {
117    Parts { key: K, value: V },
118    Built(Box<Node<K, V>>),
119}
120
121impl<K, V> Pending<K, V> {
122    #[inline(always)]
123    fn key(&self) -> &K {
124        match self {
125            Self::Parts { key, .. } => key,
126            Self::Built(node) => &node.key,
127        }
128    }
129
130    /// The node, allocated the first time an attempt needs it, its `next` set to `next`.
131    #[inline]
132    fn into_node(self, hash: u64, next: *mut Node<K, V>) -> *mut Node<K, V> {
133        let node = match self {
134            Self::Parts { key, value } => Box::new(Node::new(hash, key, value)),
135            Self::Built(node) => node,
136        };
137        // Relaxed: the node is private until the CAS that links it releases it.
138        node.next.store(word(next), Ordering::Relaxed);
139        Box::into_raw(node)
140    }
141
142    /// The node an attempt failed to link, back for the next attempt.
143    #[inline]
144    fn back(node: *mut Node<K, V>) -> Self {
145        // SAFETY: the CAS that would have published `node` failed, so it is still the
146        // allocation `into_node` gave this call.
147        Self::Built(unsafe { Box::from_raw(node) })
148    }
149}
150
151/// How a conditional insert ended.
152enum Claim<R, V> {
153    /// This call linked the key's node; what the caller asked of the value it linked.
154    Inserted(R),
155    /// The key was present; its value.
156    Present(V),
157}
158
159// Small accessors that never hash: only the struct's own `'static` bound, as std's equivalent
160// block for `with_hasher`/`with_capacity_and_hasher`/`capacity`/`len`/`is_empty`/`hasher` needs
161// no `Hash`, `Eq`, `Clone` or `BuildHasher`. A method that hashes or clones a value lives in the
162// bound impl block below instead.
163impl<K: 'static, V: 'static, S> HashMap<K, V, S> {
164    /// Creates a new hash map with custom hasher.
165    pub fn with_hasher(hasher: S) -> Self {
166        Self::with_capacity_and_hasher(DEFAULT_CAPACITY, hasher)
167    }
168
169    /// Creates a new hash map with at least `capacity` buckets and a custom hasher.
170    ///
171    /// The map grows when its load factor exceeds 0.75 and shrinks when it
172    /// falls below 0.25 - but never below `capacity`.
173    pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
174        let table = TableRef::<K, V>::alloc(capacity);
175        let floor = table.capacity();
176        Self {
177            table: Atomic::new(table.as_raw()),
178            resizing: AtomicBool::new(false),
179            floor,
180            hasher,
181            _marker: core::marker::PhantomData,
182        }
183    }
184
185    /// Returns the current number of buckets.
186    pub fn capacity(&self) -> usize {
187        let guard = pin();
188        self.table_ref(&guard).capacity()
189    }
190
191    /// Returns true if the map is empty.
192    pub fn is_empty(&self) -> bool {
193        self.len() == 0
194    }
195
196    /// Returns the number of elements in the map.
197    ///
198    /// O(1): the current table's count, kept by insert/remove and set exactly
199    /// by a resize. Approximate while concurrent updates are in flight (a
200    /// write counts itself right after its CAS), exact in quiescence.
201    pub fn len(&self) -> usize {
202        let guard = pin();
203        self.table_ref(&guard)
204            .count()
205            .load(Ordering::Relaxed)
206            .max(0) as usize
207    }
208
209    /// Get the underlying hasher itself.
210    pub fn hasher(&self) -> &S {
211        &self.hasher
212    }
213}
214
215// Construction with the built-in hasher never hashes either: the struct's own `'static` only,
216// as std's `HashMap::new`/`with_capacity` carry no bound.
217#[cfg(feature = "std")]
218impl<K: 'static, V: 'static> HashMap<K, V, FixedState> {
219    /// Creates a new empty hash map with FoldHash (FixedState).
220    pub fn new() -> Self {
221        Self::with_hasher(FixedState::default())
222    }
223
224    /// Creates a new empty hash map with at least `capacity` buckets.
225    pub fn with_capacity(capacity: usize) -> Self {
226        Self::with_capacity_and_hasher(capacity, FixedState::default())
227    }
228}
229
230impl<K, V, S> HashMap<K, V, S>
231where
232    K: Hash + Eq + Clone + 'static,
233    V: Clone + 'static,
234    S: BuildHasher,
235{
236    /// Returns the value of `key`. Never blocks: reads the current table
237    /// under a guard, even while a resize is in flight.
238    ///
239    /// Linearizable: the answer is the value of a node that was not deleted
240    /// at the moment its `next` was read, or `None` when the key's chain
241    /// held no such node.
242    pub fn get<Q>(&self, key: &Q) -> Option<V>
243    where
244        K: Borrow<Q>,
245        Q: Hash + Eq + ?Sized,
246    {
247        let hash = self.hasher.hash_one(key);
248        let guard = pin();
249        self.lookup(hash, key, &guard)
250            .map(|node| node.value.clone())
251    }
252
253    /// Checks if the key exists (without cloning its value).
254    pub fn contains_key<Q>(&self, key: &Q) -> bool
255    where
256        K: Borrow<Q>,
257        Q: Hash + Eq + ?Sized,
258    {
259        let hash = self.hasher.hash_one(key);
260        let guard = pin();
261        self.lookup(hash, key, &guard).is_some()
262    }
263
264    /// Inserts a key-value pair, returning the value it replaced.
265    ///
266    /// Linearizable at the one CAS that links the new node (a new key) or
267    /// marks the old node naming the new one (a present key); the answer is
268    /// exactly the value that CAS replaced.
269    pub fn insert(&self, key: K, value: V) -> Option<V> {
270        let hash = self.hasher.hash_one(&key);
271        let mut pending = Pending::Parts { key, value };
272        let mut backoff = Backoff::new();
273        loop {
274            let guard = pin();
275            let (table, found) = self.find(hash, pending.key(), &guard);
276            match found {
277                Found::Frozen => {
278                    drop(guard);
279                    self.wait_for_resize();
280                }
281                Found::Miss { tail } => {
282                    let node = pending.into_node(hash, core::ptr::null_mut());
283                    // Release: a reader that acquires the link sees the node's fields.
284                    match tail.compare_exchange(
285                        word(core::ptr::null_mut()),
286                        word(node),
287                        Ordering::Release,
288                        Ordering::Relaxed,
289                        &guard,
290                    ) {
291                        Ok(_) => {
292                            self.landed(table, guard);
293                            return None;
294                        }
295                        Err(_) => {
296                            pending = Pending::back(node);
297                            backoff.spin();
298                        }
299                    }
300                }
301                Found::Hit {
302                    prev,
303                    node: old,
304                    next,
305                } => {
306                    // Cloned before the CAS: a panicking clone leaves the map as it was.
307                    let previous = old.value.clone();
308                    let node = pending.into_node(hash, next);
309                    // The replace: the old node deleted, naming the new node, in one CAS.
310                    // AcqRel: releases the new node; acquires what the old word carried.
311                    match old.next.compare_exchange(
312                        word(next),
313                        word(with(node, MARK)),
314                        Ordering::AcqRel,
315                        Ordering::Relaxed,
316                        &guard,
317                    ) {
318                        Ok(_) => {
319                            self.unlink(prev, old, node, hash, &old.key, &guard);
320                            return Some(previous);
321                        }
322                        Err(_) => {
323                            pending = Pending::back(node);
324                            backoff.spin();
325                        }
326                    }
327                }
328            }
329        }
330    }
331
332    /// Insert a key-value pair only if the key does not exist.
333    /// Returns `None` if inserted, `Some(existing_value)` if the key already exists.
334    ///
335    /// Exact: `None` exactly when this call linked the key's node (its CAS is
336    /// the linearization point), `Some` with the value of the node it found
337    /// live otherwise; never both, and a resize in flight changes neither.
338    /// A call that finds the key present drops its own key and value, once.
339    pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
340        match self.claim(key, value, |_| ()) {
341            Claim::Inserted(()) => None,
342            Claim::Present(existing) => Some(existing),
343        }
344    }
345
346    /// Returns the value corresponding to the key, or inserts the given value if the key is not present.
347    ///
348    /// Linearizable and exact: of the callers racing for an absent key, the
349    /// one whose CAS links the key's node gets its own value back, and every
350    /// other gets the value it found live (the winner's, unless a later
351    /// write already replaced it). One clone of the value either way.
352    pub fn get_or_insert(&self, key: K, value: V) -> V {
353        match self.claim(key, value, V::clone) {
354            Claim::Inserted(own) | Claim::Present(own) => own,
355        }
356    }
357
358    /// The conditional insert both `insert_if_absent` and `get_or_insert`
359    /// are: link the key's node unless the key is present. `on_insert` reads
360    /// the value this call linked, under its guard.
361    fn claim<R>(&self, key: K, value: V, on_insert: impl FnOnce(&V) -> R) -> Claim<R, V> {
362        let hash = self.hasher.hash_one(&key);
363        let mut pending = Pending::Parts { key, value };
364        let mut backoff = Backoff::new();
365        loop {
366            let guard = pin();
367            let (table, found) = self.find(hash, pending.key(), &guard);
368            match found {
369                Found::Frozen => {
370                    drop(guard);
371                    self.wait_for_resize();
372                }
373                // Present: the node was not deleted when the walk read its `next`.
374                Found::Hit { node, .. } => return Claim::Present(node.value.clone()),
375                Found::Miss { tail } => {
376                    let node = pending.into_node(hash, core::ptr::null_mut());
377                    // Release: a reader that acquires the link sees the node's fields.
378                    match tail.compare_exchange(
379                        word(core::ptr::null_mut()),
380                        word(node),
381                        Ordering::Release,
382                        Ordering::Relaxed,
383                        &guard,
384                    ) {
385                        Ok(_) => {
386                            // SAFETY: this call allocated and linked it under `guard`, which
387                            // keeps it from being freed.
388                            let answer = on_insert(unsafe { &(*node).value });
389                            self.landed(table, guard);
390                            return Claim::Inserted(answer);
391                        }
392                        Err(_) => {
393                            pending = Pending::back(node);
394                            backoff.spin();
395                        }
396                    }
397                }
398            }
399        }
400    }
401
402    /// Remove a key-value pair, returning its value.
403    ///
404    /// Linearizable at the CAS that marks the key's node deleted; the answer
405    /// is that node's value, and a later `get` of the key (with no insert of
406    /// it in between) answers `None`.
407    pub fn remove<Q>(&self, key: &Q) -> Option<V>
408    where
409        K: Borrow<Q>,
410        Q: Hash + Eq + ?Sized,
411    {
412        let hash = self.hasher.hash_one(key);
413        let mut backoff = Backoff::new();
414        loop {
415            let guard = pin();
416            let (table, found) = self.find(hash, key, &guard);
417            match found {
418                Found::Frozen => {
419                    drop(guard);
420                    self.wait_for_resize();
421                }
422                Found::Miss { .. } => return None,
423                Found::Hit { prev, node, next } => {
424                    // Cloned before the CAS: a panicking clone leaves the map as it was.
425                    let value = node.value.clone();
426                    // The removal: the node's own word marked, keeping its successor.
427                    if node
428                        .next
429                        .compare_exchange(
430                            word(next),
431                            word(with(next, MARK)),
432                            Ordering::AcqRel,
433                            Ordering::Relaxed,
434                            &guard,
435                        )
436                        .is_err()
437                    {
438                        backoff.spin();
439                        continue;
440                    }
441                    let remaining = table.count().fetch_sub(1, Ordering::Relaxed) - 1;
442                    self.unlink(prev, node, next, hash, key, &guard);
443                    let capacity = table.capacity();
444                    // Integer load-factor check: count/cap < 1/4.
445                    if 4 * (remaining.max(0) as usize) < capacity && capacity > self.floor {
446                        drop(guard);
447                        self.try_resize(capacity / 2);
448                    }
449                    return Some(value);
450                }
451            }
452        }
453    }
454
455    /// Remove the key's entry, returning its value if the key was present.
456    ///
457    /// A key has at most one live node, so this removes what [`remove`](Self::remove)
458    /// removes; it keeps removing until a call answers `None` (the key is
459    /// absent at that call's linearization point), and it is kept for
460    /// parity with `HopscotchMap::force_remove`.
461    ///
462    /// Note: a concurrent `insert` of the same key can land after the final
463    /// scan, as with any removal under contention.
464    pub fn force_remove<Q>(&self, key: &Q) -> Option<V>
465    where
466        K: Borrow<Q>,
467        Q: Hash + Eq + ?Sized,
468    {
469        let mut newest = None;
470        loop {
471            match self.remove(key) {
472                Some(v) => {
473                    if newest.is_none() {
474                        newest = Some(v);
475                    }
476                }
477                None => return newest,
478            }
479        }
480    }
481
482    /// Insert all `(K, V)` pairs from `iter`. Takes `&self` (concurrent map).
483    pub fn extend<I: IntoIterator<Item = (K, V)>>(&self, iter: I) {
484        for (k, v) in iter {
485            self.insert(k, v);
486        }
487    }
488
489    /// A new key landed in `table`: count it there, and grow the table past three quarters.
490    #[inline]
491    fn landed(&self, table: TableRef<K, V>, guard: Guard) {
492        let count = table.count().fetch_add(1, Ordering::Relaxed) + 1;
493        let capacity = table.capacity();
494        // Integer load-factor check: count/cap > 3/4.
495        if 4 * (count.max(0) as usize) > 3 * capacity {
496            drop(guard);
497            self.try_resize(capacity * 2);
498        }
499    }
500
501    /// Unlink the deleted `node` from `prev`, which named it unmarked, in favour of `succ`. The
502    /// thread whose CAS unlinks a node retires it; when this CAS fails (another snip, or the
503    /// predecessor deleted too), the cleanup walk makes sure the node is unlinked before the
504    /// caller returns.
505    #[inline]
506    fn unlink<Q>(
507        &self,
508        prev: &Atomic<Node<K, V>>,
509        node: &Node<K, V>,
510        succ: *mut Node<K, V>,
511        hash: u64,
512        key: &Q,
513        guard: &Guard,
514    ) where
515        K: Borrow<Q>,
516        Q: Eq + ?Sized,
517    {
518        let raw = node as *const Node<K, V> as *mut Node<K, V>;
519        // AcqRel: as a snip (see `walk::find`).
520        if prev
521            .compare_exchange(
522                word(raw),
523                word(succ),
524                Ordering::AcqRel,
525                Ordering::Relaxed,
526                guard,
527            )
528            .is_ok()
529        {
530            // SAFETY: this CAS unlinked it, so no other thread retires it and no link names it
531            // again; a walker that loaded it holds a guard that keeps it. Node is #[repr(C)]
532            // with its RetiredNode at offset 0.
533            unsafe { retire(raw) };
534        } else {
535            self.cleanup(hash, key, guard);
536        }
537    }
538}
539
540// SAFETY: HashMap is Send if K, V, S are Send (moving ownership between threads).
541// HashMap is Sync if K, V, S are Send+Sync. The stronger bound on Sync is needed
542// because concurrent `get()` calls clone V through a `&V` reference across threads;
543// if V were Send but not Sync, sharing `&HashMap` could transmit a non-Sync V reference
544// to another thread via clone(), violating thread-safety.
545unsafe impl<K: Send, V: Send, S: Send> Send for HashMap<K, V, S> {}
546unsafe impl<K: Send + Sync, V: Send + Sync, S: Send + Sync> Sync for HashMap<K, V, S> {}
547
548impl<K: 'static, V: 'static, S> Drop for HashMap<K, V, S> {
549    fn drop(&mut self) {
550        // SAFETY: `drop(&mut self)` guarantees exclusive ownership - no concurrent
551        // readers can exist. Rust's type system enforces this: `Iter<'a, ...>` borrows
552        // `&'a HashMap`, so it cannot outlive the `HashMap`. The Table's destructor
553        // frees its chains.
554        let guard = pin();
555        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Relaxed, &guard).as_raw());
556        drop(guard);
557        // SAFETY: exclusive access; frees remaining chains + the allocation.
558        unsafe { table.free() };
559
560        // Flush nodes/tables previously retired by concurrent operations
561        kovan::flush();
562    }
563}
564
565#[cfg(test)]
566mod tests;