use crate::index::{Index, Keys};
use crate::scan::Cursor;
use yo_arena::Arena;
use yo_common::{Addr, Space, bytes_eq, wyhash};
const HDR: usize = 8;
const EVAC_FLOOR: usize = 64 * 1024;
const EVAC_CEILING: usize = yo_arena::SEGMENT_SIZE;
#[derive(Clone, Copy)]
struct Evac {
seg: usize,
off: usize,
}
struct Record;
impl Record {
#[inline]
fn lens(bytes: &[u8]) -> (usize, usize) {
let k = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
let v = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as usize;
(k, v)
}
}
struct Records<'a> {
arena: &'a Arena,
}
impl Keys for Records<'_> {
#[inline]
fn hash_at(&self, addr: Addr) -> u64 {
let (klen, _) = Record::lens(self.arena.get(addr, HDR));
let bytes = self.arena.get(addr, HDR + klen);
wyhash(&bytes[HDR..], 0)
}
#[inline]
fn eq_at(&self, addr: Addr, key: &[u8]) -> bool {
let bytes = self.arena.get(addr, HDR);
let (klen, _) = Record::lens(bytes);
if klen != key.len() {
return false;
}
let bytes = self.arena.get(addr, HDR + klen);
bytes_eq(&bytes[HDR..], key)
}
}
pub struct RawMap {
index: Index,
arena: Arena,
evac: Option<Evac>,
writes: u64,
}
impl RawMap {
pub fn new() -> RawMap {
RawMap {
index: Index::new(),
arena: Arena::new(),
evac: None,
writes: 0,
}
}
#[inline]
#[must_use]
pub const fn writes(&self) -> u64 {
self.writes
}
#[inline]
pub fn len(&self) -> usize {
self.index.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.index.is_empty()
}
pub fn clear(&mut self) {
let writes = self.writes;
*self = RawMap::new();
self.writes = writes + 1;
}
#[inline]
#[must_use]
pub fn hash_of(key: &[u8]) -> u64 {
wyhash(key, 0)
}
#[inline]
pub fn prefetch(&self, hash: u64) {
self.index.prefetch(hash);
}
#[inline]
pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
self.get_hashed(Self::hash_of(key), key)
}
#[inline]
pub fn get_hashed(&self, hash: u64, key: &[u8]) -> Option<&[u8]> {
let addr = self.index.get(hash, key, &Records { arena: &self.arena })?;
Some(self.value_at(addr))
}
#[inline]
pub fn find(&self, key: &[u8]) -> Option<Addr> {
self.find_hashed(Self::hash_of(key), key)
}
#[inline]
pub fn find_hashed(&self, hash: u64, key: &[u8]) -> Option<Addr> {
self.index.get(hash, key, &Records { arena: &self.arena })
}
#[inline]
#[must_use]
pub fn value_at(&self, addr: Addr) -> &[u8] {
let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
&self.arena.get(addr, HDR + klen + vlen)[HDR + klen..]
}
#[inline]
pub fn value_at_mut(&mut self, addr: Addr) -> &mut [u8] {
let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
&mut self.arena.get_mut(addr, HDR + klen + vlen)[HDR + klen..]
}
#[inline]
pub fn value_mut(&mut self, key: &[u8]) -> Option<&mut [u8]> {
self.value_mut_hashed(Self::hash_of(key), key)
}
#[inline]
pub fn value_mut_hashed(&mut self, hash: u64, key: &[u8]) -> Option<&mut [u8]> {
self.writes += 1;
let addr = self.index.get(hash, key, &Records { arena: &self.arena })?;
let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
Some(&mut self.arena.get_mut(addr, HDR + klen + vlen)[HDR + klen..])
}
pub fn set(&mut self, key: &[u8], val: &[u8]) -> Option<usize> {
self.set_with(key, val.len(), |buf| buf.copy_from_slice(val))
}
#[inline]
#[must_use]
pub const fn max_record() -> usize {
yo_arena::MAX_ALLOC
}
#[inline]
#[must_use]
pub const fn header_len() -> usize {
HDR
}
pub fn set_with<F>(&mut self, key: &[u8], vlen: usize, fill: F) -> Option<usize>
where
F: FnOnce(&mut [u8]),
{
self.writes += 1;
assert!(key.len() <= u32::MAX as usize, "key too long");
assert!(vlen <= u32::MAX as usize, "value too long");
let total = HDR + key.len() + vlen;
let h = wyhash(key, 0);
if let Some(addr) = self.index.get(h, key, &Records { arena: &self.arena }) {
let (klen, old_vlen) = Record::lens(self.arena.get(addr, HDR));
debug_assert_eq!(klen, key.len(), "the index matched a different key");
if old_vlen == vlen {
let rec = self.arena.get_mut(addr, total);
fill(&mut rec[HDR + klen..]);
return Some(vlen);
}
}
let (addr, buf) = self
.arena
.alloc(total)
.expect("record is larger than a segment");
buf[0..4].copy_from_slice(&(key.len() as u32).to_le_bytes());
buf[4..8].copy_from_slice(&(vlen as u32).to_le_bytes());
buf[HDR..HDR + key.len()].copy_from_slice(key);
fill(&mut buf[HDR + key.len()..total]);
let old = {
let recs = Records { arena: &self.arena };
self.index.insert(h, key, addr, &recs)
};
match old {
Some(prev) => {
let (pk, pv) = Record::lens(self.arena.get(prev, HDR));
self.arena.free(prev, HDR + pk + pv);
Some(pv)
}
None => None,
}
}
pub fn del(&mut self, key: &[u8]) -> bool {
self.writes += 1;
let h = wyhash(key, 0);
let addr = {
let recs = Records { arena: &self.arena };
self.index.remove(h, key, &recs)
};
match addr {
Some(a) => {
let (k, v) = Record::lens(self.arena.get(a, HDR));
self.arena.free(a, HDR + k + v);
true
}
None => false,
}
}
#[inline]
pub fn contains(&self, key: &[u8]) -> bool {
let h = wyhash(key, 0);
self.index.contains(h, key, &Records { arena: &self.arena })
}
#[inline]
#[must_use]
pub fn entry_at(&self, addr: Addr) -> (&[u8], &[u8]) {
let (klen, vlen) = Record::lens(self.arena.get(addr, HDR));
let bytes = self.arena.get(addr, HDR + klen + vlen);
(&bytes[HDR..HDR + klen], &bytes[HDR + klen..])
}
pub fn scan(&self, from: Cursor, budget: usize, mut out: impl FnMut(&[u8], &[u8])) -> Cursor {
let arena = &self.arena;
let mut at = from;
let mut seen = 0usize;
loop {
at = self.index.scan(at, |addr| {
let (klen, vlen) = Record::lens(arena.get(addr, HDR));
let bytes = arena.get(addr, HDR + klen + vlen);
out(&bytes[HDR..HDR + klen], &bytes[HDR + klen..]);
seen += 1;
});
if at.is_end() || seen >= budget {
return at;
}
}
}
pub fn sample(&self, r: u64, mut out: impl FnMut(&[u8], &[u8], Addr) -> bool) {
let arena = &self.arena;
self.index.sample(r, |addr| {
let (klen, vlen) = Record::lens(arena.get(addr, HDR));
let bytes = arena.get(addr, HDR + klen + vlen);
out(&bytes[HDR..HDR + klen], &bytes[HDR + klen..], addr)
});
}
pub fn index(&self) -> &Index {
&self.index
}
pub fn arena(&self) -> &Arena {
&self.arena
}
pub fn memory_bytes(&self) -> usize {
self.index.memory_bytes() + self.arena.reserved_bytes() as usize
}
pub fn compact_segment(&mut self, seg: usize) -> usize {
self.writes += 1;
if seg == self.arena.current_segment() {
return 0;
}
let (moved, _) = self.evacuate(seg, yo_arena::HEADER_SIZE, usize::MAX);
self.arena.reclaim(seg);
moved
}
fn evacuate(&mut self, seg: usize, from: usize, budget: usize) -> (usize, usize) {
let base = (seg as u64) << yo_arena::SEGMENT_SHIFT;
let bump = self.arena.recorded_bump(seg) as usize;
let stop = from.saturating_add(budget).min(bump);
let mut moved = 0;
let mut off = from;
while off < stop {
let old = Addr::new(Space::Arena, base + off as u64);
let (klen, vlen) = Record::lens(self.arena.get(old, HDR));
let total = HDR + klen + vlen;
off += total.next_multiple_of(yo_arena::ALIGN);
let hash = {
let bytes = self.arena.get(old, HDR + klen);
wyhash(&bytes[HDR..], 0)
};
let live = {
let bytes = self.arena.get(old, HDR + klen);
let key = &bytes[HDR..];
let recs = Records { arena: &self.arena };
self.index.get(hash, key, &recs) == Some(old)
};
if !live {
continue;
}
let new = self.arena.copy_within(old, total);
let bytes = self.arena.get(new, HDR + klen);
let key = &bytes[HDR..];
let recs = Records { arena: &self.arena };
let ok = self.index.relocate(hash, key, new, &recs);
debug_assert!(ok, "compaction lost an entry the index just handed us");
self.arena.free(old, total);
moved += 1;
}
(moved, off)
}
fn budget(&self) -> usize {
let behind = self.arena.candidate_count().max(1);
EVAC_FLOOR.saturating_mul(behind).min(EVAC_CEILING)
}
pub fn compact_step(&mut self) -> Option<usize> {
self.compact(false)
}
pub fn compact_hard(&mut self) -> Option<usize> {
self.compact(true)
}
fn compact(&mut self, hard: bool) -> Option<usize> {
self.writes += 1;
let (seg, from) = match self.evac {
Some(e) => (e.seg, e.off),
None => {
let pick = if hard {
self.arena.any_candidate()?
} else {
self.arena.worst_candidate()?
};
(pick, yo_arena::HEADER_SIZE)
}
};
if seg == self.arena.current_segment() {
self.evac = None;
return Some(0);
}
let budget = self.budget();
let (moved, off) = self.evacuate(seg, from, budget);
if off >= self.arena.recorded_bump(seg) as usize {
self.arena.reclaim(seg);
self.evac = None;
} else {
self.evac = Some(Evac { seg, off });
}
Some(moved)
}
}
impl Default for RawMap {
fn default() -> RawMap {
RawMap::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::{HashMap, HashSet};
fn key(i: usize) -> Vec<u8> {
let mut k = *b"key:000000000000";
let mut n = i;
let mut p = k.len() - 1;
while n > 0 {
k[p] = b'0' + (n % 10) as u8;
n /= 10;
p -= 1;
}
k.to_vec()
}
fn val(i: usize) -> Vec<u8> {
let mut v = vec![b'v'];
if i == 0 {
v.push(b'0');
return v;
}
let start = v.len();
let mut n = i;
while n > 0 {
v.push(b'0' + (n % 10) as u8);
n /= 10;
}
v[start..].reverse();
v
}
#[cfg(miri)]
const GROW_N: usize = 3_000;
#[cfg(not(miri))]
const GROW_N: usize = 200_000;
#[cfg(miri)]
const ADVERSARIAL_N: u64 = 1_000;
#[cfg(not(miri))]
const ADVERSARIAL_N: u64 = 50_000;
#[cfg(miri)]
const COMPACT_VAL: usize = 65_536;
#[cfg(miri)]
const COMPACT_N: usize = 200;
#[cfg(not(miri))]
const COMPACT_VAL: usize = 1024;
#[cfg(not(miri))]
const COMPACT_N: usize = 8_000;
#[test]
fn set_get_del() {
let mut m = RawMap::new();
assert!(m.is_empty());
assert_eq!(m.set(b"a", b"1"), None);
assert_eq!(m.get(b"a"), Some(&b"1"[..]));
assert_eq!(m.len(), 1);
assert_eq!(m.set(b"a", b"22"), Some(1));
assert_eq!(m.get(b"a"), Some(&b"22"[..]));
assert_eq!(m.len(), 1);
assert!(m.del(b"a"));
assert!(!m.del(b"a"));
assert_eq!(m.get(b"a"), None);
assert!(m.is_empty());
}
#[test]
fn a_value_can_be_overwritten_where_it_lies() {
let mut m = RawMap::new();
m.set(b"n", &7u64.to_le_bytes());
m.set(b"other", b"untouched");
let before = m.arena().live_bytes();
let v = m.value_mut(b"n").expect("the key is there");
v.copy_from_slice(&8u64.to_le_bytes());
assert_eq!(m.get(b"n"), Some(&8u64.to_le_bytes()[..]));
assert_eq!(m.get(b"other"), Some(&b"untouched"[..]));
assert_eq!(m.arena().live_bytes(), before);
assert_eq!(m.len(), 2);
assert!(m.value_mut(b"missing").is_none());
}
#[test]
fn an_overwrite_of_the_same_size_makes_no_garbage() {
let mut m = RawMap::new();
m.set(b"k", b"12345678");
m.set(b"other", b"untouched");
let live = m.arena().live_bytes();
let dead = m.arena().dead_bytes_total();
for i in 0..1000u32 {
let v = format!("{i:08}");
assert_eq!(m.set(b"k", v.as_bytes()), Some(8));
}
assert_eq!(m.get(b"k"), Some(&b"00000999"[..]));
assert_eq!(m.get(b"other"), Some(&b"untouched"[..]));
assert_eq!(m.len(), 2);
assert_eq!(m.arena().live_bytes(), live, "a thousand writes, no growth");
assert_eq!(m.arena().dead_bytes_total(), dead, "and nothing dead");
assert_eq!(m.set(b"k", b"123456789"), Some(8));
assert_eq!(m.get(b"k"), Some(&b"123456789"[..]));
assert!(
m.arena().dead_bytes_total() > dead,
"the old record is dead"
);
}
#[test]
fn a_longer_value_moves_and_the_index_follows_it() {
let mut m = RawMap::new();
m.set(b"k", b"aaaa");
let first = m
.index()
.get(RawMap::hash_of(b"k"), b"k", &Records { arena: m.arena() });
m.set(b"k", b"aaaaaaaa");
let second = m
.index()
.get(RawMap::hash_of(b"k"), b"k", &Records { arena: m.arena() });
assert_ne!(first, second, "a longer value needs a new record");
assert_eq!(m.get(b"k"), Some(&b"aaaaaaaa"[..]));
}
#[test]
fn empty_key_and_empty_value() {
let mut m = RawMap::new();
m.set(b"", b"");
assert_eq!(m.get(b""), Some(&b""[..]));
m.set(b"x", b"");
assert_eq!(m.get(b"x"), Some(&b""[..]));
assert_eq!(m.len(), 2);
}
#[test]
fn grows_through_many_splits() {
let mut m = RawMap::new();
const N: usize = GROW_N;
for i in 0..N {
m.set(&key(i), &val(i));
}
assert_eq!(m.len(), N);
assert!(
m.index().splits() > 4,
"expected real growth, saw {} splits",
m.index().splits()
);
for i in 0..N {
assert_eq!(
m.get(&key(i)),
Some(val(i).as_slice()),
"lost key {i} after {} splits",
m.index().splits()
);
}
for i in (0..N).step_by(3) {
assert!(m.del(&key(i)), "delete missed key {i}");
}
for i in 0..N {
assert_eq!(
m.contains(&key(i)),
i % 3 != 0,
"wrong presence for key {i}"
);
}
}
#[test]
fn compaction_preserves_everything() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
for i in (0..N).step_by(2) {
m.del(&key(i));
}
let candidates = m.arena().compaction_candidates();
assert!(
!candidates.is_empty(),
"expected at least one segment past the dead ratio"
);
for seg in candidates {
m.compact_segment(seg);
}
for i in 0..N {
let want = if i % 2 == 0 { None } else { Some(val.clone()) };
assert_eq!(m.get(&key(i)).map(|v| v.to_vec()), want, "key {i}");
}
}
#[test]
fn rewriting_the_same_keys_stops_growing() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
m.compact_step();
}
let after_first_pass = m.arena().reserved_bytes();
for _ in 0..9 {
for i in 0..N {
m.set(&key(i), &val);
m.compact_step();
}
}
let after_ten = m.arena().reserved_bytes();
assert!(
after_ten <= after_first_pass * 2,
"held {after_ten} after ten passes against {after_first_pass} after one, \
which is the grow forever shape"
);
assert!(
after_ten < m.arena().live_bytes() * 2,
"held {after_ten} for {} live, which is more than the ratio allows",
m.arena().live_bytes()
);
for i in 0..N {
assert_eq!(
m.get(&key(i)).map(<[u8]>::to_vec),
Some(val.clone()),
"key {i}"
);
}
}
#[test]
fn a_segment_comes_back_over_several_calls() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
for i in (0..N).step_by(2) {
m.del(&key(i));
}
let rec = (HDR + key(0).len() + COMPACT_VAL).next_multiple_of(yo_arena::ALIGN);
let per_call = m.budget() / rec + 1;
let free = m.arena().free_segments();
let moved = m.compact_step().expect("half of it is dead");
assert!(
moved <= per_call,
"one call moved {moved} records and the budget is {per_call}"
);
assert_eq!(
m.arena().free_segments(),
free,
"a segment came back before the walk reached the end of it"
);
let mut calls = 1;
while m.arena().free_segments() == free {
m.compact_step()
.expect("the segment in flight is not finished");
calls += 1;
assert!(calls < 1000, "the walk is not getting any further along");
}
assert!(calls > 2, "the whole segment came back in {calls} calls");
for i in 0..N {
let want = if i % 2 == 0 { None } else { Some(val.clone()) };
assert_eq!(m.get(&key(i)).map(<[u8]>::to_vec), want, "key {i}");
}
}
#[test]
fn a_store_with_little_dead_in_it_only_collects_when_pushed() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
for i in (0..N).step_by(50) {
m.del(&key(i));
}
assert_eq!(m.compact_step(), None, "not worth collecting");
let free = m.arena().free_segments();
let mut calls = 0;
while m.arena().free_segments() == free {
assert!(
m.compact_hard().is_some(),
"there is a segment holding something dead"
);
calls += 1;
assert!(calls < 1000, "the walk is not getting any further along");
}
for i in 0..N {
let want = if i % 50 == 0 { None } else { Some(val.clone()) };
assert_eq!(m.get(&key(i)).map(<[u8]>::to_vec), want, "key {i}");
}
}
#[test]
fn the_budget_scales_with_the_backlog() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
assert_eq!(m.budget(), EVAC_FLOOR, "nothing is waiting yet");
for i in 0..N {
m.del(&key(i));
}
let flooded = m.budget();
assert!(
flooded >= EVAC_FLOOR * m.arena().candidate_count(),
"{} segments are waiting and the budget is {flooded}",
m.arena().candidate_count()
);
assert!(
flooded > EVAC_FLOOR,
"every segment is dead and the budget is still the floor"
);
assert!(flooded <= EVAC_CEILING, "walked past a whole segment");
}
#[test]
fn the_segment_in_flight_is_finished_first() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
for i in 0..N / 4 {
m.del(&key(i));
}
let free = m.arena().free_segments();
let first = m.arena().worst_candidate().expect("the front is all dead");
m.compact_step().expect("there is a candidate");
for i in N / 2..N {
m.del(&key(i));
}
let worse = m.arena().worst_candidate().expect("the back is all dead");
assert_ne!(worse, first, "the test needs the answer to have moved");
while m.arena().free_segments() == free {
m.compact_step()
.expect("the segment in flight is not finished");
}
assert!(
m.arena().is_free(first),
"the segment that was in flight is not the one that came back"
);
assert!(
!m.arena().is_free(worse),
"the walk moved to the segment that tied with it partway through"
);
}
#[test]
fn an_emptied_segment_is_used_again() {
let mut m = RawMap::new();
let val = vec![b'z'; COMPACT_VAL];
const N: usize = COMPACT_N;
for i in 0..N {
m.set(&key(i), &val);
}
for i in (0..N).step_by(2) {
m.del(&key(i));
}
let before = m.arena().segment_count();
let seg = m.arena().worst_candidate().expect("half of it is dead");
m.compact_segment(seg);
assert_eq!(
m.arena().free_segments(),
1,
"the segment did not come back"
);
for i in N..N * 2 {
m.set(&key(i), &val);
if m.arena().free_segments() == 0 {
break;
}
}
assert_eq!(
m.arena().segment_count(),
before,
"asked the system for memory while holding an empty segment"
);
}
#[test]
fn adversarial_keys_that_share_low_bits() {
let mut m = RawMap::new();
let mut inserted = Vec::new();
for i in 0..ADVERSARIAL_N {
let k = i.to_le_bytes().to_vec();
m.set(&k, b"v");
inserted.push(k);
}
for k in &inserted {
assert_eq!(m.get(k), Some(&b"v"[..]));
}
assert_eq!(m.len(), inserted.len());
}
#[test]
fn every_way_of_writing_moves_the_counter() {
let mut m = RawMap::new();
let mut last = m.writes();
let mut moved = |m: &RawMap, what: &str| {
assert!(m.writes() > last, "{what} did not move the counter");
last = m.writes();
};
m.set(b"k", b"v");
moved(&m, "set");
m.set_with(b"k", 1, |b| b[0] = b'w');
moved(&m, "set_with");
m.value_mut(b"k");
moved(&m, "value_mut");
m.value_mut_hashed(RawMap::hash_of(b"k"), b"k");
moved(&m, "value_mut_hashed");
m.compact_step();
moved(&m, "compact_step");
m.compact_segment(0);
moved(&m, "compact_segment");
m.del(b"k");
moved(&m, "del");
}
#[test]
fn sampling_hands_back_real_entries_and_stops_when_told() {
let mut m = RawMap::new();
for i in 0..2000u32 {
m.set(format!("k{i}").as_bytes(), format!("v{i}").as_bytes());
}
let mut count = 0usize;
m.sample(0x1234_5678_9abc_def0, |key, val, addr| {
assert_eq!(m.get(key), Some(val));
assert_eq!(m.find(key), Some(addr));
count += 1;
count < 5
});
assert_eq!(count, 5, "it did not stop when it was told to");
let mut all = 0usize;
m.sample(0, |_, _, _| {
all += 1;
true
});
assert!(all > 0, "it found nothing in a map of two thousand keys");
assert!(
all < m.len(),
"one segment and not the whole map, got {all} of {}",
m.len()
);
}
#[test]
fn sampling_a_sparse_map_still_finds_something() {
let mut m = RawMap::new();
for i in 0..2000u32 {
m.set(format!("k{i}").as_bytes(), b"v");
}
for i in 0..1998u32 {
m.del(format!("k{i}").as_bytes());
}
assert_eq!(m.len(), 2);
let mut found = 0usize;
for r in 0..200u64 {
m.sample(r.wrapping_mul(0x9e37_79b9_7f4a_7c15), |_, _, _| {
found += 1;
true
});
}
assert!(found > 0, "two hundred draws and it never found either key");
}
#[test]
fn stamping_a_value_in_place_is_not_a_write() {
let mut m = RawMap::new();
m.set(b"k", b"hello");
let addr = m.find(b"k").expect("just stored");
let before = m.writes();
m.value_at_mut(addr)[0] = b'j';
assert_eq!(m.writes(), before, "a stamp counted as a write");
assert_eq!(m.get(b"k"), Some(&b"jello"[..]));
assert_eq!(m.find(b"k"), Some(addr));
assert_eq!(m.value_at(addr), b"jello");
}
#[test]
fn clearing_does_not_send_the_counter_backwards() {
let mut m = RawMap::new();
for i in 0..10u32 {
m.set(&i.to_le_bytes(), b"v");
}
let before = m.writes();
m.clear();
assert!(m.writes() > before, "clear went backwards or stood still");
}
#[cfg(miri)]
const SCAN_N: usize = 400;
#[cfg(not(miri))]
const SCAN_N: usize = 20_000;
#[test]
fn a_walk_of_an_empty_map_ends_on_the_first_call() {
let m = RawMap::new();
let mut seen = 0;
let at = m.scan(Cursor::START, 1000, |_, _| seen += 1);
assert_eq!(seen, 0);
assert!(
at.is_end(),
"an empty map took more than one call to finish"
);
}
#[test]
fn a_quiet_walk_returns_every_key_exactly_once() {
let mut m = RawMap::new();
for i in 0..SCAN_N {
m.set(&key(i), &val(i));
}
let mut counts: HashMap<Vec<u8>, usize> = HashMap::new();
let mut at = Cursor::START;
let mut calls = 0;
loop {
at = m.scan(at, 1, |k, v| {
assert_eq!(m.get(k), Some(v), "the value came back on the wrong key");
*counts.entry(k.to_vec()).or_default() += 1;
});
calls += 1;
assert!(calls < 1_000_000, "the cursor is not advancing");
if at.is_end() {
break;
}
}
assert_eq!(
counts.len(),
SCAN_N,
"the walk missed keys or invented them"
);
for i in 0..SCAN_N {
assert_eq!(counts.get(&key(i)).copied(), Some(1), "key {i}");
}
}
#[test]
fn a_budget_big_enough_finishes_in_one_call() {
let mut m = RawMap::new();
for i in 0..SCAN_N {
m.set(&key(i), &val(i));
}
let mut seen = 0;
let at = m.scan(Cursor::START, usize::MAX, |_, _| seen += 1);
assert_eq!(seen, SCAN_N);
assert!(at.is_end());
}
#[test]
fn a_walk_survives_the_map_growing_underneath_it() {
let mut m = RawMap::new();
for i in 0..SCAN_N {
m.set(&key(i), &val(i));
}
let depth_before = m.index().global_depth();
let mut seen: HashSet<Vec<u8>> = HashSet::new();
let mut at = Cursor::START;
let mut added = SCAN_N;
loop {
at = m.scan(at, 8, |k, _| {
seen.insert(k.to_vec());
});
if at.is_end() {
break;
}
for _ in 0..64 {
m.set(&key(added), &val(added));
added += 1;
}
}
assert!(
m.index().global_depth() > depth_before,
"the directory never doubled, so this test proved nothing"
);
for i in 0..SCAN_N {
assert!(
seen.contains(&key(i)),
"key {i} was there throughout and never came back"
);
}
}
#[test]
fn a_walk_survives_keys_being_deleted_underneath_it() {
let mut m = RawMap::new();
for i in 0..SCAN_N {
m.set(&key(i), &val(i));
}
let mut seen: HashSet<Vec<u8>> = HashSet::new();
let mut at = Cursor::START;
let mut next_gone = 1;
loop {
at = m.scan(at, 8, |k, _| {
seen.insert(k.to_vec());
});
if at.is_end() {
break;
}
for _ in 0..16 {
if next_gone < SCAN_N {
m.del(&key(next_gone));
next_gone += 2;
}
}
}
for i in (0..SCAN_N).step_by(2) {
assert!(
seen.contains(&key(i)),
"key {i} was never deleted and never came back"
);
}
}
#[test]
fn a_walk_that_starts_partway_returns_everything_from_there_on() {
let mut m = RawMap::new();
for i in 0..SCAN_N {
m.set(&key(i), &val(i));
}
let half = 1u64 << (crate::scan::PREFIX_BITS - 1);
let mut seen: HashSet<Vec<u8>> = HashSet::new();
let at = m.scan(Cursor::at(half, 0), usize::MAX, |k, _| {
seen.insert(k.to_vec());
});
assert!(at.is_end());
let mut expected = 0;
for i in 0..SCAN_N {
let k = key(i);
if Cursor::prefix_of(RawMap::hash_of(&k)) >= half {
expected += 1;
assert!(
seen.contains(&k),
"key {i} is past the cursor and did not come back"
);
}
}
assert!(
expected > 0 && expected < SCAN_N,
"the split point was degenerate"
);
}
}