lockfree_cuckoohash/
lib.rs

1//! This crate implements a lockfree cuckoo hashmap.
2#![deny(
3    // The following are allowed by default lints according to
4    // https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html
5    anonymous_parameters,
6    bare_trait_objects,
7    // box_pointers, // futures involve boxed pointers
8    elided_lifetimes_in_paths, // allow anonymous lifetime in generated code
9    missing_copy_implementations,
10    missing_debug_implementations,
11    // missing_docs, // TODO: add documents
12    single_use_lifetimes, // TODO: fix lifetime names only used once
13    trivial_casts, // TODO: remove trivial casts in code
14    trivial_numeric_casts,
15    // unreachable_pub, use clippy::redundant_pub_crate instead
16    // unsafe_code, unsafe codes are inevitable here
17    unstable_features,
18    unused_extern_crates,
19    unused_import_braces,
20    unused_qualifications,
21    // unused_results, // TODO: fix unused results
22    variant_size_differences,
23
24    // Treat warnings as errors
25    warnings,
26
27    clippy::all,
28    clippy::restriction,
29    clippy::pedantic,
30    clippy::nursery,
31    clippy::cargo
32)]
33#![allow(
34    // Some explicitly allowed Clippy lints, must have clear reason to allow
35    clippy::blanket_clippy_restriction_lints, // allow clippy::restriction
36    clippy::panic, // allow debug_assert, panic in production code
37    clippy::implicit_return, // actually omitting the return keyword is idiomatic Rust code
38)]
39
40/// `pointer` defines atomic pointers which will be used for lockfree operations.
41mod pointer;
42
43/// `map_inner` defines the inner implementation of the hashmap.
44mod map_inner;
45
46use pointer::{AtomicPtr, SharedPtr};
47use std::borrow::Borrow;
48use std::collections::hash_map::RandomState;
49use std::hash::Hash;
50use std::sync::atomic::Ordering;
51
52// Re-export `crossbeam_epoch::pin()` and `crossbeam_epoch::Guard`.
53pub use crossbeam_epoch::{pin, Guard};
54
55/// `LockFreeCuckooHash` is a lock-free hash table using cuckoo hashing scheme.
56/// This implementation is based on the approach discussed in the paper:
57///
58/// "Nguyen, N., & Tsigas, P. (2014). Lock-Free Cuckoo Hashing. 2014 IEEE 34th International
59/// Conference on Distributed Computing Systems, 627-636."
60///
61/// Cuckoo hashing is an open addressing solution for hash collisions. The basic idea of cuckoo
62/// hashing is to resolve collisions by using two or more hash functions instead of only one. In this
63/// implementation, we use two hash functions and two arrays (or tables).
64///
65/// The search operation only looks up two slots, i.e. table[0][hash0(key)] and table[1][hash1(key)].
66/// If these two slots do not contain the key, the hash table does not contain the key. So the search operation
67/// only takes a constant time in the worst case.
68///
69/// The insert operation must pay the price for the quick search. The insert operation can only put the key
70/// into one of the two slots. However, when both slots are already occupied by other entries, it will be
71/// necessary to move other keys to their second locations (or back to their first locations) to make room
72/// for the new key, which is called a `relocation`. If the moved key can't be relocated because the other
73/// slot of it is also occupied, another `relocation` is required and so on. If relocation is a very long chain
74/// or meets a infinite loop, the table should be resized or rehashed.
75///
76pub struct LockFreeCuckooHash<K, V>
77where
78    K: Eq + Hash,
79{
80    /// The inner map will be replaced after resize.
81    map: AtomicPtr<map_inner::MapInner<K, V>>,
82}
83
84impl<K, V> std::fmt::Debug for LockFreeCuckooHash<K, V>
85where
86    K: std::fmt::Debug + Eq + Hash,
87    V: std::fmt::Debug,
88{
89    #[inline]
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        let guard = pin();
92        self.load_inner(&guard).fmt(f)
93    }
94}
95
96impl<K, V> Default for LockFreeCuckooHash<K, V>
97where
98    K: Eq + Hash,
99{
100    #[inline]
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106impl<K, V> Drop for LockFreeCuckooHash<K, V>
107where
108    K: Eq + Hash,
109{
110    #[inline]
111    fn drop(&mut self) {
112        let guard = pin();
113        self.load_inner(&guard).drop_entries(&guard);
114        unsafe {
115            self.map.load(Ordering::SeqCst, &guard).into_box();
116        }
117    }
118}
119
120impl<'guard, K, V> LockFreeCuckooHash<K, V>
121where
122    K: 'guard + Eq + Hash,
123{
124    /// The default capacity of a new `LockFreeCuckooHash` when created by `LockFreeHashMap::new()`.
125    pub const DEFAULT_CAPACITY: usize = 16;
126
127    /// Create an empty `LockFreeCuckooHash` with default capacity.
128    #[must_use]
129    #[inline]
130    pub fn new() -> Self {
131        Self::with_capacity(Self::DEFAULT_CAPACITY)
132    }
133
134    /// Creates an empty `LockFreeCuckooHash` with the specified capacity.
135    #[must_use]
136    #[inline]
137    pub fn with_capacity(capacity: usize) -> Self {
138        Self {
139            map: AtomicPtr::new(map_inner::MapInner::with_capacity(
140                capacity,
141                [RandomState::new(), RandomState::new()],
142            )),
143        }
144    }
145
146    /// Returns the capacity of this hash table.
147    #[inline]
148    pub fn capacity(&self) -> usize {
149        let guard = pin();
150        self.load_inner(&guard).capacity()
151    }
152
153    /// Returns the number of used slots of this hash table.
154    #[inline]
155    pub fn size(&self) -> usize {
156        let guard = pin();
157        self.load_inner(&guard).size()
158    }
159
160    /// # Safety
161    ///
162    /// Clear the hashmap with the specified capacity.
163    /// The caller must make sure the hashmap is not during a resize.
164    #[inline]
165    pub unsafe fn clear(&self) {
166        let cap = self.capacity();
167        self.clear_with_capacity(cap);
168    }
169
170    /// # Safety
171    ///
172    /// Clear the hashmap with the specified capacity.
173    /// The caller must make sure the hashmap is not during a resize.
174    #[inline]
175    pub unsafe fn clear_with_capacity(&self, capacity: usize) {
176        let guard = pin();
177        let new_map = SharedPtr::from_box(Box::new(map_inner::MapInner::<K, V>::with_capacity(
178            capacity,
179            [RandomState::new(), RandomState::new()],
180        )));
181        loop {
182            let current_map = self.map.load(Ordering::SeqCst, &guard);
183            match self
184                .map
185                .compare_and_set(current_map, new_map, Ordering::SeqCst, &guard)
186            {
187                Ok(old_map) => {
188                    guard.defer_unchecked(move || {
189                        old_map.into_box();
190                    });
191                    break;
192                }
193                Err(_) => {
194                    continue;
195                }
196            }
197        }
198    }
199
200    /// Returns a reference to the value corresponding to the key.
201    ///
202    /// # Example:
203    ///
204    /// ```
205    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
206    /// let map = LockFreeCuckooHash::new();
207    /// map.insert(1, "a");
208    /// let guard = pin();
209    /// let v = map.get(&1, &guard);
210    /// assert_eq!(v, Some(&"a"));
211    /// ```
212    ///
213    #[inline]
214    pub fn get<Q: ?Sized>(&self, key: &Q, guard: &'guard Guard) -> Option<&'guard V>
215    where
216        K: Borrow<Q>,
217        Q: Hash + Eq,
218    {
219        self.load_inner(guard)
220            .search(key, guard)
221            .map(|pair| &pair.value)
222    }
223
224    /// Returns the key-value pair corresponding to the supplied key.
225    ///
226    /// # Example
227    ///
228    /// ```
229    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
230    /// let map = LockFreeCuckooHash::new();
231    /// map.insert(1, "a");
232    /// let guard = pin();
233    /// let v = map.get_key_value(&1, &guard);
234    /// assert_eq!(v, Some((&1, &"a")));
235    /// ```
236    ///
237    #[inline]
238    pub fn get_key_value<Q: ?Sized>(
239        &self,
240        key: &Q,
241        guard: &'guard Guard,
242    ) -> Option<(&'guard K, &'guard V)>
243    where
244        K: Borrow<Q>,
245        Q: Hash + Eq,
246    {
247        self.load_inner(guard)
248            .search(key, guard)
249            .map(|pair| (&pair.key, &pair.value))
250    }
251
252    /// Returns `true` if the map contains a value for the specified key.
253    ///
254    /// # Example
255    /// ```
256    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
257    /// let map = LockFreeCuckooHash::new();
258    /// map.insert(1, "a");
259    /// assert_eq!(map.contains_key(&1), true);
260    /// assert_eq!(map.contains_key(&2), false);
261    /// ```
262    ///
263    #[inline]
264    pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
265    where
266        K: Borrow<Q>,
267        Q: Hash + Eq,
268    {
269        let guard = pin();
270        self.get_key_value(key, &guard).is_some()
271    }
272
273    /// Insert a new key-value pair into the map.
274    /// If the map did not have this key present, `false` is returned.
275    /// If the map did have this key present, the value is updated, and `true` is returned.
276    /// If you want to get the replaced value, try `insert_with_guard` instead.
277    ///
278    /// # Example:
279    ///
280    /// ```
281    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
282    /// let map = LockFreeCuckooHash::new();
283    /// assert_eq!(map.insert(1, "a"), false);
284    /// assert_eq!(map.insert(2, "b"), false);
285    /// assert_eq!(map.insert(1, "aaa"), true);
286    /// ```
287    ///
288    #[inline]
289    pub fn insert(&self, key: K, value: V) -> bool {
290        let guard = pin();
291        self.insert_with_guard(key, value, &guard).is_some()
292    }
293
294    /// Insert a new key-value pair into the map.
295    /// If the map did not have this key present, `None` is returned.
296    /// If the map did have this key present, the value is updated, and the reference to the old value is returned.
297    /// Different from `insert(k, v)`, this method requires a user provided guard.
298    ///
299    /// # Example:
300    ///
301    /// ```
302    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
303    /// let map = LockFreeCuckooHash::new();
304    /// let guard = pin();
305    /// assert_eq!(map.insert_with_guard(1, "a", &guard), None);
306    /// assert_eq!(map.insert_with_guard(2, "b", &guard), None);
307    /// assert_eq!(map.insert_with_guard(1, "abc", &guard), Some(&"a"));
308    /// ```
309    ///
310    #[inline]
311    pub fn insert_with_guard(&self, key: K, value: V, guard: &'guard Guard) -> Option<&'guard V> {
312        let kvpair = SharedPtr::from_box(Box::new(map_inner::KVPair { key, value }));
313        loop {
314            match self.load_inner(guard).insert(
315                kvpair,
316                map_inner::InsertType::InsertOrReplace,
317                &self.map,
318                guard,
319            ) {
320                // If `insert` returns false it means the hashmap has been
321                // resized, we need to try to insert the kvpair again.
322                map_inner::WriteResult::Retry => continue,
323                map_inner::WriteResult::Succ(result) => return result.map(|pair| &pair.value),
324            }
325        }
326    }
327
328    /// Insert a new key-value pair into the map if the map does not contain the key.
329    /// If the map contains the key, return the old value.
330    /// If the map does not contain the key, insert the new key-value, and return the new value.
331    ///
332    /// Notice: When two concurrent `get_or_insert` methods are trying to insert the same key,
333    /// only one will succeed. But if a `get_or_insert` and a `insert` are called simultaneously with
334    /// the same key, the `get_or_insert` may still can insert the key-value pair even if `insert` has
335    /// already succeeded.
336    ///
337    /// An example for concurrent `get_or_insert`s:
338    ///
339    ///#         Thread A              |        Thread B
340    ///#  call `get_or_insert(key, A)` | call `get_or_insert(key, B)`
341    ///#                               |
342    ///#     return value = A          |      
343    ///#                               |     return value = A
344    ///
345    /// We can see, only one thread can insert the key-value, and the other will return the old value.
346    ///
347    /// An example for concurrent `get_or_insert` and `insert`:
348    ///
349    ///#         Thread A              |        Thread B
350    ///#  call `get_or_insert(key, A)` |   call `insert(key, B)`
351    ///#                               |      return value = B
352    ///#     return value = A          |       
353    ///#                               |   call `get(key, A)`
354    ///#                               |      return value = A
355    ///
356    /// We can see here, even if Thread B has already inserted (key, B) into the map, but Thread A can
357    /// still insert (key, A), which is not consistent with the semantics of `get_or_insert`.      
358    ///
359    /// # Example:
360    ///
361    /// ```
362    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
363    /// let map = LockFreeCuckooHash::new();
364    /// let guard = pin();
365    /// assert_eq!(map.get_or_insert(1, "a", &guard), &"a");
366    /// assert_eq!(map.get_or_insert(1, "b", &guard), &"a");
367    ///
368    /// ```
369    #[inline]
370    #[allow(clippy::unwrap_used)]
371    pub fn get_or_insert(&self, key: K, value: V, guard: &'guard Guard) -> &'guard V {
372        let kvpair = SharedPtr::from_box(Box::new(map_inner::KVPair { key, value }));
373        loop {
374            match self.load_inner(guard).insert(
375                kvpair,
376                map_inner::InsertType::GetOrInsert,
377                &self.map,
378                guard,
379            ) {
380                // If `insert` returns false it means the hashmap has been
381                // resized, we need to try to insert the kvpair again.
382                map_inner::WriteResult::Retry => continue,
383                map_inner::WriteResult::Succ(result) => {
384                    return &result
385                        .unwrap_or(unsafe { kvpair.as_raw().as_ref().unwrap() })
386                        .value;
387                }
388            }
389        }
390    }
391
392    /// Insert a new key-value pair into the map if the map does not contain the key.
393    ///
394    /// Notice: similar to `get_or_insert`, when two concurent `insert_if_not_exists` are
395    /// called, only one will succeed. But when concurrent `insert_if_not_exists` and `insert`
396    /// are called, `insert_if_not_exists` may still succeed even if `insert` has already inserted
397    /// the pair.
398    ///
399    /// # Example:
400    ///
401    /// ```
402    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
403    /// let map = LockFreeCuckooHash::new();
404    /// let guard = pin();
405    /// assert_eq!(map.insert_if_not_exists(1, "a"), true);
406    /// assert_eq!(map.get(&1, &guard), Some(&"a"));
407    /// assert_eq!(map.insert_if_not_exists(1, "b"), false);
408    /// assert_eq!(map.get(&1, &guard), Some(&"a"));
409    /// ```
410    #[inline]
411    pub fn insert_if_not_exists(&self, key: K, value: V) -> bool {
412        let guard = &pin();
413        let kvpair = SharedPtr::from_box(Box::new(map_inner::KVPair { key, value }));
414        loop {
415            match self.load_inner(guard).insert(
416                kvpair,
417                map_inner::InsertType::GetOrInsert,
418                &self.map,
419                guard,
420            ) {
421                // If `insert` returns false it means the hashmap has been
422                // resized, we need to try to insert the kvpair again.
423                map_inner::WriteResult::Retry => continue,
424                map_inner::WriteResult::Succ(result) => return result.is_none(),
425            }
426        }
427    }
428
429    /// Compare the cuurent value with `old_value`, update the value to `new_value` if
430    /// they are equal.
431    /// This method returns true if the update succeeds, otherwise returns false.
432    ///     
433    /// # Example:
434    ///
435    /// ```
436    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
437    /// let map = LockFreeCuckooHash::new();
438    /// let guard = pin();
439    /// assert_eq!(map.insert(1, "a"), false);
440    /// assert_eq!(map.compare_and_update(1, "c", &"b"), false);
441    /// assert_eq!(map.get(&1, &guard), Some(&"a"));
442    /// assert_eq!(map.compare_and_update(1, "c", &"a"), true);
443    /// assert_eq!(map.get(&1, &guard), Some(&"c"));
444    #[inline]
445    pub fn compare_and_update(&self, key: K, new_value: V, old_value: &V) -> bool
446    where
447        V: PartialEq,
448    {
449        let guard = &pin();
450        let kvpair = SharedPtr::from_box(Box::new(map_inner::KVPair {
451            key,
452            value: new_value,
453        }));
454        let update_func: fn(&V, &V) -> bool = V::eq;
455        loop {
456            match self.load_inner(guard).insert(
457                kvpair,
458                map_inner::InsertType::CompareAndUpdate(old_value, update_func),
459                &self.map,
460                guard,
461            ) {
462                // If `insert` returns false it means the hashmap has been
463                // resized, we need to try to insert the kvpair again.
464                map_inner::WriteResult::Retry => continue,
465                map_inner::WriteResult::Succ(res) => return res.is_some(),
466            }
467        }
468    }
469
470    /// Removes a key from the map, returning `true` if the key was previously in the map.
471    /// If you want to get the old value, try `map.remove_with_guard()` instead.
472    ///
473    /// # Example:
474    ///
475    /// ```
476    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
477    /// let map = LockFreeCuckooHash::new();
478    /// map.insert(1, "a");
479    /// assert_eq!(map.remove(&2), false);
480    /// assert_eq!(map.remove(&1), true);
481    /// assert_eq!(map.remove(&1), false);
482    /// ```
483    ///
484    #[inline]
485    pub fn remove<Q: ?Sized>(&self, key: &Q) -> bool
486    where
487        K: Borrow<Q>,
488        Q: Hash + Eq,
489    {
490        let guard = pin();
491        self.remove_with_guard(key, &guard).is_some()
492    }
493
494    /// Remove a key from the map.
495    /// Different from `remove(k)`, this method requires a user provided guard.
496    ///
497    /// # Example:
498    ///
499    /// ```
500    /// use lockfree_cuckoohash::{pin, LockFreeCuckooHash};
501    /// let map = LockFreeCuckooHash::new();
502    /// let guard = pin();
503    /// map.insert(1, "a");
504    /// assert_eq!(map.remove_with_guard(&2, &guard), None);
505    /// assert_eq!(map.remove_with_guard(&1, &guard), Some(&"a"));
506    /// assert_eq!(map.remove_with_guard(&1, &guard), None);
507    /// ```
508    ///
509    #[inline]
510    pub fn remove_with_guard<Q: ?Sized>(&self, key: &Q, guard: &'guard Guard) -> Option<&'guard V>
511    where
512        K: Borrow<Q>,
513        Q: Hash + Eq,
514    {
515        loop {
516            match self.load_inner(guard).remove(key, &self.map, guard) {
517                map_inner::WriteResult::Retry => continue,
518                map_inner::WriteResult::Succ(old) => return old.map(|pair| &pair.value),
519            }
520        }
521    }
522
523    /// `load_inner` atomically loads the `MapInner` of hashmap.
524    #[allow(clippy::unwrap_used)]
525    fn load_inner(&self, guard: &'guard Guard) -> &'guard map_inner::MapInner<K, V> {
526        let raw = self.map.load(Ordering::SeqCst, guard).as_raw();
527        // map is always not null, so the unsafe code is safe here.
528        unsafe { raw.as_ref().unwrap() }
529    }
530}
531
532#[cfg(test)]
533#[allow(clippy::all, clippy::restriction)]
534mod tests {
535    use super::{pin, LockFreeCuckooHash};
536    #[test]
537    fn test_insert() {
538        let hashtable = LockFreeCuckooHash::new();
539        let key: u32 = 1;
540        let value: u32 = 2;
541        hashtable.insert(key, value);
542        let guard = pin();
543        let ret = hashtable.get(&key, &guard);
544        assert!(ret.is_some());
545        assert_eq!(*(ret.unwrap()), value);
546    }
547
548    #[test]
549    fn test_replace() {
550        let hashtable = LockFreeCuckooHash::new();
551        let key: u32 = 1;
552        let value0: u32 = 2;
553        hashtable.insert(key, value0);
554        let guard = pin();
555        let ret0 = hashtable.get(&key, &guard);
556        assert!(ret0.is_some());
557        assert_eq!(*(ret0.unwrap()), value0);
558        assert_eq!(hashtable.size(), 1);
559        let value1: u32 = 3;
560        hashtable.insert(key, value1);
561        let ret1 = hashtable.get(&key, &guard);
562        assert!(ret1.is_some());
563        assert_eq!(*(ret1.unwrap()), value1);
564        assert_eq!(hashtable.size(), 1);
565    }
566
567    #[test]
568    fn test_get_or_insert() {
569        let hashtable = LockFreeCuckooHash::new();
570        let guard = &pin();
571        hashtable.insert(1, 1);
572        assert_eq!(hashtable.get_or_insert(1, 2, guard), &1);
573        assert_eq!(hashtable.get_or_insert(2, 3, guard), &3);
574        assert_eq!(hashtable.get_or_insert(2, 4, guard), &3);
575    }
576
577    #[test]
578    fn test_remove() {
579        let hashtable = LockFreeCuckooHash::new();
580        let key = 1;
581        let value = 2;
582        let fake_key = 3;
583        hashtable.insert(key, value);
584        assert_eq!(hashtable.size(), 1);
585        assert!(!hashtable.remove(&fake_key));
586        assert!(hashtable.remove(&key));
587        assert_eq!(hashtable.size(), 0);
588    }
589
590    #[test]
591    fn test_clear() {
592        let hashtable = LockFreeCuckooHash::new();
593        let key = 1;
594        let value = 2;
595        hashtable.insert(key, value);
596        let guard = pin();
597        let ret = hashtable.get(&key, &guard);
598        assert!(ret.is_some());
599        assert_eq!(*(ret.unwrap()), value);
600        unsafe { hashtable.clear() };
601        let ret = hashtable.get(&key, &guard);
602        assert!(ret.is_none());
603    }
604
605    #[test]
606    fn test_compare_and_update() {
607        let hashtable = LockFreeCuckooHash::new();
608        let guard = &pin();
609        hashtable.insert(1, 10);
610        assert_eq!(hashtable.compare_and_update(1, 20, &30), false);
611        assert_eq!(hashtable.get(&1, guard), Some(&10));
612        assert_eq!(hashtable.compare_and_update(1, 20, &10), true);
613        assert_eq!(hashtable.get(&1, guard), Some(&20));
614    }
615}