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