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),
10//!    mirroring `HopscotchMap`'s resize protocol.
11//! 3. **Optimized Node Layout**: fields ordered `hash -> key -> value -> next`
12//!    to optimize cache line usage during checks.
13//!
14//! # Architecture
15//! - **Table**: kovan-retired object holding the bucket array (atomic head
16//!   pointers). Readers snapshot the table under a guard and never block.
17//! - **Nodes**: singly linked chains, CAS-based lock-free insert/remove.
18//! - **Resize**: a single resizer (CAS on `resizing`) clones all entries
19//!   into a new table, swaps the table pointer, and retires the old table;
20//!   the old table's destructor frees its remaining chains exactly once at
21//!   reclamation time. Writers wait out an active resize and re-validate
22//!   after success so no update is lost to a concurrent migration.
23
24extern crate alloc;
25
26#[cfg(feature = "std")]
27extern crate std;
28
29use alloc::boxed::Box;
30use core::borrow::Borrow;
31use core::hash::{BuildHasher, Hash};
32use core::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
33use foldhash::fast::FixedState;
34use kovan::{Atomic, RetiredNode, Shared, pin, retire};
35
36/// Default number of buckets for `new()`. Matches the previous fixed-table
37/// sizing (zero collisions for ~100k items, fits in L3); maps created with
38/// `new()` keep exactly the old memory/performance profile and additionally
39/// grow past it on demand. Use [`HashMap::with_capacity`] for small elastic
40/// maps.
41const DEFAULT_CAPACITY: usize = 524_288;
42
43/// Minimum number of buckets; the map never shrinks below this.
44const MIN_CAPACITY: usize = 64;
45
46// Load-factor thresholds (implemented as integer comparisons on the hot
47// paths): grow when count/capacity > 3/4, shrink when count/capacity < 1/4
48// (never below the map's floor capacity). Same thresholds as HopscotchMap.
49
50/// A simple exponential backoff for reducing contention.
51struct Backoff {
52    step: u32,
53}
54
55impl Backoff {
56    #[inline(always)]
57    fn new() -> Self {
58        Self { step: 0 }
59    }
60
61    #[inline(always)]
62    fn spin(&mut self) {
63        for _ in 0..(1 << self.step.min(6)) {
64            core::hint::spin_loop();
65        }
66        if self.step <= 6 {
67            self.step += 1;
68        }
69    }
70}
71
72/// Node in the lock-free linked list.
73#[repr(C)]
74struct Node<K, V> {
75    retired: RetiredNode,
76    hash: u64,
77    key: K,
78    value: V,
79    next: Atomic<Node<K, V>>,
80}
81
82// SAFETY (kovan retirement rule): a retired Node's destructor may run on
83// any thread, and nodes (with K and V inside) move between threads — hence
84// `K: Send, V: Send` for Send. Unlike exclusive-transfer containers,
85// lookups DO produce `&K`/`&V` from a shared `&Node` (get() clones V
86// through &V under concurrent readers), so Sync additionally requires
87// `K: Sync, V: Sync` — the same bounds the map-level Sync impl below has
88// always required for sharing the map.
89unsafe impl<K: Send, V: Send> Send for Node<K, V> {}
90unsafe impl<K: Send + Sync, V: Send + Sync> Sync for Node<K, V> {}
91
92// ---------------------------------------------------------------------------
93// Harris-style logical deletion
94// ---------------------------------------------------------------------------
95//
96// Removing (or replacing) a node first TAGS the victim's `next` pointer
97// (low bit set) — the logical delete — and only then unlinks it from its
98// predecessor. The thread whose tag-CAS succeeded exclusively owns the node
99// and is the only one to `retire()` it. This closes two races a plain
100// unlink-CAS protocol has:
101//
102//  * insert-after-removed-tail: a tail insert CASes `tail.next: null -> new`;
103//    if the tail was concurrently unlinked and retired, the new node is
104//    spliced onto dead memory — the insert is lost and the node leaks.
105//    With tagging, the remover first turns the tail's `next` into
106//    tagged-null, so the insert's CAS (expecting untagged null) fails.
107//
108//  * adjacent removes: removing B (A->B->C) and C (B->C->D) concurrently
109//    can unlink C from the already-detached B while C is still reachable
110//    through A, retiring a reachable node (use-after-free for later
111//    readers). With tagging, C's remover owns C via the tag; walkers
112//    observe `B.next` tagged and never operate relative to deleted nodes.
113//
114// Invariants:
115//  * tags appear only on `Node.next` fields, never on bucket heads
116//    (snipping stores the untagged successor);
117//  * a node whose `next` is tagged has been retired by its tag owner —
118//    `clear()`, the migration sweep, and `Table::drop` must skip it;
119//  * every traversal untags before following a `next` pointer.
120
121#[inline(always)]
122fn tagged<K, V>(p: *mut Node<K, V>) -> *mut Node<K, V> {
123    (p as usize | 1) as *mut Node<K, V>
124}
125
126#[inline(always)]
127fn untag<K, V>(p: *mut Node<K, V>) -> *mut Node<K, V> {
128    (p as usize & !1) as *mut Node<K, V>
129}
130
131#[inline(always)]
132fn is_tagged<K, V>(p: *const Node<K, V>) -> bool {
133    (p as usize) & 1 != 0
134}
135
136// ---------------------------------------------------------------------------
137// Single-allocation table: [TableHeader][Atomic<Node>; capacity]
138// ---------------------------------------------------------------------------
139//
140// The header and the bucket array share one allocation, so the read path is
141// `table ptr -> header line (mask, hot in cache) -> bucket line` — the same
142// number of cold dereferences as a fixed embedded array. The table pointer
143// itself is swapped atomically on resize.
144//
145// Reclamation goes through a tiny boxed `TableProxy` (RetiredNode at offset
146// 0, as `retire()` requires): retiring the proxy defers until every guard
147// that could observe the old table has been released; the proxy's destructor
148// then frees the table's remaining chains and the allocation itself.
149
150#[repr(C)]
151struct TableHeader {
152    mask: usize,
153    capacity: usize,
154}
155
156/// Borrowed view of a table allocation.
157struct TableRef<K: 'static, V: 'static> {
158    header: *mut TableHeader,
159    _marker: core::marker::PhantomData<(K, V)>,
160}
161
162impl<K, V> Clone for TableRef<K, V> {
163    fn clone(&self) -> Self {
164        *self
165    }
166}
167impl<K, V> Copy for TableRef<K, V> {}
168
169impl<K: 'static, V: 'static> TableRef<K, V> {
170    #[inline(always)]
171    fn from_raw(header: *mut TableHeader) -> Self {
172        Self {
173            header,
174            _marker: core::marker::PhantomData,
175        }
176    }
177
178    fn layout(capacity: usize) -> (core::alloc::Layout, usize) {
179        let header = core::alloc::Layout::new::<TableHeader>();
180        let buckets = core::alloc::Layout::array::<Atomic<Node<K, V>>>(capacity)
181            .expect("bucket array layout overflow");
182        let (layout, offset) = header.extend(buckets).expect("table layout overflow");
183        (layout.pad_to_align(), offset)
184    }
185
186    /// Allocate a zero-initialized table (`Atomic` buckets zero == null).
187    fn alloc(capacity: usize) -> Self {
188        let capacity = capacity.next_power_of_two().max(MIN_CAPACITY);
189        let (layout, _) = Self::layout(capacity);
190        // SAFETY: layout is non-zero sized; zeroed AtomicUsize == null bucket.
191        let header = unsafe { alloc::alloc::alloc_zeroed(layout) as *mut TableHeader };
192        assert!(!header.is_null(), "table allocation failed");
193        unsafe {
194            (*header).mask = capacity - 1;
195            (*header).capacity = capacity;
196        }
197        Self::from_raw(header)
198    }
199
200    /// Free remaining chains (skipping tagged nodes — already retired by
201    /// their tag owners) and the allocation itself.
202    ///
203    /// Free only the table allocation, not the chains (caller already drained
204    /// the live nodes, e.g. `IntoIter`). Tagged nodes remain kovan-owned.
205    ///
206    /// # Safety
207    /// Exclusive access; the chains must already be drained.
208    unsafe fn free_array_only(self) {
209        let capacity = unsafe { (*self.header).capacity };
210        let (layout, _) = Self::layout(capacity);
211        unsafe { alloc::alloc::dealloc(self.header as *mut u8, layout) };
212    }
213
214    /// # Safety
215    /// Caller must have exclusive access (map drop, or proxy reclamation
216    /// after guard quiescence).
217    unsafe fn free(self) {
218        let capacity = unsafe { (*self.header).capacity };
219        let guard = pin();
220        for i in 0..capacity {
221            let mut current = self.bucket(i).load(Ordering::Relaxed, &guard).as_raw();
222            while !current.is_null() {
223                unsafe {
224                    let next = (*current).next.load(Ordering::Relaxed, &guard).as_raw();
225                    if !is_tagged(next) {
226                        drop(Box::from_raw(current));
227                    }
228                    current = untag(next);
229                }
230            }
231        }
232        drop(guard);
233        let (layout, _) = Self::layout(capacity);
234        unsafe { alloc::alloc::dealloc(self.header as *mut u8, layout) };
235    }
236
237    #[inline(always)]
238    fn as_raw(self) -> *mut TableHeader {
239        self.header
240    }
241
242    #[inline(always)]
243    fn capacity(self) -> usize {
244        unsafe { (*self.header).capacity }
245    }
246
247    #[inline(always)]
248    fn buckets(self) -> *const Atomic<Node<K, V>> {
249        let (_, offset) = Self::layout_offset();
250        unsafe { (self.header as *const u8).add(offset) as *const Atomic<Node<K, V>> }
251    }
252
253    /// Header/bucket offset is capacity-independent; compute it once.
254    #[inline(always)]
255    fn layout_offset() -> ((), usize) {
256        let header = core::alloc::Layout::new::<TableHeader>();
257        let one = core::alloc::Layout::new::<Atomic<Node<K, V>>>();
258        let (_, offset) = header.extend(one).expect("layout");
259        ((), offset)
260    }
261
262    #[inline(always)]
263    fn bucket_index(self, hash: u64) -> usize {
264        (hash as usize) & unsafe { (*self.header).mask }
265    }
266
267    #[inline(always)]
268    fn bucket(self, idx: usize) -> &'static Atomic<Node<K, V>> {
269        // SAFETY: idx is masked or bounded by capacity; the 'static is a
270        // lie scoped by the caller's guard (same discipline as Shared).
271        unsafe { &*self.buckets().add(idx) }
272    }
273}
274
275/// Reclamation proxy for a table allocation (RetiredNode at offset 0).
276#[repr(C)]
277struct TableProxy<K: 'static, V: 'static> {
278    retired: RetiredNode,
279    table: TableRef<K, V>,
280}
281
282// SAFETY (kovan retirement rule): the proxy's destructor (running on any
283// thread) frees the table's nodes, hence K, V: Send.
284unsafe impl<K: Send, V: Send> Send for TableProxy<K, V> {}
285unsafe impl<K: Send + Sync, V: Send + Sync> Sync for TableProxy<K, V> {}
286
287impl<K, V> Drop for TableProxy<K, V> {
288    fn drop(&mut self) {
289        // SAFETY: kovan reclaimed the proxy only after every guard that
290        // could observe the old table has been released.
291        unsafe { self.table.free() };
292    }
293}
294
295/// High-Performance Lock-Free Map with automatic grow/shrink.
296pub struct HashMap<K: 'static, V: 'static, S = FixedState> {
297    table: Atomic<TableHeader>,
298    /// Approximate live-entry count driving the resize thresholds.
299    /// Signed: a transient negative under racing removes is harmless and
300    /// avoids a CAS loop (fetch_update) on the hot remove path.
301    count: AtomicIsize,
302    /// Single-resizer latch; writers wait while a resize is in flight.
303    resizing: AtomicBool,
304    /// Shrink floor: the initial capacity. The map never shrinks below the
305    /// size it was created with, preserving the caller's sizing intent (and
306    /// the historical fixed-table behavior for `new()`).
307    floor: usize,
308    hasher: S,
309    _marker: core::marker::PhantomData<(K, V)>,
310}
311
312#[cfg(feature = "std")]
313impl<K, V> HashMap<K, V, FixedState>
314where
315    K: Hash + Eq + Clone + 'static,
316    V: Clone + 'static,
317{
318    /// Creates a new empty hash map with FoldHash (FixedState).
319    pub fn new() -> Self {
320        Self::with_hasher(FixedState::default())
321    }
322
323    /// Creates a new empty hash map with at least `capacity` buckets.
324    pub fn with_capacity(capacity: usize) -> Self {
325        Self::with_capacity_and_hasher(capacity, FixedState::default())
326    }
327}
328
329impl<K, V, S> HashMap<K, V, S>
330where
331    K: Hash + Eq + Clone + 'static,
332    V: Clone + 'static,
333    S: BuildHasher,
334{
335    /// Creates a new hash map with custom hasher.
336    pub fn with_hasher(hasher: S) -> Self {
337        Self::with_capacity_and_hasher(DEFAULT_CAPACITY, hasher)
338    }
339
340    /// Creates a new hash map with at least `capacity` buckets and a custom hasher.
341    ///
342    /// The map grows when its load factor exceeds 0.75 and shrinks when it
343    /// falls below 0.25 — but never below `capacity`.
344    pub fn with_capacity_and_hasher(capacity: usize, hasher: S) -> Self {
345        let table = TableRef::<K, V>::alloc(capacity);
346        let floor = table.capacity();
347        Self {
348            table: Atomic::new(table.as_raw()),
349            count: AtomicIsize::new(0),
350            resizing: AtomicBool::new(false),
351            floor,
352            hasher,
353            _marker: core::marker::PhantomData,
354        }
355    }
356
357    /// Returns the current number of buckets.
358    pub fn capacity(&self) -> usize {
359        let guard = pin();
360        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
361        table.capacity()
362    }
363
364    /// Spin until any in-flight resize completes.
365    #[inline]
366    fn wait_for_resize(&self) {
367        while self.resizing.load(Ordering::Acquire) {
368            core::hint::spin_loop();
369        }
370    }
371
372    /// Optimized get operation. Never blocks — reads the current table
373    /// snapshot under a guard, even while a resize is in flight.
374    pub fn get<Q>(&self, key: &Q) -> Option<V>
375    where
376        K: Borrow<Q>,
377        Q: Hash + Eq + ?Sized,
378    {
379        let hash = self.hasher.hash_one(key);
380        let guard = pin();
381        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
382        let bucket = table.bucket(table.bucket_index(hash));
383
384        let mut current = bucket.load(Ordering::Acquire, &guard).as_raw();
385        while !current.is_null() {
386            unsafe {
387                let node = &*current;
388                // Check hash first (integer compare is fast). Matching a
389                // logically-deleted node is linearizable (the read happened
390                // before the delete), so no tag check on the match path.
391                if node.hash == hash && node.key.borrow() == key {
392                    return Some(node.value.clone());
393                }
394                // Untag: the pointer may carry the deletion tag.
395                current = untag(node.next.load(Ordering::Acquire, &guard).as_raw());
396            }
397        }
398        None
399    }
400
401    /// Checks if the key exists.
402    pub fn contains_key<Q>(&self, key: &Q) -> bool
403    where
404        K: Borrow<Q>,
405        Q: Hash + Eq + ?Sized,
406    {
407        self.get(key).is_some()
408    }
409
410    /// Insert a key-value pair.
411    pub fn insert(&self, key: K, value: V) -> Option<V> {
412        let hash = self.hasher.hash_one(&key);
413        let mut backoff = Backoff::new();
414        // Count a new key exactly once across re-validation retries
415        // (mirrors HopscotchMap: prevents both under-count, which causes
416        // cascading resizes, and double-count).
417        let mut counted = false;
418        // The first successful op's previous value is the linearized result;
419        // re-validation retries may replace a migrated clone of it.
420        let mut result: Option<Option<V>> = None;
421
422        'outer: loop {
423            self.wait_for_resize();
424
425            let guard = pin();
426            let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
427            let table = TableRef::<K, V>::from_raw(table_raw);
428            if self.resizing.load(Ordering::Acquire) {
429                continue;
430            }
431
432            let bucket = table.bucket(table.bucket_index(hash));
433
434            // 1. Search for existing key to update (snip-walk: physically
435            //    unlink logically-deleted nodes as we pass them).
436            let mut prev_link = bucket;
437            let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
438
439            while !current.is_null() {
440                unsafe {
441                    let node = &*current;
442                    let next = node.next.load(Ordering::Acquire, &guard).as_raw();
443
444                    if is_tagged(next) {
445                        // Logically deleted: snip it out (its tag owner has
446                        // already retired it). On contention restart the scan.
447                        if prev_link
448                            .compare_exchange(
449                                Shared::from_raw(current),
450                                Shared::from_raw(untag(next)),
451                                Ordering::AcqRel,
452                                Ordering::Relaxed,
453                                &guard,
454                            )
455                            .is_err()
456                        {
457                            backoff.spin();
458                            continue 'outer;
459                        }
460                        current = untag(next);
461                        continue;
462                    }
463
464                    if node.hash == hash && node.key == key {
465                        // Replace: logically delete the old node (tag-CAS
466                        // makes us its exclusive owner), then swing the
467                        // predecessor to the replacement in one step.
468                        let old_value = node.value.clone();
469                        if node
470                            .next
471                            .compare_exchange(
472                                Shared::from_raw(next),
473                                Shared::from_raw(tagged(next)),
474                                Ordering::AcqRel,
475                                Ordering::Relaxed,
476                                &guard,
477                            )
478                            .is_err()
479                        {
480                            // Someone else deleted/replaced it first.
481                            backoff.spin();
482                            continue 'outer;
483                        }
484                        // We own the old node now — we retire it, exactly once.
485                        let new_node = Box::into_raw(Box::new(Node {
486                            retired: RetiredNode::new(),
487                            hash,
488                            key: key.clone(),
489                            value: value.clone(),
490                            next: Atomic::new(next),
491                        }));
492                        let swapped = prev_link
493                            .compare_exchange(
494                                Shared::from_raw(current),
495                                Shared::from_raw(new_node),
496                                Ordering::AcqRel,
497                                Ordering::Relaxed,
498                                &guard,
499                            )
500                            .is_ok();
501                        // SAFETY: tag ownership; Node is #[repr(C)] with
502                        // RetiredNode at offset 0.
503                        retire(current);
504                        if result.is_none() {
505                            result = Some(Some(old_value));
506                        }
507                        if !swapped {
508                            // A helper snipped the old node before our swing;
509                            // the replacement is not installed — retry the
510                            // whole op (the removal already linearized).
511                            drop(Box::from_raw(new_node));
512                            backoff.spin();
513                            continue 'outer;
514                        }
515                        // Re-validate: if a resize started (or completed)
516                        // since we loaded the table, the migration may have
517                        // cloned the entry before our update — redo the op
518                        // on the new table so the update is not lost.
519                        if self.resizing.load(Ordering::SeqCst)
520                            || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
521                        {
522                            continue 'outer;
523                        }
524                        return result.unwrap();
525                    }
526
527                    prev_link = &node.next;
528                    current = next;
529                }
530            }
531
532            // 2. Key not found. Insert at TAIL (prev_link). The CAS expects
533            //    an untagged null, so it fails if the tail node was
534            //    concurrently logically deleted.
535            let new_node_ptr = Box::into_raw(Box::new(Node {
536                retired: RetiredNode::new(),
537                hash,
538                key: key.clone(),
539                value: value.clone(),
540                next: Atomic::null(),
541            }));
542
543            match prev_link.compare_exchange(
544                unsafe { Shared::from_raw(core::ptr::null_mut()) },
545                unsafe { Shared::from_raw(new_node_ptr) },
546                Ordering::Release,
547                Ordering::Relaxed,
548                &guard,
549            ) {
550                Ok(_) => {
551                    if !counted {
552                        counted = true;
553                        self.count.fetch_add(1, Ordering::Relaxed);
554                    }
555                    if result.is_none() {
556                        result = Some(None);
557                    }
558                    // Re-validate against a concurrent migration (see above).
559                    if self.resizing.load(Ordering::SeqCst)
560                        || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
561                    {
562                        continue 'outer;
563                    }
564
565                    // Grow check (only when we actually added an entry).
566                    let new_count = self.count.load(Ordering::Relaxed).max(0) as usize;
567                    let capacity = table.capacity();
568                    // Integer load-factor check: count/cap > 3/4.
569                    if 4 * new_count > 3 * capacity {
570                        drop(guard);
571                        self.try_resize(capacity * 2);
572                    }
573                    return result.unwrap();
574                }
575                Err(_) => {
576                    // Contention at the tail — retry the search/append loop.
577                    unsafe {
578                        drop(Box::from_raw(new_node_ptr));
579                    }
580                    backoff.spin();
581                    continue 'outer;
582                }
583            }
584        }
585    }
586
587    /// Insert a key-value pair only if the key does not exist.
588    /// Returns `None` if inserted, `Some(existing_value)` if the key already exists.
589    pub fn insert_if_absent(&self, key: K, value: V) -> Option<V> {
590        let hash = self.hasher.hash_one(&key);
591        let mut backoff = Backoff::new();
592        let mut counted = false;
593        let mut inserted = false;
594
595        'outer: loop {
596            self.wait_for_resize();
597
598            let guard = pin();
599            let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
600            let table = TableRef::<K, V>::from_raw(table_raw);
601            if self.resizing.load(Ordering::Acquire) {
602                continue;
603            }
604
605            let bucket = table.bucket(table.bucket_index(hash));
606
607            // 1. Search for existing key (snip-walk).
608            let mut prev_link = bucket;
609            let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
610
611            while !current.is_null() {
612                unsafe {
613                    let node = &*current;
614                    let next = node.next.load(Ordering::Acquire, &guard).as_raw();
615
616                    if is_tagged(next) {
617                        if prev_link
618                            .compare_exchange(
619                                Shared::from_raw(current),
620                                Shared::from_raw(untag(next)),
621                                Ordering::AcqRel,
622                                Ordering::Relaxed,
623                                &guard,
624                            )
625                            .is_err()
626                        {
627                            backoff.spin();
628                            continue 'outer;
629                        }
630                        current = untag(next);
631                        continue;
632                    }
633
634                    if node.hash == hash && node.key == key {
635                        // If WE inserted this entry on a previous
636                        // (pre-migration) attempt, the op already succeeded.
637                        if inserted {
638                            return None;
639                        }
640                        return Some(node.value.clone());
641                    }
642                    prev_link = &node.next;
643                    current = next;
644                }
645            }
646
647            // 2. Key not found (or our pre-migration insert was not carried
648            //    over) — insert at TAIL. Untagged-null expectation makes the
649            //    CAS fail if the tail was concurrently logically deleted.
650            let new_node_ptr = Box::into_raw(Box::new(Node {
651                retired: RetiredNode::new(),
652                hash,
653                key: key.clone(),
654                value: value.clone(),
655                next: Atomic::null(),
656            }));
657
658            match prev_link.compare_exchange(
659                unsafe { Shared::from_raw(core::ptr::null_mut()) },
660                unsafe { Shared::from_raw(new_node_ptr) },
661                Ordering::Release,
662                Ordering::Relaxed,
663                &guard,
664            ) {
665                Ok(_) => {
666                    inserted = true;
667                    if !counted {
668                        counted = true;
669                        self.count.fetch_add(1, Ordering::Relaxed);
670                    }
671                    // Re-validate against a concurrent migration.
672                    if self.resizing.load(Ordering::SeqCst)
673                        || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
674                    {
675                        continue 'outer;
676                    }
677
678                    let new_count = self.count.load(Ordering::Relaxed).max(0) as usize;
679                    let capacity = table.capacity();
680                    // Integer load-factor check: count/cap > 3/4.
681                    if 4 * new_count > 3 * capacity {
682                        drop(guard);
683                        self.try_resize(capacity * 2);
684                    }
685                    return None;
686                }
687                Err(actual_val) => {
688                    // Contention at the tail.
689                    unsafe {
690                        let appended_ptr = actual_val.as_raw();
691                        drop(Box::from_raw(new_node_ptr));
692                        if !is_tagged(appended_ptr) && !appended_ptr.is_null() {
693                            let appended = &*appended_ptr;
694                            if appended.hash == hash && appended.key == key {
695                                // Race lost, key exists now.
696                                if inserted {
697                                    return None;
698                                }
699                                return Some(appended.value.clone());
700                            }
701                        }
702                    }
703                    backoff.spin();
704                    continue 'outer;
705                }
706            }
707        }
708    }
709
710    /// Returns the value corresponding to the key, or inserts the given value if the key is not present.
711    ///
712    /// This is linearizable: concurrent callers for the same key are guaranteed to
713    /// agree on which value was inserted (exactly one thread's CAS succeeds at the
714    /// list tail, and all others see that node on retry).
715    pub fn get_or_insert(&self, key: K, value: V) -> V {
716        match self.insert_if_absent(key, value.clone()) {
717            Some(existing) => existing,
718            None => value,
719        }
720    }
721
722    /// Remove a key-value pair.
723    pub fn remove<Q>(&self, key: &Q) -> Option<V>
724    where
725        K: Borrow<Q>,
726        Q: Hash + Eq + ?Sized,
727    {
728        let hash = self.hasher.hash_one(key);
729        let mut backoff = Backoff::new();
730        // First successful removal's value is the linearized result;
731        // re-validation retries only evict migrated clones.
732        let mut result: Option<V> = None;
733
734        'outer: loop {
735            self.wait_for_resize();
736
737            let guard = pin();
738            let table_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
739            let table = TableRef::<K, V>::from_raw(table_raw);
740            if self.resizing.load(Ordering::Acquire) {
741                continue;
742            }
743
744            let bucket = table.bucket(table.bucket_index(hash));
745
746            let mut prev_link = bucket;
747            let mut current = prev_link.load(Ordering::Acquire, &guard).as_raw();
748
749            while !current.is_null() {
750                unsafe {
751                    let node = &*current;
752                    let next = node.next.load(Ordering::Acquire, &guard).as_raw();
753
754                    if is_tagged(next) {
755                        // Logically deleted by someone else: snip and move on.
756                        if prev_link
757                            .compare_exchange(
758                                Shared::from_raw(current),
759                                Shared::from_raw(untag(next)),
760                                Ordering::AcqRel,
761                                Ordering::Relaxed,
762                                &guard,
763                            )
764                            .is_err()
765                        {
766                            backoff.spin();
767                            continue 'outer;
768                        }
769                        current = untag(next);
770                        continue;
771                    }
772
773                    if node.hash == hash && node.key.borrow() == key {
774                        let old_value = node.value.clone();
775
776                        // Logical delete: tag the victim's next. The tag
777                        // owner — and only the tag owner — retires the node,
778                        // and the tag makes concurrent tail-inserts onto
779                        // this node fail.
780                        if node
781                            .next
782                            .compare_exchange(
783                                Shared::from_raw(next),
784                                Shared::from_raw(tagged(next)),
785                                Ordering::AcqRel,
786                                Ordering::Relaxed,
787                                &guard,
788                            )
789                            .is_err()
790                        {
791                            backoff.spin();
792                            continue 'outer;
793                        }
794
795                        // Physical unlink (best effort — if it fails, a
796                        // later walker snips it).
797                        let _ = prev_link.compare_exchange(
798                            Shared::from_raw(current),
799                            Shared::from_raw(next),
800                            Ordering::AcqRel,
801                            Ordering::Relaxed,
802                            &guard,
803                        );
804
805                        // SAFETY: tag ownership; Node is #[repr(C)] with
806                        // RetiredNode at offset 0.
807                        retire(current);
808                        if result.is_none() {
809                            result = Some(old_value);
810                        }
811
812                        // Single atomic decrement (signed counter — cannot
813                        // wrap; a transient negative just clamps to 0 below).
814                        let new_count =
815                            (self.count.fetch_sub(1, Ordering::Relaxed) - 1).max(0) as usize;
816                        // Integer load-factor check: count/cap < 1/4.
817                        let shrink_to = (4 * new_count < table.capacity()
818                            && table.capacity() > self.floor)
819                            .then_some(table.capacity() / 2);
820
821                        // Re-validate: a concurrent migration may have cloned
822                        // this entry into the new table before we deleted it
823                        // here — redo the removal on the current table so the
824                        // key does not resurrect.
825                        if self.resizing.load(Ordering::SeqCst)
826                            || self.table.load(Ordering::SeqCst, &guard).as_raw() != table_raw
827                        {
828                            continue 'outer;
829                        }
830
831                        if let Some(cap) = shrink_to {
832                            drop(guard);
833                            self.try_resize(cap);
834                        }
835                        return result;
836                    }
837
838                    prev_link = &node.next;
839                    current = next;
840                }
841            }
842
843            // Key not present in the current table.
844            return result;
845        }
846    }
847
848    /// Remove **all** nodes matching `key`, returning the most recent value
849    /// if the key was present.
850    ///
851    /// [`remove`](Self::remove) unlinks only the first matching entry.
852    /// Insert/remove races can transiently leave more than one entry for
853    /// the same key ("versions"); after a plain `remove()` an older version
854    /// would become visible again. This method keeps removing until a full
855    /// scan finds no match, so the key is guaranteed absent at the
856    /// linearization point of the final scan.
857    ///
858    /// Use `remove()` for single-version removal semantics and
859    /// `force_remove()` when the key must be fully evicted.
860    ///
861    /// Note: a concurrent `insert` of the same key can land after the final
862    /// scan, as with any removal under contention.
863    pub fn force_remove<Q>(&self, key: &Q) -> Option<V>
864    where
865        K: Borrow<Q>,
866        Q: Hash + Eq + ?Sized,
867    {
868        let mut newest = None;
869        loop {
870            match self.remove(key) {
871                Some(v) => {
872                    // The first removal unlinks the first match in scan
873                    // order — the live (most recent) version.
874                    if newest.is_none() {
875                        newest = Some(v);
876                    }
877                }
878                None => return newest,
879            }
880        }
881    }
882
883    /// Clear the map.
884    pub fn clear(&self) {
885        // Take the resize latch so the table cannot be swapped (and no
886        // writer is mid-migration) while we unlink the chains.
887        while self
888            .resizing
889            .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
890            .is_err()
891        {
892            core::hint::spin_loop();
893        }
894
895        let guard = pin();
896        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
897
898        for i in 0..table.capacity() {
899            let bucket = table.bucket(i);
900            loop {
901                let head = bucket.load(Ordering::Acquire, &guard);
902                if head.is_null() {
903                    break;
904                }
905
906                // Try to unlink the whole chain at once
907                match bucket.compare_exchange(
908                    head,
909                    unsafe { Shared::from_raw(core::ptr::null_mut()) },
910                    Ordering::Release,
911                    Ordering::Relaxed,
912                    &guard,
913                ) {
914                    Ok(_) => {
915                        // Retire the chain's live nodes. Tagged nodes were
916                        // already retired by their tag owners — skip them.
917                        unsafe {
918                            let mut current = head.as_raw();
919                            while !current.is_null() {
920                                let next = (*current).next.load(Ordering::Relaxed, &guard).as_raw();
921                                if !is_tagged(next) {
922                                    // SAFETY: allocated via Box::into_raw;
923                                    // Node is #[repr(C)], RetiredNode first.
924                                    retire(current);
925                                }
926                                current = untag(next);
927                            }
928                        }
929                        break;
930                    }
931                    Err(_) => {
932                        // Contention, retry
933                        continue;
934                    }
935                }
936            }
937        }
938
939        self.count.store(0, Ordering::Release);
940        self.resizing.store(false, Ordering::Release);
941    }
942
943    /// Returns true if the map is empty.
944    pub fn is_empty(&self) -> bool {
945        self.len() == 0
946    }
947
948    /// Returns the number of elements in the map.
949    ///
950    /// O(1): maintained by insert/remove. Approximate while concurrent
951    /// updates are in flight (exact in quiescence), like `HopscotchMap`.
952    pub fn len(&self) -> usize {
953        self.count.load(Ordering::Relaxed).max(0) as usize
954    }
955
956    /// Resize the table to `new_capacity` buckets (single resizer wins).
957    ///
958    /// Clones every entry into a new table, swaps the table pointer, then
959    /// retires the old table. The old table's destructor frees whatever
960    /// nodes remain in its chains at reclamation time — entries removed or
961    /// replaced in the meantime were unlinked and retired individually, so
962    /// nothing is freed twice and nothing leaks.
963    fn try_resize(&self, new_capacity: usize) {
964        if self
965            .resizing
966            .compare_exchange(false, true, Ordering::SeqCst, Ordering::Relaxed)
967            .is_err()
968        {
969            return;
970        }
971
972        let new_capacity = new_capacity
973            .next_power_of_two()
974            .max(MIN_CAPACITY)
975            .max(self.floor);
976        let guard = pin();
977        let old_raw = self.table.load(Ordering::Acquire, &guard).as_raw();
978        let old_table = TableRef::<K, V>::from_raw(old_raw);
979
980        if old_table.capacity() == new_capacity {
981            self.resizing.store(false, Ordering::Release);
982            return;
983        }
984
985        let new_table = TableRef::<K, V>::alloc(new_capacity);
986
987        // Migrate: clone every live entry (logically-deleted nodes — tagged
988        // next — are skipped). We are the only writer of the new table (it
989        // is unpublished), so plain stores are sufficient.
990        for i in 0..old_table.capacity() {
991            let bucket = old_table.bucket(i);
992            let mut current = bucket.load(Ordering::Acquire, &guard).as_raw();
993            while !current.is_null() {
994                let node = unsafe { &*current };
995                let next = node.next.load(Ordering::Acquire, &guard).as_raw();
996                if !is_tagged(next) {
997                    let dst = new_table.bucket(new_table.bucket_index(node.hash));
998                    let head = dst.load(Ordering::Relaxed, &guard);
999                    let clone = Box::into_raw(Box::new(Node {
1000                        retired: RetiredNode::new(),
1001                        hash: node.hash,
1002                        key: node.key.clone(),
1003                        value: node.value.clone(),
1004                        next: Atomic::new(head.as_raw()),
1005                    }));
1006                    dst.store(unsafe { Shared::from_raw(clone) }, Ordering::Relaxed);
1007                }
1008                current = untag(next);
1009            }
1010        }
1011
1012        match self.table.compare_exchange(
1013            unsafe { Shared::from_raw(old_raw) },
1014            unsafe { Shared::from_raw(new_table.as_raw()) },
1015            Ordering::Release,
1016            Ordering::Relaxed,
1017            &guard,
1018        ) {
1019            Ok(_) => {
1020                // Retire the old table through a proxy: reclamation (which
1021                // frees the remaining chains and the allocation) is deferred
1022                // until every guard that could observe it is gone.
1023                let proxy = Box::into_raw(Box::new(TableProxy {
1024                    retired: RetiredNode::new(),
1025                    table: old_table,
1026                }));
1027                // SAFETY: TableProxy is #[repr(C)] with RetiredNode at
1028                // offset 0, allocated via Box::into_raw.
1029                unsafe { retire(proxy) };
1030            }
1031            Err(_) => {
1032                // Table changed under us (cannot normally happen — we hold
1033                // the resize latch). Discard the unpublished new table.
1034                unsafe { new_table.free() };
1035            }
1036        }
1037
1038        self.resizing.store(false, Ordering::Release);
1039    }
1040
1041    /// Returns an iterator over the map entries.
1042    /// Yields (K, V) clones from a table snapshot taken at creation.
1043    pub fn iter(&self) -> Iter<'_, K, V, S> {
1044        let guard = pin();
1045        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Acquire, &guard).as_raw());
1046        Iter {
1047            _map: self,
1048            table,
1049            bucket_idx: 0,
1050            current: core::ptr::null(),
1051            guard,
1052        }
1053    }
1054
1055    /// Returns an iterator over the map keys.
1056    /// Yields K clones.
1057    pub fn keys(&self) -> Keys<'_, K, V, S> {
1058        Keys { iter: self.iter() }
1059    }
1060
1061    /// Returns an iterator over the map values (clones `V`).
1062    pub fn values(&self) -> Values<'_, K, V, S> {
1063        Values { iter: self.iter() }
1064    }
1065
1066    /// Insert all `(K, V)` pairs from `iter`. Takes `&self` (concurrent map).
1067    pub fn extend<I: IntoIterator<Item = (K, V)>>(&self, iter: I) {
1068        for (k, v) in iter {
1069            self.insert(k, v);
1070        }
1071    }
1072
1073    /// Get the underlying hasher itself.
1074    pub fn hasher(&self) -> &S {
1075        &self.hasher
1076    }
1077}
1078
1079/// Iterator over HashMap entries.
1080///
1081/// Field ordering matters for drop safety.
1082/// Rust drops struct fields in declaration order.
1083/// The `guard` must be dropped *after* `current`/`table` so that the epoch
1084/// pin covering the snapshot is not released before we're done with the raw
1085/// pointers.
1086pub struct Iter<'a, K: 'static, V: 'static, S> {
1087    _map: &'a HashMap<K, V, S>,
1088    table: TableRef<K, V>,
1089    bucket_idx: usize,
1090    current: *const Node<K, V>,
1091    guard: kovan::Guard,
1092}
1093
1094impl<'a, K, V, S> Iterator for Iter<'a, K, V, S>
1095where
1096    K: Clone,
1097    V: Clone,
1098{
1099    type Item = (K, V);
1100
1101    fn next(&mut self) -> Option<Self::Item> {
1102        loop {
1103            if !self.current.is_null() {
1104                unsafe {
1105                    let node = &*self.current;
1106                    let next = node.next.load(Ordering::Acquire, &self.guard).as_raw();
1107                    // Advance current (the pointer may carry a deletion tag).
1108                    self.current = untag(next);
1109                    if is_tagged(next) {
1110                        // Logically deleted — do not yield.
1111                        continue;
1112                    }
1113                    return Some((node.key.clone(), node.value.clone()));
1114                }
1115            }
1116
1117            // Move to next bucket
1118            let table = self.table;
1119            if self.bucket_idx >= table.capacity() {
1120                return None;
1121            }
1122
1123            let bucket = table.bucket(self.bucket_idx);
1124            self.bucket_idx += 1;
1125            self.current = bucket.load(Ordering::Acquire, &self.guard).as_raw();
1126        }
1127    }
1128}
1129
1130/// Iterator over HashMap keys.
1131pub struct Keys<'a, K: 'static, V: 'static, S> {
1132    iter: Iter<'a, K, V, S>,
1133}
1134
1135impl<'a, K, V, S> Iterator for Keys<'a, K, V, S>
1136where
1137    K: Clone,
1138    V: Clone,
1139{
1140    type Item = K;
1141
1142    fn next(&mut self) -> Option<Self::Item> {
1143        self.iter.next().map(|(k, _)| k)
1144    }
1145}
1146
1147impl<'a, K, V, S> IntoIterator for &'a HashMap<K, V, S>
1148where
1149    K: Hash + Eq + Clone + 'static,
1150    V: Clone + 'static,
1151    S: BuildHasher,
1152{
1153    type Item = (K, V);
1154    type IntoIter = Iter<'a, K, V, S>;
1155
1156    fn into_iter(self) -> Self::IntoIter {
1157        self.iter()
1158    }
1159}
1160
1161/// Iterator over HashMap values (clones `V`).
1162pub struct Values<'a, K: 'static, V: 'static, S> {
1163    iter: Iter<'a, K, V, S>,
1164}
1165
1166impl<'a, K, V, S> Iterator for Values<'a, K, V, S>
1167where
1168    K: Clone,
1169    V: Clone,
1170{
1171    type Item = V;
1172
1173    #[inline]
1174    fn next(&mut self) -> Option<V> {
1175        self.iter.next().map(|(_, v)| v)
1176    }
1177}
1178
1179/// Owned iterator yielding `(K, V)` by value — moves out of the nodes, no
1180/// clone. Consuming the map gives exclusive access, so no guard protection of
1181/// the yielded values is needed.
1182pub struct IntoIter<K: 'static, V: 'static> {
1183    table: TableRef<K, V>,
1184    bucket_idx: usize,
1185    current: *mut Node<K, V>,
1186    guard: kovan::Guard,
1187}
1188
1189impl<K, V> Iterator for IntoIter<K, V> {
1190    type Item = (K, V);
1191
1192    fn next(&mut self) -> Option<(K, V)> {
1193        loop {
1194            if !self.current.is_null() {
1195                let node = self.current;
1196                let next = unsafe { (*node).next.load(Ordering::Acquire, &self.guard).as_raw() };
1197                self.current = untag(next);
1198                if is_tagged(next) {
1199                    continue; // logically deleted, owned by kovan
1200                }
1201                // Move K and V out, then free the shell without running drop.
1202                let k = unsafe { core::ptr::read(&(*node).key) };
1203                let v = unsafe { core::ptr::read(&(*node).value) };
1204                unsafe {
1205                    alloc::alloc::dealloc(
1206                        node as *mut u8,
1207                        core::alloc::Layout::new::<Node<K, V>>(),
1208                    );
1209                }
1210                return Some((k, v));
1211            }
1212            if self.bucket_idx >= self.table.capacity() {
1213                return None;
1214            }
1215            let bucket = self.table.bucket(self.bucket_idx);
1216            self.bucket_idx += 1;
1217            self.current = bucket.load(Ordering::Acquire, &self.guard).as_raw();
1218        }
1219    }
1220}
1221
1222impl<K, V> Drop for IntoIter<K, V> {
1223    fn drop(&mut self) {
1224        while self.next().is_some() {} // drop remaining live K/V + free shells
1225        unsafe { self.table.free_array_only() };
1226    }
1227}
1228
1229impl<K, V, S> IntoIterator for HashMap<K, V, S>
1230where
1231    K: 'static,
1232    V: 'static,
1233{
1234    type Item = (K, V);
1235    type IntoIter = IntoIter<K, V>;
1236
1237    fn into_iter(self) -> IntoIter<K, V> {
1238        let mut me = core::mem::ManuallyDrop::new(self);
1239        let guard = pin();
1240        let table = TableRef::<K, V>::from_raw(me.table.load(Ordering::Relaxed, &guard).as_raw());
1241        // Suppress HashMap::drop (we own the table now); drop only the hasher —
1242        // the remaining fields are atomics / usize / ZST marker.
1243        unsafe { core::ptr::drop_in_place(&mut me.hasher) };
1244        IntoIter {
1245            table,
1246            bucket_idx: 0,
1247            current: core::ptr::null_mut(),
1248            guard,
1249        }
1250    }
1251}
1252
1253impl<K, V, S> core::iter::FromIterator<(K, V)> for HashMap<K, V, S>
1254where
1255    K: Hash + Eq + Clone + Send + 'static,
1256    V: Clone + Send + 'static,
1257    S: BuildHasher + Default,
1258{
1259    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
1260        let map = Self::with_capacity_and_hasher(MIN_CAPACITY, S::default());
1261        for (k, v) in iter {
1262            map.insert(k, v);
1263        }
1264        map
1265    }
1266}
1267
1268#[cfg(feature = "std")]
1269impl<K, V> Default for HashMap<K, V, FixedState>
1270where
1271    K: Hash + Eq + Clone + 'static,
1272    V: Clone + 'static,
1273{
1274    fn default() -> Self {
1275        Self::new()
1276    }
1277}
1278
1279// SAFETY: HashMap is Send if K, V, S are Send (moving ownership between threads).
1280// HashMap is Sync if K, V, S are Send+Sync. The stronger bound on Sync is needed
1281// because concurrent `get()` calls clone V through a `&V` reference across threads;
1282// if V were Send but not Sync, sharing `&HashMap` could transmit a non-Sync V reference
1283// to another thread via clone(), violating thread-safety.
1284unsafe impl<K: Send, V: Send, S: Send> Send for HashMap<K, V, S> {}
1285unsafe impl<K: Send + Sync, V: Send + Sync, S: Send + Sync> Sync for HashMap<K, V, S> {}
1286
1287impl<K: 'static, V: 'static, S> Drop for HashMap<K, V, S> {
1288    fn drop(&mut self) {
1289        // SAFETY: `drop(&mut self)` guarantees exclusive ownership — no concurrent
1290        // readers can exist.  Rust's type system enforces this: `Iter<'a, …>` borrows
1291        // `&'a HashMap`, so it cannot outlive the `HashMap`.  The Table's destructor
1292        // frees its chains.
1293        let guard = pin();
1294        let table = TableRef::<K, V>::from_raw(self.table.load(Ordering::Relaxed, &guard).as_raw());
1295        drop(guard);
1296        // SAFETY: exclusive access; frees remaining chains + the allocation.
1297        unsafe { table.free() };
1298
1299        // Flush nodes/tables previously retired by concurrent operations
1300        kovan::flush();
1301    }
1302}
1303
1304#[cfg(test)]
1305mod tests {
1306    use super::*;
1307
1308    #[test]
1309    fn test_insert_and_get() {
1310        let map = HashMap::new();
1311        assert_eq!(map.insert(1, 100), None);
1312        assert_eq!(map.get(&1), Some(100));
1313        assert_eq!(map.get(&2), None);
1314    }
1315
1316    #[test]
1317    fn test_insert_replace() {
1318        let map = HashMap::new();
1319        assert_eq!(map.insert(1, 100), None);
1320        assert_eq!(map.insert(1, 200), Some(100));
1321        assert_eq!(map.get(&1), Some(200));
1322    }
1323
1324    #[test]
1325    fn test_grow() {
1326        let map = HashMap::with_capacity(64);
1327        assert_eq!(map.capacity(), 64);
1328        for i in 0..1000u64 {
1329            map.insert(i, i * 2);
1330        }
1331        assert!(map.capacity() > 64, "map should have grown");
1332        for i in 0..1000u64 {
1333            assert_eq!(map.get(&i), Some(i * 2));
1334        }
1335        assert_eq!(map.len(), 1000);
1336    }
1337
1338    #[test]
1339    fn test_shrink() {
1340        let map = HashMap::with_capacity(64);
1341        for i in 0..1000u64 {
1342            map.insert(i, i);
1343        }
1344        let grown = map.capacity();
1345        assert!(grown > 64);
1346        for i in 0..1000u64 {
1347            map.remove(&i);
1348        }
1349        assert!(
1350            map.capacity() < grown,
1351            "map should have shrunk (capacity {} -> {})",
1352            grown,
1353            map.capacity()
1354        );
1355        assert!(map.capacity() >= 64, "never below the initial capacity");
1356        assert_eq!(map.len(), 0);
1357    }
1358
1359    #[test]
1360    fn test_no_shrink_below_floor() {
1361        let map = HashMap::with_capacity(4096);
1362        for i in 0..100u64 {
1363            map.insert(i, i);
1364        }
1365        for i in 0..100u64 {
1366            map.remove(&i);
1367        }
1368        assert_eq!(map.capacity(), 4096, "floor preserves sizing intent");
1369    }
1370
1371    #[test]
1372    fn test_concurrent_inserts() {
1373        use alloc::sync::Arc;
1374        extern crate std;
1375        use std::thread;
1376
1377        let map = Arc::new(HashMap::new());
1378        let mut handles = alloc::vec::Vec::new();
1379
1380        for thread_id in 0..4 {
1381            let map_clone = Arc::clone(&map);
1382            let handle = thread::spawn(move || {
1383                for i in 0..1000 {
1384                    let key = thread_id * 1000 + i;
1385                    map_clone.insert(key, key * 2);
1386                }
1387            });
1388            handles.push(handle);
1389        }
1390
1391        for handle in handles {
1392            handle.join().unwrap();
1393        }
1394
1395        for thread_id in 0..4 {
1396            for i in 0..1000 {
1397                let key = thread_id * 1000 + i;
1398                assert_eq!(map.get(&key), Some(key * 2));
1399            }
1400        }
1401    }
1402
1403    #[test]
1404    fn test_concurrent_grow() {
1405        use alloc::sync::Arc;
1406        extern crate std;
1407        use std::thread;
1408
1409        let map = Arc::new(HashMap::with_capacity(64));
1410        let mut handles = alloc::vec::Vec::new();
1411
1412        for thread_id in 0..8u64 {
1413            let map_clone = Arc::clone(&map);
1414            handles.push(thread::spawn(move || {
1415                for i in 0..2000u64 {
1416                    let key = thread_id * 10_000 + i;
1417                    map_clone.insert(key, key);
1418                }
1419            }));
1420        }
1421        for handle in handles {
1422            handle.join().unwrap();
1423        }
1424
1425        for thread_id in 0..8u64 {
1426            for i in 0..2000u64 {
1427                let key = thread_id * 10_000 + i;
1428                assert_eq!(map.get(&key), Some(key), "lost key {key} during growth");
1429            }
1430        }
1431        assert!(map.capacity() >= 16_000);
1432    }
1433}