use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Barrier};
use regolith::{Db, Options, Statistics, Ticker};
use tempfile::TempDir;
const KEYS: u32 = 40_000;
fn key(i: u32) -> Vec<u8> {
format!("k{i:07}").into_bytes()
}
fn value(i: u32) -> Vec<u8> {
let mut v = format!("v{i:07}:").into_bytes();
v.extend(std::iter::repeat_n((i % 251) as u8, 64 + (i % 97) as usize));
v
}
#[test]
fn readers_never_see_a_wrong_block_while_compaction_evicts_files() {
let dir = TempDir::new().unwrap();
let stats = Arc::new(Statistics::new());
let db = Arc::new(
Db::open(
dir.path(),
Options {
block_cache_size: 256 * 1024,
block_cache_num_shard_bits: 4,
block_size: 1024,
write_buffer_size: 256 * 1024,
statistics: Some(Arc::clone(&stats)),
..Options::default()
},
)
.unwrap(),
);
for i in 0..KEYS {
db.put(&key(i), &value(i)).unwrap();
}
db.compact_range(None, None).unwrap();
let stop = Arc::new(AtomicBool::new(false));
let barrier = Arc::new(Barrier::new(5));
let readers: Vec<_> = (0..4u32)
.map(|t| {
let db = Arc::clone(&db);
let stop = Arc::clone(&stop);
let barrier = Arc::clone(&barrier);
std::thread::spawn(move || {
barrier.wait();
let mut reads = 0u64;
let mut i = t;
while !stop.load(Ordering::Relaxed) {
let k = i % KEYS;
assert_eq!(
db.get(&key(k)).unwrap(),
Some(value(k)),
"reader {t} read the wrong value for key {k}"
);
reads += 1;
i = i.wrapping_add(7919);
}
reads
})
})
.collect();
barrier.wait();
for round in 0..6u32 {
for i in (round..KEYS).step_by(97) {
db.put(&key(i), &value(i)).unwrap();
}
db.compact_range(None, None).unwrap();
}
stop.store(true, Ordering::Relaxed);
let total: u64 = readers.into_iter().map(|r| r.join().expect("reader")).sum();
assert!(total > 10_000, "the readers barely ran: {total} reads");
for i in 0..KEYS {
assert_eq!(db.get(&key(i)).unwrap(), Some(value(i)), "key {i} diverged");
}
let usage = db.get_int_property("regolith.block-cache-usage").unwrap();
let capacity = db
.get_int_property("regolith.block-cache-capacity")
.unwrap();
let hits = stats.get_ticker(Ticker::BlockCacheHit);
let misses = stats.get_ticker(Ticker::BlockCacheMiss);
println!(
"ADVINTEGRITY reads={total} usage={usage} capacity={capacity} hits={hits} misses={misses}"
);
assert!(
usage <= capacity,
"cache holds {usage} bytes against a {capacity}-byte budget after the storm"
);
assert!(hits > 0, "the cache never served a hit");
}
#[test]
fn a_disabled_cache_serves_the_same_storm() {
let dir = TempDir::new().unwrap();
let db = Arc::new(
Db::open(
dir.path(),
Options {
block_cache_size: 0,
block_cache_num_shard_bits: 8,
block_size: 1024,
write_buffer_size: 256 * 1024,
..Options::default()
},
)
.unwrap(),
);
for i in 0..KEYS {
db.put(&key(i), &value(i)).unwrap();
}
db.compact_range(None, None).unwrap();
let stop = Arc::new(AtomicBool::new(false));
let readers: Vec<_> = (0..4u32)
.map(|t| {
let db = Arc::clone(&db);
let stop = Arc::clone(&stop);
std::thread::spawn(move || {
let mut i = t;
while !stop.load(Ordering::Relaxed) {
let k = i % KEYS;
assert_eq!(db.get(&key(k)).unwrap(), Some(value(k)));
i = i.wrapping_add(7919);
}
})
})
.collect();
for _ in 0..3 {
for i in (0..KEYS).step_by(211) {
db.put(&key(i), &value(i)).unwrap();
}
db.compact_range(None, None).unwrap();
}
stop.store(true, Ordering::Relaxed);
for r in readers {
r.join().expect("reader");
}
assert_eq!(
(
db.get_int_property("regolith.block-cache-usage").unwrap(),
db.get_int_property("regolith.block-cache-capacity")
.unwrap()
),
(0, 0)
);
}