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}