diff --git a/src/node.rs b/src/node.rs index 5fbb8082..aac4f9f5 100644 --- a/src/node.rs +++ b/src/node.rs @@ -146,3 +146,115 @@ pub(crate) struct Node { pub(crate) next: Atomic>, pub(crate) lock: Mutex<()>, } + +#[cfg(test)] +mod tests { + use super::*; + use crossbeam_epoch::Owned; + + fn new_node(hash: u64, key: usize, value: usize) -> Node { + Node { + hash, + key, + value: Atomic::new(value), + next: Atomic::null(), + lock: Mutex::new(()), + } + } + + fn drop_entry(entry: BinEntry) { + // currently bins don't handle dropping their + // own values the Table is responsible. This + // makes use of the tables implementation for + // convenience in the unit test + let mut table = Table::::new(1); + table.store_bin(0, Owned::new(entry)); + table.drop_bins(); + } + + #[test] + fn find_node_no_match() { + let guard = &crossbeam_epoch::pin(); + let node2 = new_node(4, 5, 6); + let entry2 = BinEntry::Node(node2); + let node1 = new_node(1, 2, 3); + node1.next.store(Owned::new(entry2), Ordering::SeqCst); + let entry1 = BinEntry::Node(node1); + assert!(entry1.find(1, &0, guard).is_null()); + drop_entry(entry1); + } + + #[test] + fn find_node_single_match() { + let guard = &crossbeam_epoch::pin(); + let entry = BinEntry::Node(new_node(1, 2, 3)); + assert_eq!( + unsafe { entry.find(1, &2, guard).deref() } + .as_node() + .unwrap() + .key, + 2 + ); + drop_entry(entry); + } + + #[test] + fn find_node_multi_match() { + let guard = &crossbeam_epoch::pin(); + let node2 = new_node(4, 5, 6); + let entry2 = BinEntry::Node(node2); + let node1 = new_node(1, 2, 3); + node1.next.store(Owned::new(entry2), Ordering::SeqCst); + let entry1 = BinEntry::Node(node1); + assert_eq!( + unsafe { entry1.find(4, &5, guard).deref() } + .as_node() + .unwrap() + .key, + 5 + ); + drop_entry(entry1); + } + + #[test] + fn find_moved_empty_bins_no_match() { + let guard = &crossbeam_epoch::pin(); + let table = &Table::::new(1); + let entry = BinEntry::::Moved(table as *const _); + assert!(entry.find(1, &2, guard).is_null()); + } + + #[test] + fn find_moved_no_bins_no_match() { + let guard = &crossbeam_epoch::pin(); + let table = &Table::::new(0); + let entry = BinEntry::::Moved(table as *const _); + assert!(entry.find(1, &2, guard).is_null()); + } + + #[test] + fn find_moved_null_bin_no_match() { + let guard = &crossbeam_epoch::pin(); + let table = &mut Table::::new(2); + table.store_bin(1, Owned::new(BinEntry::Node(new_node(1, 2, 3)))); + let entry = BinEntry::::Moved(table as *const _); + assert!(entry.find(0, &1, guard).is_null()); + table.drop_bins(); + } + + #[test] + fn find_moved_match() { + let guard = &crossbeam_epoch::pin(); + let table = &mut Table::::new(1); + table.store_bin(0, Owned::new(BinEntry::Node(new_node(1, 2, 3)))); + let entry = BinEntry::::Moved(table as *const _); + assert_eq!( + unsafe { entry.find(1, &2, guard).deref() } + .as_node() + .unwrap() + .key, + 2 + ); + table.drop_bins(); + } +} diff --git a/tests/basic.rs b/tests/basic.rs index e2e01f5a..5b6312a7 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -314,6 +314,51 @@ fn concurrent_compute_if_present() { } } +#[test] +fn concurrent_resize_and_get() { + let map = Arc::new(HashMap::::new()); + { + let guard = epoch::pin(); + for i in 0..1024 { + map.insert(i, i, &guard); + } + } + + let map1 = map.clone(); + // t1 is using reserve to trigger a bunch of resizes + let t1 = std::thread::spawn(move || { + let guard = epoch::pin(); + // there should be 2 ** 10 capacity already, so trigger additional resizes + for power in 11..16 { + map1.reserve(1 << power, &guard); + } + }); + let map2 = map.clone(); + // t2 is retrieving existing keys a lot, attempting to encounter a BinEntry::Moved + let t2 = std::thread::spawn(move || { + let guard = epoch::pin(); + for _ in 0..32 { + for i in 0..1024 { + let v = map2.get(&i, &guard).unwrap(); + assert_eq!(v, &i); + } + } + }); + + t1.join().unwrap(); + t2.join().unwrap(); + + // make sure all the entries still exist after all the resizes + { + let guard = epoch::pin(); + + for i in 0..1024 { + let v = map.get(&i, &guard).unwrap(); + assert_eq!(v, &i); + } + } +} + #[test] fn current_kv_dropped() { let dropped1 = Arc::new(0);