Skip to main content

fuchsia_rcu_collections/
rcu_raw_hash_map.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![warn(unsafe_op_in_unsafe_fn)]
6
7use crate::rcu_array::RcuArray;
8use crate::rcu_intrusive_list::{
9    Link, RcuIntrusiveList, RcuIntrusiveListCursor, RcuListAdapter, rcu_list_adapter,
10};
11use crate::rcu_list::RcuList;
12use fuchsia_rcu::{RcuDroppable, RcuReadScope};
13use std::borrow::Borrow;
14use std::hash::{BuildHasher, Hash, Hasher};
15use std::sync::atomic::{AtomicUsize, Ordering};
16
17/// The initial capacity of the hash map.
18const INITIAL_CAPACITY: usize = 16;
19
20/// An entry in the hash table.
21#[derive(Debug, RcuDroppable)]
22struct Entry<K, V> {
23    /// The key for this entry.
24    key: K,
25
26    /// The value for this entry.
27    value: V,
28
29    /// The link to the next node in the collision chain for this bucket.
30    collision_chain: Link,
31
32    /// The link to the next node in the insertion chain for this bucket.
33    insertion_chain: Link,
34}
35
36impl<K, V> Entry<K, V> {
37    /// Create a new hash table entry.
38    fn new(key: K, value: V) -> Self {
39        Self {
40            key,
41            value,
42            collision_chain: Default::default(),
43            insertion_chain: Default::default(),
44        }
45    }
46}
47
48/// An RcuListAdapter for the collision chain.
49#[derive(Debug, RcuDroppable)]
50struct CollisionAdapter;
51
52impl<K, V> RcuListAdapter<Entry<K, V>> for CollisionAdapter {
53    rcu_list_adapter!(Entry<K, V>, collision_chain);
54}
55
56/// An RcuListAdapter for the insertion chain.
57#[derive(Debug, RcuDroppable)]
58struct InsertionAdapter;
59
60impl<K, V> RcuListAdapter<Entry<K, V>> for InsertionAdapter {
61    rcu_list_adapter!(Entry<K, V>, insertion_chain);
62}
63
64/// The result of inserting an entry into the map.
65pub enum InsertionResult<V> {
66    /// The entry was inserted.
67    ///
68    /// The number of entries in the map is returned.
69    Inserted(usize),
70
71    /// The entry was updated.
72    ///
73    /// The old value is returned.
74    Updated(V),
75}
76
77/// The bucket in the hash table.
78///
79/// Each bucket is a linked list to hold the collision chain.
80type Bucket<K, V> = RcuList<Entry<K, V>, CollisionAdapter>;
81
82/// A hash map that uses read-copy-update (RCU) to manage concurrent accesses.
83///
84/// By default, this map uses `rapidhash::RapidBuildHasher`, which provides high performance.
85/// However, if this map holds keys which may be attacker-controlled, consider using
86/// `std::collections::hash_map::RandomState` instead.
87#[derive(RcuDroppable)]
88pub struct RcuRawHashMap<K, V, S = rapidhash::RapidBuildHasher>
89where
90    K: Eq + Hash + Clone + RcuDroppable + Sync,
91    V: Clone + RcuDroppable + Sync,
92    S: BuildHasher + Send + Sync + 'static,
93{
94    /// The table of buckets.
95    table: RcuArray<Bucket<K, V>>,
96
97    /// The number of entries in the map.
98    num_entries: AtomicUsize,
99
100    /// The entries in this map in the order they were inserted.
101    insertion_chain: RcuIntrusiveList<Entry<K, V>, InsertionAdapter>,
102
103    /// The build hasher.
104    hash_builder: S,
105}
106
107impl<K, V> Default for RcuRawHashMap<K, V, rapidhash::RapidBuildHasher>
108where
109    K: Eq + Hash + Clone + RcuDroppable + Sync,
110    V: Clone + RcuDroppable + Sync,
111{
112    fn default() -> Self {
113        Self::with_capacity_and_hasher(0, rapidhash::RapidBuildHasher::default())
114    }
115}
116
117impl<K, V> RcuRawHashMap<K, V, rapidhash::RapidBuildHasher>
118where
119    K: Eq + Hash + Clone + RcuDroppable + Sync,
120    V: Clone + RcuDroppable + Sync,
121{
122    /// Creates a new hash map with the given capacity.
123    pub fn with_capacity(capacity: usize) -> Self {
124        Self::with_capacity_and_hasher(capacity, rapidhash::RapidBuildHasher::default())
125    }
126}
127
128impl<K, V, S> RcuRawHashMap<K, V, S>
129where
130    K: Eq + Hash + Clone + RcuDroppable + Sync,
131    V: Clone + RcuDroppable + Sync,
132    S: BuildHasher + Send + Sync + 'static,
133{
134    /// Creates a new hash map with the given capacity and hasher.
135    pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
136        let mut table = Vec::new();
137        table.resize_with((capacity + 1) / 2, Default::default);
138        Self {
139            table: RcuArray::from(table),
140            num_entries: AtomicUsize::new(0),
141            insertion_chain: Default::default(),
142            hash_builder,
143        }
144    }
145
146    /// Creates a new hash map with the given hasher.
147    pub fn with_hasher(hash_builder: S) -> Self {
148        Self::with_capacity_and_hasher(0, hash_builder)
149    }
150
151    /// Returns the hash of the key as a u64.
152    fn hash_key<Q>(&self, key: &Q) -> u64
153    where
154        Q: ?Sized + Hash,
155    {
156        let mut hasher = self.hash_builder.build_hasher();
157        key.hash(&mut hasher);
158        hasher.finish()
159    }
160
161    /// Returns the bucket for the given key in the given table.
162    fn get_bucket<'a, Q>(&self, table: &'a [Bucket<K, V>], key: &Q) -> &'a Bucket<K, V>
163    where
164        K: Borrow<Q>,
165        Q: ?Sized + Hash,
166    {
167        let hash = self.hash_key(key);
168        let index = hash as usize % table.len();
169        &table[index]
170    }
171
172    /// Returns a reference to the bucket for the given key.
173    fn read_bucket<'a, Q>(&self, scope: &'a RcuReadScope, key: &Q) -> Option<&'a Bucket<K, V>>
174    where
175        K: Borrow<Q>,
176        Q: ?Sized + Hash,
177    {
178        let table = self.table.as_slice(scope);
179        if table.is_empty() {
180            return None;
181        }
182        Some(self.get_bucket(table, key))
183    }
184
185    /// Returns a reference to the value corresponding to the key.
186    ///
187    /// Another thread running concurrently might see a different value for the object.
188    pub fn get<'a, Q>(&self, scope: &'a RcuReadScope, key: &Q) -> Option<&'a V>
189    where
190        K: Borrow<Q>,
191        Q: ?Sized + Hash + Eq,
192    {
193        let bucket = self.read_bucket(scope, key)?;
194        bucket.iter(scope).find(|entry| entry.key.borrow() == key).map(|entry| &entry.value)
195    }
196
197    /// Returns the number of entries in the map.
198    ///
199    /// The length can change concurrently with this call.
200    pub fn len(&self) -> usize {
201        self.num_entries.load(Ordering::Relaxed)
202    }
203
204    /// Inserts a key-value pair into the map.
205    ///
206    /// If the map did not have this key present, `None` is returned.
207    ///
208    /// If the map did have this key present, the value is updated, and the old
209    /// value is returned.
210    ///
211    /// Concurrent readers might not see the inserted value until the RCU state machine has made
212    /// sufficient progress to ensure that no concurrent readers are holding read guards.
213    ///
214    /// # Safety
215    ///
216    /// Requires external synchronization to exclude concurrent writers.
217    pub unsafe fn insert(&self, scope: &RcuReadScope, key: K, value: V) -> InsertionResult<V> {
218        let mut table = self.table.as_slice(scope);
219        if self.needs_to_grow(table) {
220            // SAFETY: Our caller is required to use external synchronization to exclude concurrent
221            // writers.
222            table = unsafe { self.grow(&scope, table) };
223        }
224        let bucket = self.get_bucket(table, &key);
225        let mut cursor = bucket.cursor(&scope);
226        while let Some(entry) = cursor.current() {
227            if entry.key == key {
228                let old_value = entry.value.clone();
229                // SAFETY: We have exclusive access to the bucket because we have exclusive access
230                // to the table.
231                unsafe {
232                    let removed_entry = cursor.remove();
233                    self.insertion_chain.remove(&scope, removed_entry);
234                    let entry = bucket.push_front(&scope, Entry::new(key, value));
235                    self.insertion_chain.push_back(&scope, entry);
236                };
237                return InsertionResult::Updated(old_value);
238            }
239            cursor.advance();
240        }
241
242        // SAFETY: We have exclusive access to the bucket because we have exclusive access to the
243        // table.
244        unsafe {
245            let entry = bucket.push_front(&scope, Entry::new(key, value));
246            self.insertion_chain.push_back(&scope, entry);
247        }
248        let count = self.num_entries.fetch_add(1, Ordering::Relaxed);
249        InsertionResult::Inserted(count + 1)
250    }
251
252    /// Removes a key from the map, returning the value at the key if the key
253    /// was previously in the map.
254    ///
255    /// Concurrent readers might see the removed value until the RCU state machine has made
256    /// sufficient progress to ensure that no concurrent readers are holding read guards.
257    ///
258    /// # Safety
259    ///
260    /// Requires external synchronization to exclude concurrent writers.
261    pub unsafe fn remove<Q>(&self, key: &Q) -> Option<V>
262    where
263        K: Borrow<Q>,
264        Q: ?Sized + Hash + Eq,
265    {
266        let scope = RcuReadScope::new();
267        let bucket = self.read_bucket(&scope, key)?;
268        let mut cursor = bucket.cursor(&scope);
269        while let Some(entry) = cursor.current() {
270            if entry.key.borrow() == key {
271                let old_value = entry.value.clone();
272                // SAFETY: We have exclusive access to the bucket because we have exclusive access
273                // to the table.
274                unsafe {
275                    let removed_entry = cursor.remove();
276                    self.insertion_chain.remove(&scope, removed_entry);
277                };
278                self.num_entries.fetch_sub(1, Ordering::Relaxed);
279                return Some(old_value);
280            }
281            cursor.advance();
282        }
283        None
284    }
285
286    /// Whether the given table needs to grow to reduce the number of collisions.
287    fn needs_to_grow(&self, table: &[Bucket<K, V>]) -> bool {
288        table.is_empty() || self.num_entries.load(Ordering::Relaxed) > table.len() * 2
289    }
290
291    /// Grows the table to reduce the number of collisions.
292    ///
293    /// Returns a reference to the new table. Callers should be sure to update the table reference
294    /// they are using to the returned value.
295    ///
296    /// # Safety
297    ///
298    /// Requires external synchronization to exclude concurrent writers.
299    #[must_use]
300    unsafe fn grow<'a>(
301        &self,
302        scope: &'a RcuReadScope,
303        old_table: &[Bucket<K, V>],
304    ) -> &'a [Bucket<K, V>] {
305        let new_size = if old_table.is_empty() { INITIAL_CAPACITY } else { old_table.len() * 2 };
306        let mut new_table = Vec::new();
307        let new_insertion_chain = RcuIntrusiveList::default();
308        new_table.resize_with(new_size, Default::default);
309
310        for entry in self.insertion_chain.iter(scope) {
311            let bucket = self.get_bucket(&new_table, &entry.key);
312            let key = entry.key.clone();
313            let value = entry.value.clone();
314            // SAFETY: We have exclusive access to new_table_vec because we just created it.
315            unsafe {
316                let entry = bucket.push_front(&scope, Entry::new(key, value));
317                new_insertion_chain.push_back(&scope, entry);
318            };
319        }
320
321        self.table.update(new_table);
322        // SAFETY: Our caller promises to exclude concurrent writers.
323        unsafe {
324            self.insertion_chain.update(&scope, new_insertion_chain);
325        }
326        self.table.as_slice(scope)
327    }
328
329    /// Returns a cursor that can be used to traverse and modify the map.
330    ///
331    /// The cursor iterates through the map in insertion order.
332    pub fn cursor<'a>(&'a self, scope: &'a RcuReadScope) -> RcuRawHashMapCursor<'a, K, V, S> {
333        RcuRawHashMapCursor { inner: self.insertion_chain.cursor(scope), map: self }
334    }
335
336    /// Returns an iterator over the keys in the map.
337    pub fn keys<'a>(&'a self, scope: &'a RcuReadScope) -> impl Iterator<Item = &'a K> {
338        self.insertion_chain.iter(scope).map(|entry| &entry.key)
339    }
340}
341
342// TODO(https://fxbug.dev/482462174): switch back to #[derive(Debug)]
343impl<K, V, S> std::fmt::Debug for RcuRawHashMap<K, V, S>
344where
345    K: Eq + Hash + Clone + RcuDroppable + Sync + std::fmt::Debug,
346    V: Clone + RcuDroppable + Sync + std::fmt::Debug,
347    S: std::hash::BuildHasher + Send + Sync + 'static,
348{
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        f.debug_struct("RcuRawHashMap")
351            .field("table", &self.table)
352            .field("num_entries", &self.num_entries)
353            .field("insertion_chain", &self.insertion_chain)
354            .field("hash_builder", &std::any::type_name::<S>())
355            .finish_non_exhaustive()
356    }
357}
358
359/// A cursor for traversing and modifying an `RcuRawHashMap`.
360///
361/// See `RcuRawHashMap::cursor` for more information.
362pub struct RcuRawHashMapCursor<'a, K, V, S = rapidhash::RapidBuildHasher>
363where
364    K: Eq + Hash + Clone + RcuDroppable + Sync,
365    V: Clone + RcuDroppable + Sync,
366    S: BuildHasher + Send + Sync + 'static,
367{
368    inner: RcuIntrusiveListCursor<'a, Entry<K, V>, InsertionAdapter>,
369    map: &'a RcuRawHashMap<K, V, S>,
370}
371
372impl<'a, K, V, S> RcuRawHashMapCursor<'a, K, V, S>
373where
374    K: Eq + Hash + Clone + RcuDroppable + Sync,
375    V: Clone + RcuDroppable + Sync,
376    S: BuildHasher + Send + Sync + 'static,
377{
378    /// Returns the element at the current cursor position.
379    pub fn current(&self) -> Option<(&'a K, &'a V)> {
380        self.inner.current().map(|entry| (&entry.key, &entry.value))
381    }
382
383    /// Advances the cursor to the next element in the list.
384    pub fn advance(&mut self) {
385        self.inner.advance()
386    }
387
388    /// Removes the element at the current cursor position.
389    ///
390    /// After calling `remove`, the cursor will be positioned at the next element in the list.
391    ///
392    /// Concurrent readers may continue to see this entry in the list until the RCU state machine
393    /// has made sufficient progress to ensure that no concurrent readers are holding read guards.
394    ///
395    /// # Safety
396    ///
397    /// Requires external synchronization to exclude concurrent writers.
398    pub unsafe fn remove(&mut self) -> Option<V> {
399        if let Some((key, _)) = self.current() {
400            self.advance();
401            // SAFETY: The caller promises to exclude concurrent writers.
402            unsafe { self.map.remove(key) }
403        } else {
404            None
405        }
406    }
407}
408
409#[cfg(test)]
410mod tests {
411    use super::*;
412    use fuchsia_rcu::rcu_run_callbacks;
413
414    #[test]
415    fn test_rcu_hash_map_custom_hasher() {
416        use std::collections::hash_map::DefaultHasher;
417        use std::hash::BuildHasherDefault;
418        let hasher = BuildHasherDefault::<DefaultHasher>::default();
419        let map = RcuRawHashMap::with_capacity_and_hasher(10, hasher);
420        let scope = RcuReadScope::new();
421        unsafe {
422            map.insert(&scope, 1, 10);
423        }
424        assert_eq!(map.get(&scope, &1), Some(&10));
425    }
426
427    #[test]
428    fn test_rcu_hash_map_insert_and_get() {
429        let map = RcuRawHashMap::default();
430        let scope = RcuReadScope::new();
431        unsafe {
432            map.insert(&scope, 1, 10);
433            map.insert(&scope, 2, 20);
434        }
435
436        assert_eq!(map.get(&scope, &1), Some(&10));
437        assert_eq!(map.get(&scope, &2), Some(&20));
438        assert_eq!(map.get(&scope, &3), None);
439
440        std::mem::drop(scope);
441        rcu_run_callbacks();
442    }
443
444    #[test]
445    fn test_rcu_hash_map_remove() {
446        let map = RcuRawHashMap::default();
447        let scope = RcuReadScope::new();
448        unsafe {
449            map.insert(&scope, 1, 10);
450            map.insert(&scope, 2, 20);
451        }
452
453        assert_eq!(map.get(&scope, &1), Some(&10));
454
455        unsafe {
456            assert_eq!(map.remove(&1), Some(10));
457        }
458
459        assert_eq!(map.get(&scope, &1), None);
460        assert_eq!(map.get(&scope, &2), Some(&20));
461
462        std::mem::drop(scope);
463        rcu_run_callbacks();
464    }
465
466    #[test]
467    fn test_rcu_hash_map_insert_update() {
468        let map = RcuRawHashMap::default();
469        let scope = RcuReadScope::new();
470        unsafe {
471            map.insert(&scope, 1, 10);
472        }
473
474        assert_eq!(map.get(&scope, &1), Some(&10));
475
476        let result = unsafe { map.insert(&scope, 1, 100) };
477        assert!(matches!(result, InsertionResult::Updated(10)));
478
479        assert_eq!(map.get(&scope, &1), Some(&100));
480
481        std::mem::drop(scope);
482        rcu_run_callbacks();
483    }
484
485    #[test]
486    fn test_rcu_hash_map_cursor() {
487        let map = RcuRawHashMap::default();
488        let scope = RcuReadScope::new();
489        unsafe {
490            map.insert(&scope, 1, 10);
491            map.insert(&scope, 2, 20);
492            map.insert(&scope, 3, 30);
493        }
494
495        let mut cursor = map.cursor(&scope);
496
497        assert_eq!(cursor.current(), Some((&1, &10)));
498        cursor.advance();
499        assert_eq!(cursor.current(), Some((&2, &20)));
500
501        unsafe {
502            cursor.remove();
503        }
504
505        assert_eq!(cursor.current(), Some((&3, &30)));
506        assert_eq!(map.get(&scope, &2), None);
507
508        cursor.advance();
509        assert_eq!(cursor.current(), None);
510
511        std::mem::drop(scope);
512        rcu_run_callbacks();
513    }
514
515    #[test]
516    fn test_rcu_hash_map_grow_maintains_order() {
517        let map = RcuRawHashMap::default();
518        let scope = RcuReadScope::new();
519        let num_elements = INITIAL_CAPACITY * 3;
520        let mut expected_order = Vec::new();
521
522        for i in 0..num_elements {
523            unsafe {
524                map.insert(&scope, i, i * 10);
525            }
526            expected_order.push((i, i * 10));
527        }
528
529        let mut cursor = map.cursor(&scope);
530        let mut actual_order = Vec::new();
531
532        while let Some((key, value)) = cursor.current() {
533            actual_order.push((*key, *value));
534            cursor.advance();
535        }
536
537        assert_eq!(actual_order, expected_order);
538
539        std::mem::drop(scope);
540        rcu_run_callbacks();
541    }
542    #[test]
543    fn test_rcu_hash_map_grow_overwrites_maintain_order() {
544        let map = RcuRawHashMap::default();
545        let scope = RcuReadScope::new();
546        let num_elements = INITIAL_CAPACITY * 3;
547        let mut expected_order = Vec::new();
548
549        for i in 0..num_elements {
550            unsafe {
551                map.insert(&scope, i, i * 10);
552            }
553            expected_order.push((i, i * 10));
554        }
555
556        // Overwrite some existing entries and add new ones
557        unsafe {
558            map.insert(&scope, 5, 500);
559            map.insert(&scope, INITIAL_CAPACITY * 3, (INITIAL_CAPACITY * 3) * 10); // New entry
560        }
561        expected_order.retain(|(k, _)| *k != 5);
562        expected_order.push((5, 500));
563        expected_order.push((INITIAL_CAPACITY * 3, (INITIAL_CAPACITY * 3) * 10));
564
565        let mut cursor = map.cursor(&scope);
566        let mut actual_order = Vec::new();
567
568        while let Some((key, value)) = cursor.current() {
569            actual_order.push((*key, *value));
570            cursor.advance();
571        }
572
573        assert_eq!(actual_order, expected_order);
574
575        std::mem::drop(scope);
576        rcu_run_callbacks();
577    }
578
579    #[test]
580    fn test_rcu_hash_map_grow() {
581        let map = RcuRawHashMap::default();
582        let scope = RcuReadScope::new();
583        for i in 0..(INITIAL_CAPACITY * 3) {
584            unsafe {
585                map.insert(&scope, i, i * 10);
586            }
587        }
588
589        for i in 0..(INITIAL_CAPACITY * 3) {
590            assert_eq!(map.get(&scope, &i), Some(&(i * 10)));
591        }
592
593        std::mem::drop(scope);
594        rcu_run_callbacks();
595    }
596
597    #[test]
598    fn test_rcu_hash_map_capacity_zero() {
599        let map = RcuRawHashMap::with_capacity(0);
600        let scope = RcuReadScope::new();
601
602        assert_eq!(map.get(&scope, &1), None);
603
604        unsafe {
605            map.insert(&scope, 1, 10);
606        }
607        assert_eq!(map.get(&scope, &1), Some(&10));
608
609        unsafe {
610            assert_eq!(map.remove(&1), Some(10));
611        }
612        assert_eq!(map.get(&scope, &1), None);
613
614        std::mem::drop(scope);
615        rcu_run_callbacks();
616    }
617}