Remove prev entry tracking, refactor HashMapInit into proper builder

This commit is contained in:
David Freifeld
2025-06-24 13:34:22 -07:00
parent 93a45708ff
commit 24e6c68772
4 changed files with 200 additions and 188 deletions

View File

@@ -22,16 +22,16 @@ pub mod entry;
mod tests;
use core::{CoreHashMap, INVALID_POS};
use entry::{Entry, OccupiedEntry, PrevPos};
use entry::{Entry, OccupiedEntry};
#[derive(Debug)]
pub struct OutOfMemoryError();
pub struct HashMapInit<'a, K, V, S = rustc_hash::FxBuildHasher> {
// Hash table can be allocated in a fixed memory area, or in a resizeable ShmemHandle.
shmem_handle: Option<ShmemHandle>,
shared_ptr: *mut HashMapShared<'a, K, V>,
shared_size: usize,
hasher: S,
num_buckets: u32,
}
pub struct HashMapAccess<'a, K, V, S = rustc_hash::FxBuildHasher> {
@@ -43,8 +43,46 @@ pub struct HashMapAccess<'a, K, V, S = rustc_hash::FxBuildHasher> {
unsafe impl<'a, K: Sync, V: Sync, S> Sync for HashMapAccess<'a, K, V, S> {}
unsafe impl<'a, K: Send, V: Send, S> Send for HashMapAccess<'a, K, V, S> {}
impl<'a, K, V, S> HashMapInit<'a, K, V, S> {
impl<'a, K: Clone + Hash + Eq, V, S> HashMapInit<'a, K, V, S> {
pub fn with_hasher(self, hasher: S) -> HashMapInit<'a, K, V, S> {
Self { hasher, ..self }
}
pub fn estimate_size(num_buckets: u32) -> usize {
// add some margin to cover alignment etc.
CoreHashMap::<K, V>::estimate_size(num_buckets) + size_of::<HashMapShared<K, V>>() + 1000
}
pub fn attach_writer(self) -> HashMapAccess<'a, K, V, S> {
// carve out the HashMapShared struct from the area.
let mut ptr: *mut u8 = self.shared_ptr.cast();
let end_ptr: *mut u8 = unsafe { ptr.add(self.shared_size) };
ptr = unsafe { ptr.add(ptr.align_offset(align_of::<HashMapShared<K, V>>())) };
let shared_ptr: *mut HashMapShared<K, V> = ptr.cast();
ptr = unsafe { ptr.add(size_of::<HashMapShared<K, V>>()) };
// carve out the buckets
ptr = unsafe { ptr.byte_add(ptr.align_offset(align_of::<core::Bucket<K, V>>())) };
let buckets_ptr = ptr;
ptr = unsafe { ptr.add(size_of::<core::Bucket<K, V>>() * self.num_buckets as usize) };
// use remaining space for the dictionary
ptr = unsafe { ptr.byte_add(ptr.align_offset(align_of::<u32>())) };
assert!(ptr.addr() < end_ptr.addr());
let dictionary_ptr = ptr;
let dictionary_size = unsafe { end_ptr.byte_offset_from(ptr) / size_of::<u32>() as isize };
assert!(dictionary_size > 0);
let buckets =
unsafe { std::slice::from_raw_parts_mut(buckets_ptr.cast(), self.num_buckets as usize) };
let dictionary = unsafe {
std::slice::from_raw_parts_mut(dictionary_ptr.cast(), dictionary_size as usize)
};
let hashmap = CoreHashMap::new(buckets, dictionary);
unsafe {
std::ptr::write(shared_ptr, HashMapShared { inner: hashmap });
}
HashMapAccess {
shmem_handle: self.shmem_handle,
shared_ptr: self.shared_ptr,
@@ -77,88 +115,56 @@ impl<'a, K, V> HashMapInit<'a, K, V, rustc_hash::FxBuildHasher>
where
K: Clone + Hash + Eq
{
pub fn init_in_fixed_area(
num_buckets: u32,
pub fn with_fixed(
num_buckets: u32,
area: &'a mut [MaybeUninit<u8>],
) -> HashMapInit<'a, K, V> {
Self::init_in_fixed_area_with_hasher(num_buckets, area, rustc_hash::FxBuildHasher::default())
Self {
num_buckets,
shmem_handle: None,
shared_ptr: area.as_mut_ptr().cast(),
shared_size: area.len(),
hasher: rustc_hash::FxBuildHasher::default(),
}
}
/// Initialize a new hash map in the given shared memory area
pub fn init_in_shmem(num_buckets: u32, shmem: ShmemHandle) -> HashMapInit<'a, K, V> {
Self::init_in_shmem_with_hasher(num_buckets, shmem, rustc_hash::FxBuildHasher::default())
}
}
impl<'a, K, V, S: BuildHasher> HashMapInit<'a, K, V, S>
where
K: Clone + Hash + Eq
{
pub fn estimate_size(num_buckets: u32) -> usize {
// add some margin to cover alignment etc.
CoreHashMap::<K, V>::estimate_size(num_buckets) + size_of::<HashMapShared<K, V>>() + 1000
}
pub fn init_in_shmem_with_hasher(num_buckets: u32, mut shmem: ShmemHandle, hasher: S) -> HashMapInit<'a, K, V, S> {
let size = Self::estimate_size(num_buckets);
shmem
pub fn with_shmem(num_buckets: u32, shmem: ShmemHandle) -> HashMapInit<'a, K, V> {
let size = Self::estimate_size(num_buckets);
shmem
.set_size(size)
.expect("could not resize shared memory area");
let ptr = unsafe { shmem.data_ptr.as_mut() };
Self::init_common(num_buckets, Some(shmem), ptr, size, hasher)
Self {
num_buckets,
shared_ptr: shmem.data_ptr.as_ptr().cast(),
shmem_handle: Some(shmem),
shared_size: size,
hasher: rustc_hash::FxBuildHasher::default()
}
}
pub fn init_in_fixed_area_with_hasher(
num_buckets: u32,
area: &'a mut [MaybeUninit<u8>],
hasher: S,
) -> HashMapInit<'a, K, V, S> {
Self::init_common(num_buckets, None, area.as_mut_ptr().cast(), area.len(), hasher)
}
fn init_common(
num_buckets: u32,
shmem_handle: Option<ShmemHandle>,
area_ptr: *mut u8,
area_len: usize,
hasher: S,
) -> HashMapInit<'a, K, V, S> {
// carve out the HashMapShared struct from the area.
let mut ptr: *mut u8 = area_ptr;
let end_ptr: *mut u8 = unsafe { area_ptr.add(area_len) };
ptr = unsafe { ptr.add(ptr.align_offset(align_of::<HashMapShared<K, V>>())) };
let shared_ptr: *mut HashMapShared<K, V> = ptr.cast();
ptr = unsafe { ptr.add(size_of::<HashMapShared<K, V>>()) };
pub fn new_resizeable_named(num_buckets: u32, max_buckets: u32, name: &str) -> HashMapInit<'a, K, V> {
let size = Self::estimate_size(num_buckets);
let max_size = Self::estimate_size(max_buckets);
let shmem = ShmemHandle::new(name, size, max_size)
.expect("failed to make shared memory area");
Self {
num_buckets,
shared_ptr: shmem.data_ptr.as_ptr().cast(),
shmem_handle: Some(shmem),
shared_size: size,
hasher: rustc_hash::FxBuildHasher::default()
}
}
// carve out the buckets
ptr = unsafe { ptr.byte_add(ptr.align_offset(align_of::<core::Bucket<K, V>>())) };
let buckets_ptr = ptr;
ptr = unsafe { ptr.add(size_of::<core::Bucket<K, V>>() * num_buckets as usize) };
// use remaining space for the dictionary
ptr = unsafe { ptr.byte_add(ptr.align_offset(align_of::<u32>())) };
assert!(ptr.addr() < end_ptr.addr());
let dictionary_ptr = ptr;
let dictionary_size = unsafe { end_ptr.byte_offset_from(ptr) / size_of::<u32>() as isize };
assert!(dictionary_size > 0);
let buckets =
unsafe { std::slice::from_raw_parts_mut(buckets_ptr.cast(), num_buckets as usize) };
let dictionary = unsafe {
std::slice::from_raw_parts_mut(dictionary_ptr.cast(), dictionary_size as usize)
};
let hashmap = CoreHashMap::new(buckets, dictionary);
unsafe {
std::ptr::write(shared_ptr, HashMapShared { inner: hashmap });
}
HashMapInit {
shmem_handle,
shared_ptr,
hasher,
}
}
pub fn new_resizeable(num_buckets: u32, max_buckets: u32) -> HashMapInit<'a, K, V> {
use std::sync::atomic::{AtomicUsize, Ordering};
const COUNTER: AtomicUsize = AtomicUsize::new(0);
let val = COUNTER.fetch_add(1, Ordering::Relaxed);
let name = format!("neon_shmem_hmap{}", val);
Self::new_resizeable_named(num_buckets, max_buckets, &name)
}
}
impl<'a, K, V, S: BuildHasher> HashMapAccess<'a, K, V, S>
@@ -248,6 +254,8 @@ where
num_buckets: u32,
rehash_buckets: u32,
) {
inner.free_head = INVALID_POS;
// Recalculate the dictionary
let buckets;
let dictionary;
@@ -268,7 +276,9 @@ where
for i in 0..rehash_buckets as usize {
if buckets[i].inner.is_none() {
continue;
buckets[i].next = inner.free_head;
inner.free_head = i as u32;
continue;
}
let hash = self.hasher.hash_one(&buckets[i].inner.as_ref().unwrap().0);
@@ -286,19 +296,13 @@ where
pub fn shuffle(&mut self) {
let map = unsafe { self.shared_ptr.as_mut() }.unwrap();
let inner = &mut map.inner;
let shmem_handle = self
.shmem_handle
.as_ref()
.expect("TODO(quantumish): make shuffle work w/ fixed-size table");
let num_buckets = inner.get_num_buckets() as u32;
let size_bytes = HashMapInit::<K, V, S>::estimate_size(num_buckets);
let end_ptr: *mut u8 = unsafe { shmem_handle.data_ptr.as_ptr().add(size_bytes) };
let end_ptr: *mut u8 = unsafe { (self.shared_ptr as *mut u8).add(size_bytes) };
let buckets_ptr = inner.buckets.as_mut_ptr();
self.rehash_dict(inner, buckets_ptr, end_ptr, num_buckets, num_buckets);
}
/// Grow
///
/// 1. grow the underlying shared memory area
@@ -336,11 +340,6 @@ where
} else {
inner.free_head
},
prev: if i > 0 {
PrevPos::Chained(i as u32 - 1)
} else {
PrevPos::First(INVALID_POS)
},
inner: None,
});
}
@@ -352,7 +351,7 @@ where
Ok(())
}
/// Begin a shrink, limiting all new allocations to be in buckets with index less than `num_buckets`.
/// Begin a shrink, limiting all new allocations to be in buckets with index below `num_buckets`.
pub fn begin_shrink(&mut self, num_buckets: u32) {
let map = unsafe { self.shared_ptr.as_mut() }.unwrap();
if num_buckets > map.inner.get_num_buckets() as u32 {
@@ -365,6 +364,26 @@ where
map.inner.alloc_limit = num_buckets;
}
/// Returns whether a shrink operation is currently in progress.
pub fn is_shrinking(&self) -> bool {
let map = unsafe { self.shared_ptr.as_mut() }.unwrap();
map.inner.is_shrinking()
}
/// Returns how many entries need to be evicted before shrink can complete.
pub fn shrink_remaining(&self) -> usize {
let map = unsafe { self.shared_ptr.as_mut() }.unwrap();
let inner = &mut map.inner;
if !inner.is_shrinking() {
panic!("shrink_remaining called when no ongoing shrink")
} else {
inner.buckets_in_use
.checked_sub(inner.alloc_limit)
.unwrap_or(0)
as usize
}
}
/// Complete a shrink after caller has evicted entries, removing the unused buckets and rehashing.
pub fn finish_shrink(&mut self) -> Result<(), crate::shmem::Error> {
let map = unsafe { self.shared_ptr.as_mut() }.unwrap();
@@ -377,30 +396,24 @@ where
if inner.get_num_buckets() == num_buckets as usize {
return Ok(());
} else if inner.get_num_buckets() > num_buckets as usize {
} else if inner.buckets_in_use > num_buckets {
panic!("called finish_shrink before enough entries were removed");
}
let mut open_spots = 0;
let mut curr = inner.free_head;
while curr != INVALID_POS {
if curr < num_buckets {
open_spots += 1;
}
curr = inner.buckets[curr as usize].next;
}
for i in (num_buckets as usize)..inner.buckets.len() {
if let Some((k, v)) = inner.buckets[i].inner.take() {
inner.alloc_bucket(k, v, inner.buckets[i].prev.unwrap_first()).unwrap();
} else {
match inner.buckets[i].prev {
PrevPos::First(_) => {
let next_pos = inner.buckets[i].next;
inner.free_head = next_pos;
if next_pos != INVALID_POS {
inner.buckets[next_pos as usize].prev = PrevPos::First(INVALID_POS);
}
},
PrevPos::Chained(j) => {
let next_pos = inner.buckets[i].next;
inner.buckets[j as usize].next = next_pos;
if next_pos != INVALID_POS {
inner.buckets[next_pos as usize].prev = PrevPos::Chained(j);
}
}
}
if let Some((k, v)) = inner.buckets[i].inner.take() {
// alloc bucket increases buckets in use, so need to decrease since we're just moving
inner.buckets_in_use -= 1;
inner.alloc_bucket(k, v).unwrap();
}
}

View File

@@ -12,14 +12,16 @@ pub(crate) const INVALID_POS: u32 = u32::MAX;
// Bucket
pub(crate) struct Bucket<K, V> {
pub(crate) next: u32,
pub(crate) prev: PrevPos,
pub(crate) next: u32,
pub(crate) inner: Option<(K, V)>,
}
pub(crate) struct CoreHashMap<'a, K, V> {
/// Dictionary used to map hashes to bucket indices.
pub(crate) dictionary: &'a mut [u32],
/// Buckets containing key-value pairs.
pub(crate) buckets: &'a mut [Bucket<K, V>],
/// Head of the freelist.
pub(crate) free_head: u32,
pub(crate) _user_list_head: u32,
@@ -63,12 +65,7 @@ where
i as u32 + 1
} else {
INVALID_POS
},
prev: if i > 0 {
PrevPos::Chained(i as u32 - 1)
} else {
PrevPos::First(INVALID_POS)
},
},
inner: None,
});
}
@@ -160,8 +157,8 @@ where
self.alloc_limit != INVALID_POS
}
// TODO(quantumish): How does this interact with an ongoing shrink?
/// Clears all entries from the hashmap.
/// Does not reset any allocation limits, but does clear any entries beyond them.
pub fn clear(&mut self) {
for i in 0..self.buckets.len() {
self.buckets[i] = Bucket {
@@ -169,12 +166,7 @@ where
i as u32 + 1
} else {
INVALID_POS
},
prev: if i > 0 {
PrevPos::Chained(i as u32 - 1)
} else {
PrevPos::First(INVALID_POS)
},
},
inner: None,
}
}
@@ -184,7 +176,6 @@ where
}
self.buckets_in_use = 0;
self.alloc_limit = INVALID_POS;
}
pub fn entry_at_bucket(&mut self, pos: usize) -> Option<OccupiedEntry<'a, '_, K, V>> {
@@ -192,13 +183,12 @@ where
return None;
}
let prev = self.buckets[pos].prev;
let entry = self.buckets[pos].inner.as_ref();
match entry {
Some((key, _)) => Some(OccupiedEntry {
_key: key.clone(),
bucket_pos: pos as u32,
prev_pos: prev,
prev_pos: PrevPos::Unknown,
map: self,
}),
_ => None,
@@ -206,7 +196,7 @@ where
}
/// Find the position of an unused bucket via the freelist and initialize it.
pub(crate) fn alloc_bucket(&mut self, key: K, value: V, dict_pos: u32) -> Result<u32, FullError> {
pub(crate) fn alloc_bucket(&mut self, key: K, value: V) -> Result<u32, FullError> {
let mut pos = self.free_head;
// Find the first bucket we're *allowed* to use.
@@ -225,17 +215,12 @@ where
PrevPos::First(_) => {
let next_pos = self.buckets[pos as usize].next;
self.free_head = next_pos;
if next_pos != INVALID_POS {
self.buckets[next_pos as usize].prev = PrevPos::First(dict_pos);
}
}
PrevPos::Chained(p) => if p != INVALID_POS {
let next_pos = self.buckets[pos as usize].next;
self.buckets[p as usize].next = next_pos;
if next_pos != INVALID_POS {
self.buckets[next_pos as usize].prev = PrevPos::Chained(p);
}
},
PrevPos::Unknown => unreachable!()
}
// Initialize the bucket.

View File

@@ -10,13 +10,19 @@ pub enum Entry<'a, 'b, K, V> {
Vacant(VacantEntry<'a, 'b, K, V>),
}
/// Helper enum representing the previous position within a hashmap chain.
#[derive(Clone, Copy)]
pub(crate) enum PrevPos {
/// Starting index within the dictionary.
First(u32),
/// Regular index within the buckets.
Chained(u32),
/// Unknown - e.g. the associated entry was retrieved by index instead of chain.
Unknown,
}
impl PrevPos {
/// Unwrap an index from a `PrevPos::First`, panicking otherwise.
pub fn unwrap_first(&self) -> u32 {
match self {
Self::First(i) => *i,
@@ -26,10 +32,13 @@ impl PrevPos {
}
pub struct OccupiedEntry<'a, 'b, K, V> {
pub(crate) map: &'b mut CoreHashMap<'a, K, V>,
pub(crate) _key: K, // The key of the occupied entry
pub(crate) map: &'b mut CoreHashMap<'a, K, V>,
/// The key of the occupied entry
pub(crate) _key: K,
/// The index of the previous entry in the chain.
pub(crate) prev_pos: PrevPos,
pub(crate) bucket_pos: u32, // The position of the bucket in the CoreHashMap's buckets array
/// The position of the bucket in the CoreHashMap's buckets array.
pub(crate) bucket_pos: u32,
}
impl<'a, 'b, K, V> OccupiedEntry<'a, 'b, K, V> {
@@ -65,17 +74,14 @@ impl<'a, 'b, K, V> OccupiedEntry<'a, 'b, K, V> {
PrevPos::First(dict_pos) => self.map.dictionary[dict_pos as usize] = bucket.next,
PrevPos::Chained(bucket_pos) => {
self.map.buckets[bucket_pos as usize].next = bucket.next
}
},
PrevPos::Unknown => panic!("can't safely remove entry with unknown previous entry"),
}
// and add it to the freelist
if self.map.free_head != INVALID_POS {
self.map.buckets[self.map.free_head as usize].prev = PrevPos::Chained(self.bucket_pos);
}
let bucket = &mut self.map.buckets[self.bucket_pos as usize];
let old_value = bucket.inner.take();
bucket.next = self.map.free_head;
bucket.prev = PrevPos::First(INVALID_POS);
self.map.free_head = self.bucket_pos;
self.map.buckets_in_use -= 1;
@@ -91,14 +97,11 @@ pub struct VacantEntry<'a, 'b, K, V> {
impl<'a, 'b, K: Clone + Hash + Eq, V> VacantEntry<'a, 'b, K, V> {
pub fn insert(self, value: V) -> Result<&'b mut V, FullError> {
let pos = self.map.alloc_bucket(self.key, value, self.dict_pos)?;
let pos = self.map.alloc_bucket(self.key, value)?;
if pos == INVALID_POS {
return Err(FullError());
}
let bucket = &mut self.map.buckets[pos as usize];
if let PrevPos::First(INVALID_POS) = bucket.prev {
bucket.prev = PrevPos::First(self.dict_pos);
}
bucket.next = self.map.dictionary[self.dict_pos as usize];
self.map.dictionary[self.dict_pos as usize] = pos;

View File

@@ -38,11 +38,9 @@ impl<'a> From<&'a [u8]> for TestKey {
}
fn test_inserts<K: Into<TestKey> + Copy>(keys: &[K]) {
const MAX_MEM_SIZE: usize = 10000000;
let shmem = ShmemHandle::new("test_inserts", 0, MAX_MEM_SIZE).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(100000, shmem);
let mut w = init_struct.attach_writer();
let mut w = HashMapInit::<TestKey, usize>::new_resizeable_named(
100000, 120000, "test_inserts"
).attach_writer();
for (idx, k) in keys.iter().enumerate() {
let hash = w.get_hash_value(&(*k).into());
@@ -193,24 +191,23 @@ fn do_shrink(
to: u32
) {
writer.begin_shrink(to);
for i in to..from {
if let Some(entry) = writer.entry_at_bucket(i as usize) {
shadow.remove(&entry._key);
entry.remove();
while writer.get_num_buckets_in_use() > to as usize {
let (k, _) = shadow.pop_first().unwrap();
let hash = writer.get_hash_value(&k);
let entry = writer.entry_with_hash(k, hash);
if let Entry::Occupied(mut e) = entry {
e.remove();
}
}
writer.finish_shrink().unwrap();
}
#[test]
fn random_ops() {
let shmem = ShmemHandle::new("test_inserts", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(100000, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
100000, 120000, "test_random"
).attach_writer();
let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
let distribution = Zipf::new(u128::MAX as f64, 1.1).unwrap();
let mut rng = rand::rng();
@@ -227,11 +224,25 @@ fn random_ops() {
}
}
#[test]
fn test_shuffle() {
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1000, 1200, "test_shuf"
).attach_writer();
let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
let mut rng = rand::rng();
do_random_ops(10000, 1000, 0.75, &mut writer, &mut shadow, &mut rng);
writer.shuffle();
do_random_ops(10000, 1000, 0.75, &mut writer, &mut shadow, &mut rng);
}
#[test]
fn test_grow() {
let shmem = ShmemHandle::new("test_grow", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1000, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1000, 2000, "test_grow"
).attach_writer();
let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
let mut rng = rand::rng();
@@ -242,9 +253,9 @@ fn test_grow() {
#[test]
fn test_shrink() {
let shmem = ShmemHandle::new("test_shrink", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1500, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1500, 2000, "test_shrink"
).attach_writer();
let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
let mut rng = rand::rng();
@@ -257,9 +268,9 @@ fn test_shrink() {
#[test]
fn test_shrink_grow_seq() {
let shmem = ShmemHandle::new("test_shrink_grow_seq", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1500, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1000, 20000, "test_grow_seq"
).attach_writer();
let mut shadow: std::collections::BTreeMap<TestKey, usize> = BTreeMap::new();
let mut rng = rand::rng();
@@ -281,9 +292,9 @@ fn test_shrink_grow_seq() {
#[test]
fn test_bucket_ops() {
let shmem = ShmemHandle::new("test_bucket_ops", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1000, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1000, 1200, "test_bucket_ops"
).attach_writer();
let hash = writer.get_hash_value(&1.into());
match writer.entry_with_hash(1.into(), hash) {
Entry::Occupied(mut e) => { e.insert(2); },
@@ -307,9 +318,9 @@ fn test_bucket_ops() {
#[test]
fn test_shrink_zero() {
let shmem = ShmemHandle::new("test_shrink_zero", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1500, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1500, 2000, "test_shrink_zero"
).attach_writer();
writer.begin_shrink(0);
for i in 0..1500 {
writer.entry_at_bucket(i).map(|x| x.remove());
@@ -336,27 +347,27 @@ fn test_shrink_zero() {
#[test]
#[should_panic]
fn test_grow_oom() {
let shmem = ShmemHandle::new("test_grow_oom", 0, 500).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(5, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1500, 2000, "test_grow_oom"
).attach_writer();
writer.grow(20000).unwrap();
}
#[test]
#[should_panic]
fn test_shrink_bigger() {
let shmem = ShmemHandle::new("test_shrink_bigger", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1500, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1500, 2500, "test_shrink_bigger"
).attach_writer();
writer.begin_shrink(2000);
}
#[test]
#[should_panic]
fn test_shrink_early_finish() {
let shmem = ShmemHandle::new("test_shrink_early_finish", 0, 10000000).unwrap();
let init_struct = HashMapInit::<TestKey, usize>::init_in_shmem(1500, shmem);
let mut writer = init_struct.attach_writer();
let mut writer = HashMapInit::<TestKey, usize>::new_resizeable_named(
1500, 2500, "test_shrink_early_finish"
).attach_writer();
writer.finish_shrink().unwrap();
}
@@ -364,7 +375,7 @@ fn test_shrink_early_finish() {
#[should_panic]
fn test_shrink_fixed_size() {
let mut area = [MaybeUninit::uninit(); 10000];
let init_struct = HashMapInit::<TestKey, usize>::init_in_fixed_area(3, &mut area);
let init_struct = HashMapInit::<TestKey, usize>::with_fixed(3, &mut area);
let mut writer = init_struct.attach_writer();
writer.begin_shrink(1);
}