use std::sync::atomic::{AtomicU64, Ordering};
use yo_common::{Addr, Code, Error, Result, Rng, bytes_eq};
use yo_index::RawMap;
use crate::Clock;
use crate::access::{Access, Lfu, Policy};
use crate::array::Array;
use crate::evict;
use crate::hash::{self, Hash};
use crate::list::{self, List};
use crate::set::{self, Set};
use crate::slab::{Bytes, Slab};
use crate::ttl::{self, Applied, Ask, Cond};
use crate::value::{self, Kind};
use crate::zset::{self, Zset};
macro_rules! bytes {
($($t:ty),*) => { $(impl Bytes for $t {
#[inline]
fn memory_bytes(&self) -> usize {
<$t>::memory_bytes(self)
}
})* };
}
bytes!(Set, Hash, List, Zset, Array);
pub struct Keyspace {
pub(crate) map: RawMap,
pub(crate) clock: Clock,
pub(crate) expired: u64,
pub(crate) evicted: u64,
pub(crate) sets: Slab<Set>,
pub(crate) hashes: Slab<Hash>,
pub(crate) lists: Slab<List>,
pub(crate) zsets: Slab<Zset>,
pub(crate) arrays: Slab<Array>,
pub(crate) bodies: usize,
pub(crate) limits: set::Limits,
pub(crate) hash_limits: hash::Limits,
pub(crate) list_limits: list::Limits,
pub(crate) zset_limits: zset::Limits,
pub(crate) policy: Policy,
pub(crate) lfu: Lfu,
pub(crate) samples: usize,
pub(crate) rng: Rng,
memo: Memo,
pub(crate) scratch: Vec<u8>,
pub(crate) rows: Vec<usize>,
pub(crate) setops: crate::setops::Scratch,
}
const SCRATCH: usize = 1024;
const ROUNDS: usize = 4;
struct Memo {
writes: u64,
live: bool,
kind: Kind,
slot: u32,
addr: Addr,
len: u8,
key: [u8; Memo::MAX],
}
impl Memo {
const MAX: usize = 32;
const fn empty() -> Memo {
Memo {
writes: 0,
live: false,
kind: Kind::String,
slot: 0,
addr: Addr::NONE,
len: 0,
key: [0; Memo::MAX],
}
}
#[inline]
fn get(&self, writes: u64, key: &[u8]) -> Option<(Kind, u32, Addr)> {
if !self.live || self.writes != writes || key.len() != self.len as usize {
return None;
}
bytes_eq(&self.key[..key.len()], key).then_some((self.kind, self.slot, self.addr))
}
#[inline]
fn put(&mut self, writes: u64, key: &[u8], kind: Kind, slot: u32, addr: Addr) {
if key.len() > Memo::MAX {
self.live = false;
return;
}
self.writes = writes;
self.live = true;
self.kind = kind;
self.slot = slot;
self.addr = addr;
self.len = key.len() as u8;
self.key[..key.len()].copy_from_slice(key);
}
}
static MADE: AtomicU64 = AtomicU64::new(0);
impl Keyspace {
#[must_use]
pub fn new() -> Keyspace {
Keyspace::with_clock(Clock::system())
}
#[must_use]
pub fn with_clock(clock: Clock) -> Keyspace {
let made = MADE.fetch_add(1, Ordering::Relaxed);
Keyspace {
map: RawMap::new(),
clock,
expired: 0,
evicted: 0,
sets: Slab::new(),
hashes: Slab::new(),
lists: Slab::new(),
zsets: Slab::new(),
arrays: Slab::new(),
bodies: 0,
limits: set::Limits::DEFAULT,
hash_limits: hash::Limits::DEFAULT,
list_limits: list::Limits::default(),
zset_limits: zset::Limits::DEFAULT,
policy: Policy::default(),
lfu: Lfu::DEFAULT,
samples: evict::SAMPLES,
rng: Rng::new(clock.now_ms() ^ made.wrapping_mul(0x9e37_79b9_7f4a_7c15)),
memo: Memo::empty(),
scratch: Vec::with_capacity(SCRATCH),
rows: Vec::new(),
setops: crate::setops::Scratch::new(),
}
}
#[inline]
pub const fn seed(&mut self, seed: u64) {
self.rng = Rng::new(seed);
}
#[inline]
#[must_use]
pub const fn policy(&self) -> Policy {
self.policy
}
#[inline]
pub const fn set_policy(&mut self, policy: Policy) {
self.policy = policy;
}
#[inline]
#[must_use]
pub const fn lfu(&self) -> Lfu {
self.lfu
}
#[inline]
pub const fn set_lfu(&mut self, lfu: Lfu) {
self.lfu = lfu;
}
pub fn idle_secs(&mut self, key: &[u8]) -> Option<u64> {
let addr = self.live_rec_untouched(key)?;
let now = self.clock.now_ms();
Some(self.access_at(addr).idle_secs(now))
}
pub fn freq(&mut self, key: &[u8]) -> Option<u8> {
let addr = self.live_rec_untouched(key)?;
let (now, lfu) = (self.clock.now_ms(), self.lfu);
Some(self.access_at(addr).freq(now, lfu))
}
pub(crate) fn write_rec(
&mut self,
key: &[u8],
len: usize,
fill: impl FnOnce(&mut [u8]),
) -> Option<usize> {
let a = self.access_for_write(key);
self.map.set_with(key, len, |out| {
fill(out);
value::set_access(out, a);
})
}
fn access_for_write(&mut self, key: &[u8]) -> Access {
let now = self.clock.now_ms();
if !self.policy.is_lfu() {
return Access::lru(now);
}
match self.map.get(key).and_then(value::access) {
Some(a) if !a.is_unset() => a,
_ => Access::lfu(now),
}
}
#[inline]
fn access_at(&self, addr: Addr) -> Access {
value::access(self.map.value_at(addr)).unwrap_or_default()
}
#[inline]
fn stamp(&mut self, addr: Addr) {
let now = self.clock.now_ms();
if self.policy.is_lfu() {
let (lfu, current) = (self.lfu, self.access_at(addr));
let next = current.touched(now, lfu, &mut self.rng);
value::set_access(self.map.value_at_mut(addr), next);
} else {
value::set_access(self.map.value_at_mut(addr), Access::lru(now));
}
}
#[inline]
pub const fn limits(&self) -> &set::Limits {
&self.limits
}
#[inline]
pub const fn set_limits(&mut self, limits: set::Limits) {
self.limits = limits;
}
#[inline]
pub const fn hash_limits(&self) -> &hash::Limits {
&self.hash_limits
}
#[inline]
pub const fn set_hash_limits(&mut self, limits: hash::Limits) {
self.hash_limits = limits;
}
#[inline]
pub const fn list_limits(&self) -> &list::Limits {
&self.list_limits
}
#[inline]
pub const fn set_list_limits(&mut self, limits: list::Limits) {
self.list_limits = limits;
}
#[inline]
pub const fn zset_limits(&self) -> &zset::Limits {
&self.zset_limits
}
#[inline]
pub const fn set_zset_limits(&mut self, limits: zset::Limits) {
self.zset_limits = limits;
}
#[inline]
pub const fn clock(&self) -> &Clock {
&self.clock
}
#[inline]
pub const fn clock_mut(&mut self) -> &mut Clock {
&mut self.clock
}
#[inline]
pub const fn map(&self) -> &RawMap {
&self.map
}
#[inline]
pub fn len(&self) -> usize {
self.map.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn kind_of(&mut self, key: &[u8]) -> Option<Kind> {
let now = self.clock.now_ms();
let (kind, dead) = self
.map
.get(key)
.map(|rec| (value::kind(rec), value::is_expired(rec, now)))?;
if dead {
self.drop_key(key);
self.expired += 1;
return None;
}
Some(kind)
}
pub fn set_encoding(&mut self, key: &[u8]) -> Option<set::Encoding> {
self.reap(key);
let rec = self.map.get(key)?;
if value::kind(rec) != Kind::Set {
return None;
}
let at = value::slot(rec);
Some(self.sets.get(at)?.encoding())
}
pub fn hash_encoding(&mut self, key: &[u8]) -> Option<hash::Encoding> {
self.reap(key);
let rec = self.map.get(key)?;
if value::kind(rec) != Kind::Hash {
return None;
}
let at = value::slot(rec);
Some(self.hashes.get(at)?.encoding())
}
pub fn list_encoding(&mut self, key: &[u8]) -> Option<list::Encoding> {
self.reap(key);
let rec = self.map.get(key)?;
if value::kind(rec) != Kind::List {
return None;
}
let at = value::slot(rec);
Some(self.lists.get(at)?.encoding())
}
pub fn zset_encoding(&mut self, key: &[u8]) -> Option<zset::Encoding> {
self.reap(key);
let rec = self.map.get(key)?;
if value::kind(rec) != Kind::Zset {
return None;
}
let at = value::slot(rec);
Some(self.zsets.get(at)?.encoding())
}
pub fn encoding_name(&mut self, key: &[u8]) -> Option<&'static str> {
match self.kind_of(key)? {
Kind::String => self.encoding(key).map(value::Encoding::name),
Kind::Set => self.set_encoding(key).map(set::Encoding::name),
Kind::Hash => self.hash_encoding(key).map(hash::Encoding::name),
Kind::List => self.list_encoding(key).map(list::Encoding::name),
Kind::Zset => self.zset_encoding(key).map(zset::Encoding::name),
Kind::Array => Some("sliced-array"),
Kind::Stream => unreachable!("nothing can store a stream yet"),
}
}
pub fn set_expiry(&mut self, key: &[u8], at: Option<u64>) -> bool {
self.reap(key);
let Some(rec) = self.map.get(key) else {
return false;
};
if value::expire_at(rec) == at {
return true;
}
match value::kind(rec) {
Kind::String => {
let mut bytes = std::mem::take(&mut self.scratch);
bytes.clear();
value::read(rec).write_to(&mut bytes);
self.store(key, &bytes, at);
self.scratch = bytes;
}
kind @ (Kind::Set | Kind::Hash | Kind::List | Kind::Zset | Kind::Array) => {
let slot = value::slot(rec);
let len = value::slot_record_len(at.is_some());
self.write_rec(key, len, |out| {
value::write_slot_record(out, kind, slot, at);
});
}
Kind::Stream => unreachable!("nothing can store a stream yet"),
}
true
}
pub fn deadline_of(&mut self, key: &[u8]) -> Ask {
let Some(addr) = self.live_rec_untouched(key) else {
return Ask::Missing;
};
match value::expire_at(self.map.value_at(addr)) {
Some(at) => Ask::At(at),
None => Ask::NoDeadline,
}
}
pub fn expire(&mut self, key: &[u8], at: u64, cond: Cond) -> Applied {
let prev = match self.deadline_of(key) {
Ask::Missing => return Applied::Missing,
Ask::NoDeadline => None,
Ask::At(at) => Some(at),
};
let done = ttl::decide(prev, at, cond, self.clock.now_ms());
match done {
Applied::Ok => {
self.set_expiry(key, Some(at));
}
Applied::Deleted => {
self.drop_key(key);
}
Applied::Missing | Applied::NotMet => {}
}
done
}
pub fn persist(&mut self, key: &[u8]) -> bool {
if !matches!(self.deadline_of(key), Ask::At(_)) {
return false;
}
self.set_expiry(key, None);
true
}
pub(crate) fn free_body(&mut self, key: &[u8]) {
if self.bodies == 0 {
return;
}
let Some(rec) = self.map.get(key) else {
return;
};
match value::kind(rec) {
Kind::String => {}
Kind::Set => {
let at = value::slot(rec);
self.sets.remove(at);
self.bodies -= 1;
}
Kind::Hash => {
let at = value::slot(rec);
self.hashes.remove(at);
self.bodies -= 1;
}
Kind::List => {
let at = value::slot(rec);
self.lists.remove(at);
self.bodies -= 1;
}
Kind::Zset => {
let at = value::slot(rec);
self.zsets.remove(at);
self.bodies -= 1;
}
Kind::Array => {
let at = value::slot(rec);
self.arrays.remove(at);
self.bodies -= 1;
}
Kind::Stream => unreachable!("nothing can store a stream yet"),
}
}
#[inline]
pub(crate) fn drop_key(&mut self, key: &[u8]) -> bool {
self.free_body(key);
self.map.del(key)
}
#[inline]
pub(crate) fn reap(&mut self, key: &[u8]) {
let now = self.clock.now_ms();
let dead = self.map.get(key).is_some_and(|r| value::is_expired(r, now));
if dead {
self.drop_key(key);
self.expired += 1;
}
}
pub(crate) fn live_rec(&mut self, key: &[u8]) -> Option<Addr> {
let addr = self.live_rec_untouched(key)?;
if self.policy.stamps_on_read() {
self.stamp(addr);
}
Some(addr)
}
pub(crate) fn live_rec_untouched(&mut self, key: &[u8]) -> Option<Addr> {
let now = self.clock.now_ms();
let addr = self.map.find(key)?;
if value::is_expired(self.map.value_at(addr), now) {
self.drop_key(key);
self.expired += 1;
return None;
}
Some(addr)
}
pub(crate) fn live_slot(&mut self, key: &[u8], want: Kind) -> Result<Option<u32>> {
if let Some((kind, slot, addr)) = self.memo.get(self.map.writes(), key) {
if kind != want {
return Err(wrong_type());
}
if self.policy.stamps_on_read() {
self.stamp(addr);
}
return Ok(Some(slot));
}
let now = self.clock.now_ms();
let Some(addr) = self.map.find(key) else {
return Ok(None);
};
let rec = self.map.value_at(addr);
if value::is_expired(rec, now) {
self.drop_key(key);
self.expired += 1;
return Ok(None);
}
if value::kind(rec) != want {
return Err(wrong_type());
}
let slot = value::slot(rec);
let dated = value::expire_at(rec).is_some();
if self.policy.stamps_on_read() {
self.stamp(addr);
}
if !dated {
self.memo.put(self.map.writes(), key, want, slot, addr);
}
Ok(Some(slot))
}
pub(crate) fn live_slot_either(
&mut self,
key: &[u8],
a: Kind,
b: Kind,
) -> Result<Option<(Kind, u32)>> {
if let Some((kind, slot, addr)) = self.memo.get(self.map.writes(), key) {
if kind != a && kind != b {
return Err(wrong_type());
}
if self.policy.stamps_on_read() {
self.stamp(addr);
}
return Ok(Some((kind, slot)));
}
let now = self.clock.now_ms();
let Some(addr) = self.map.find(key) else {
return Ok(None);
};
let rec = self.map.value_at(addr);
if value::is_expired(rec, now) {
self.drop_key(key);
self.expired += 1;
return Ok(None);
}
let kind = value::kind(rec);
if kind != a && kind != b {
return Err(wrong_type());
}
let slot = value::slot(rec);
let dated = value::expire_at(rec).is_some();
if self.policy.stamps_on_read() {
self.stamp(addr);
}
if !dated {
self.memo.put(self.map.writes(), key, kind, slot, addr);
}
Ok(Some((kind, slot)))
}
pub fn clear(&mut self) {
self.map.clear();
self.sets.clear();
self.hashes.clear();
self.lists.clear();
self.zsets.clear();
self.arrays.clear();
self.bodies = 0;
}
#[inline]
pub const fn expired_keys(&self) -> u64 {
self.expired
}
#[inline]
pub const fn evicted_keys(&self) -> u64 {
self.evicted
}
#[inline]
pub const fn samples(&self) -> usize {
self.samples
}
#[inline]
pub const fn set_samples(&mut self, samples: usize) {
self.samples = samples;
}
pub fn evict_one(&mut self) -> bool {
let Some(addr) = self.victim() else {
return false;
};
let mut buf = core::mem::take(&mut self.scratch);
buf.clear();
buf.extend_from_slice(self.map.entry_at(addr).0);
let gone = self.drop_key(&buf);
self.scratch = buf;
if gone {
self.evicted += 1;
}
gone
}
fn victim(&mut self) -> Option<Addr> {
if matches!(self.policy, Policy::NoEviction) || self.map.is_empty() {
return None;
}
let now = self.clock.now_ms();
let (policy, lfu, want) = (self.policy, self.lfu, self.samples);
let mut best = evict::Best::EMPTY;
let mut seen = 0usize;
for _ in 0..ROUNDS {
let r = self.rng.next_u64();
self.map.sample(r, |_key, rec, addr| {
if !value::is_expired(rec, now) && evict::eligible(rec, policy) {
seen += 1;
best.offer(addr, evict::score(rec, policy, now, lfu));
}
seen < want
});
if seen >= want {
break;
}
}
(!best.is_empty()).then_some(best.addr)
}
#[inline]
pub fn memory_bytes(&self) -> usize {
self.slab_bytes()
+ self.sets.value_bytes()
+ self.hashes.value_bytes()
+ self.lists.value_bytes()
+ self.zsets.value_bytes()
+ self.arrays.value_bytes()
}
#[inline]
pub fn settled_memory_bytes(&mut self) -> usize {
self.slab_bytes()
+ self.sets.settled_bytes()
+ self.hashes.settled_bytes()
+ self.lists.settled_bytes()
+ self.zsets.settled_bytes()
+ self.arrays.settled_bytes()
}
pub fn track_memory(&mut self, on: bool) {
self.sets.track_bytes(on);
self.hashes.track_bytes(on);
self.lists.track_bytes(on);
self.zsets.track_bytes(on);
self.arrays.track_bytes(on);
}
#[inline]
fn slab_bytes(&self) -> usize {
self.map.memory_bytes()
+ self.sets.slot_bytes()
+ self.hashes.slot_bytes()
+ self.lists.slot_bytes()
+ self.zsets.slot_bytes()
+ self.arrays.slot_bytes()
}
#[inline]
pub fn compact_step(&mut self) -> Option<usize> {
self.map.compact_step()
}
#[inline]
pub fn compact_hard(&mut self) -> Option<usize> {
self.map.compact_hard()
}
#[inline]
pub fn prefetch(&self, hash: u64) {
self.map.prefetch(hash);
}
#[inline]
#[must_use]
pub fn hash_of(key: &[u8]) -> u64 {
RawMap::hash_of(key)
}
}
pub fn wrong_type() -> Error {
Error::new(
Code::WrongType,
"Operation against a key holding the wrong kind of value",
)
}
impl Default for Keyspace {
fn default() -> Keyspace {
Keyspace::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn db() -> Keyspace {
Keyspace::with_clock(Clock::fixed(1_000))
}
#[test]
fn type_answers_string_for_a_string_and_nothing_for_a_missing_key() {
let mut d = db();
d.set_plain(b"k", b"v").expect("room");
assert_eq!(d.kind_of(b"k"), Some(Kind::String));
assert_eq!(d.kind_of(b"nope"), None);
}
#[test]
fn type_does_not_report_a_key_whose_deadline_has_gone() {
let mut d = db();
d.psetex(b"k", 100, b"v").expect("room");
assert_eq!(d.kind_of(b"k"), Some(Kind::String));
d.clock_mut().advance(100);
assert_eq!(
d.kind_of(b"k"),
None,
"the deadline was 1100 and it is 1100"
);
assert_eq!(d.len(), 0, "and asking reaped it rather than leaving it");
assert_eq!(d.expired_keys(), 1);
}
#[test]
fn the_clock_runs_under_the_default_policy() {
let mut d = db();
assert_eq!(d.policy(), Policy::NoEviction);
d.set_plain(b"k", b"v").expect("room");
assert_eq!(d.idle_secs(b"k"), Some(0));
d.clock_mut().advance(60_000);
assert_eq!(d.idle_secs(b"k"), Some(60), "a minute of nobody asking");
d.get(b"k").expect("a string").expect("still there");
assert_eq!(d.idle_secs(b"k"), Some(0), "and reading it is using it");
}
#[test]
fn asking_about_a_key_is_not_using_it() {
let mut d = db();
d.set_plain(b"k", b"v").expect("room");
d.clock_mut().advance(30_000);
assert!(d.exists(b"k"));
assert_eq!(d.kind_of(b"k"), Some(Kind::String));
assert_eq!(d.encoding_name(b"k"), Some("embstr"));
assert_eq!(d.deadline_of(b"k"), Ask::NoDeadline);
assert_eq!(d.expire_at(b"k"), None);
assert_eq!(d.idle_secs(b"k"), Some(30));
assert_eq!(
d.idle_secs(b"k"),
Some(30),
"and asking twice is still not using it"
);
}
#[test]
fn a_read_moves_the_clock_under_lru_and_leaves_it_under_lrm() {
for (policy, idle_after_read) in [(Policy::AllKeysLru, 0), (Policy::AllKeysLrm, 45)] {
let mut d = db();
d.set_policy(policy);
d.set_plain(b"k", b"v").expect("room");
d.clock_mut().advance(45_000);
d.get(b"k").expect("a string").expect("still there");
assert_eq!(
d.idle_secs(b"k"),
Some(idle_after_read),
"{}",
policy.name()
);
d.set_plain(b"k", b"w").expect("room");
assert_eq!(
d.idle_secs(b"k"),
Some(0),
"{} after a write",
policy.name()
);
}
}
#[test]
fn the_hot_key_path_still_stamps() {
let mut d = db();
d.set_policy(Policy::AllKeysLru);
d.sadd(b"s", [&b"a"[..]].into_iter()).expect("room");
d.scard(b"s").expect("a set");
d.clock_mut().advance(120_000);
for _ in 0..64 {
d.scard(b"s").expect("a set");
}
assert_eq!(d.idle_secs(b"s"), Some(0), "the memo swallowed the stamp");
}
#[test]
fn the_counter_climbs_under_an_lfu_policy() {
let mut d = db();
d.set_policy(Policy::AllKeysLfu);
d.seed(7);
d.set_plain(b"k", b"v").expect("room");
let start = d.freq(b"k").expect("there");
for _ in 0..200 {
d.get(b"k").expect("a string").expect("still there");
}
let hot = d.freq(b"k").expect("there");
assert!(hot > start, "{hot} did not climb from {start}");
d.set_plain(b"cold", b"v").expect("room");
d.clock_mut().advance(60_000 * 10);
assert!(d.freq(b"cold").expect("there") < start);
}
#[test]
fn noeviction_evicts_nothing() {
let mut d = db();
for i in 0..200u32 {
d.set_plain(format!("k{i}").as_bytes(), b"v").expect("room");
}
assert!(!d.evict_one());
assert_eq!(d.len(), 200);
assert_eq!(d.evicted_keys(), 0);
}
#[test]
fn a_volatile_policy_with_no_deadlines_anywhere_cannot_evict() {
let mut d = db();
d.set_policy(Policy::VolatileLru);
for i in 0..200u32 {
d.set_plain(format!("k{i}").as_bytes(), b"v").expect("room");
}
assert!(!d.evict_one(), "it found a key it had no business taking");
assert_eq!(d.len(), 200);
let deadline = d.clock().now_ms() + 100_000;
d.set_expiry(b"k7", Some(deadline));
assert!(d.evict_one());
assert!(!d.exists(b"k7"));
assert_eq!(d.evicted_keys(), 1);
}
#[test]
fn the_stale_key_goes_before_the_fresh_one() {
let mut d = db();
d.set_policy(Policy::AllKeysLru);
d.set_plain(b"cold", b"v").expect("room");
d.clock_mut().advance(600_000);
d.set_plain(b"hot", b"v").expect("room");
assert!(d.evict_one());
assert!(!d.exists(b"cold"), "it kept the stale one");
assert!(d.exists(b"hot"), "it took the fresh one");
}
#[test]
fn the_soonest_deadline_goes_first() {
let mut d = db();
d.set_policy(Policy::VolatileTtl);
let now = d.clock().now_ms();
d.set_plain(b"soon", b"v").expect("room");
d.set_plain(b"later", b"v").expect("room");
d.set_expiry(b"soon", Some(now + 10_000));
d.set_expiry(b"later", Some(now + 900_000));
assert!(d.evict_one());
assert!(!d.exists(b"soon"));
assert!(d.exists(b"later"));
}
#[test]
fn the_least_used_key_goes_under_lfu() {
let mut d = db();
d.set_policy(Policy::AllKeysLfu);
d.seed(11);
d.set_plain(b"popular", b"v").expect("room");
for _ in 0..300 {
d.get(b"popular").expect("a string").expect("still there");
}
d.set_plain(b"ignored", b"v").expect("room");
assert!(d.evict_one());
assert!(!d.exists(b"ignored"));
assert!(d.exists(b"popular"));
}
#[test]
fn a_nearly_empty_database_still_gives_up_a_key() {
let mut d = db();
d.set_policy(Policy::AllKeysRandom);
for i in 0..4000u32 {
d.set_plain(format!("k{i}").as_bytes(), b"v").expect("room");
}
for i in 0..3999u32 {
d.drop_key(format!("k{i}").as_bytes());
}
assert_eq!(d.len(), 1);
let mut went = false;
for _ in 0..500 {
if d.evict_one() {
went = true;
break;
}
}
assert!(went, "sampling never found the one key that was left");
assert_eq!(d.len(), 0);
assert!(!d.evict_one(), "and an empty database has nothing to give");
}
#[test]
fn a_dead_key_is_not_evicted() {
let mut d = db();
d.set_policy(Policy::AllKeysLru);
let now = d.clock().now_ms();
d.set_plain(b"k", b"v").expect("room");
d.set_expiry(b"k", Some(now + 1000));
d.clock_mut().advance(5000);
assert!(!d.evict_one(), "it evicted a key that was already dead");
assert_eq!(d.evicted_keys(), 0);
}
}