Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test which attempts to encounter a concurrent resize and retrieval #39

Merged
merged 7 commits into from
Jan 31, 2020
112 changes: 112 additions & 0 deletions src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,115 @@ pub(crate) struct Node<K, V> {
pub(crate) next: Atomic<BinEntry<K, V>>,
pub(crate) lock: Mutex<()>,
}

#[cfg(test)]
mod tests {
use super::*;
use crossbeam_epoch::Owned;

fn new_node(hash: u64, key: usize, value: usize) -> Node<usize, usize> {
Node {
hash,
key,
value: Atomic::new(value),
next: Atomic::null(),
lock: Mutex::new(()),
}
}

fn drop_entry(entry: BinEntry<usize, usize>) {
// 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::<usize, usize>::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::<usize, usize>::new(1);
let entry = BinEntry::<usize, usize>::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::<usize, usize>::new(0);
let entry = BinEntry::<usize, usize>::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::<usize, usize>::new(2);
table.store_bin(1, Owned::new(BinEntry::Node(new_node(1, 2, 3))));
let entry = BinEntry::<usize, usize>::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::<usize, usize>::new(1);
table.store_bin(0, Owned::new(BinEntry::Node(new_node(1, 2, 3))));
let entry = BinEntry::<usize, usize>::Moved(table as *const _);
assert_eq!(
unsafe { entry.find(1, &2, guard).deref() }
.as_node()
.unwrap()
.key,
2
);
table.drop_bins();
}
}
45 changes: 45 additions & 0 deletions tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,51 @@ fn concurrent_compute_if_present() {
}
}

#[test]
fn concurrent_resize_and_get() {
let map = Arc::new(HashMap::<usize, usize>::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);
Expand Down