use crate::common::default_shard_amount;
use aliasable::boxed::AliasableBox;
use foldhash::fast::RandomState;
use hashbrown::hash_table::Entry;
use hashbrown::HashTable;
use parking_lot::lock_api::RawMutex as _;
use parking_lot::Mutex;
use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::collections::BTreeSet;
use std::hash::{BuildHasher, Hash};
use std::sync::atomic::{AtomicUsize, Ordering};
struct Links<K, V> {
prev: UnsafeCell<*mut State<K, V>>,
next: UnsafeCell<*mut State<K, V>>,
}
impl<K, V> Default for Links<K, V> {
fn default() -> Self {
Self {
prev: UnsafeCell::new(std::ptr::null_mut()),
next: UnsafeCell::new(std::ptr::null_mut()),
}
}
}
type State<K, V> = crate::common::State<K, V, Links<K, V>>;
struct LruShardInner<K, V> {
table: HashTable<AliasableBox<State<K, V>>>,
head: *mut State<K, V>,
tail: *mut State<K, V>,
max_size: usize,
max_evict: usize,
}
unsafe impl<K: Send, V: Send> Send for LruShardInner<K, V> {}
impl<K, V> LruShardInner<K, V> {
fn with_capacity(max_size: usize, capacity: usize) -> Self {
Self {
table: HashTable::with_capacity(capacity),
head: std::ptr::null_mut(),
tail: std::ptr::null_mut(),
max_size,
max_evict: usize::MAX,
}
}
unsafe fn detach(&mut self, node: *mut State<K, V>) {
let prev = *(*node).links.prev.get();
let next = *(*node).links.next.get();
if !prev.is_null() {
*(*prev).links.next.get() = next;
} else {
self.head = next;
}
if !next.is_null() {
*(*next).links.prev.get() = prev;
} else {
self.tail = prev;
}
*(*node).links.prev.get() = std::ptr::null_mut();
*(*node).links.next.get() = std::ptr::null_mut();
}
unsafe fn push_front(&mut self, node: *mut State<K, V>) {
*(*node).links.next.get() = self.head;
*(*node).links.prev.get() = std::ptr::null_mut();
if !self.head.is_null() {
*(*self.head).links.prev.get() = node;
}
self.head = node;
if self.tail.is_null() {
self.tail = node;
}
}
unsafe fn move_to_front(&mut self, node: *mut State<K, V>) {
if self.head == node {
return;
}
self.detach(node);
self.push_front(node);
}
fn try_evict(&mut self, current: *mut State<K, V>) {
let mut cursor = self.tail;
let mut evicted = 0;
while self.table.len() > self.max_size
&& !cursor.is_null()
&& cursor != current
&& evicted < self.max_evict
{
let prev = unsafe { *(*cursor).links.prev.get() };
let state = unsafe { &*cursor };
if state.flags().refcnt() > 0 {
cursor = prev;
continue;
}
unsafe { self.detach(cursor) };
let hash = state.hash;
if let Ok(entry) = self.table.find_entry(hash, |s| std::ptr::eq(&**s, cursor)) {
let _ = entry.remove();
}
evicted += 1;
cursor = prev;
}
}
fn pop_lru(&mut self) -> Option<(K, V)> {
let mut cursor = self.tail;
while !cursor.is_null() {
let prev = unsafe { *(*cursor).links.prev.get() };
let state = unsafe { &*cursor };
if state.flags().refcnt() == 0 {
let hash = state.hash;
unsafe { self.detach(cursor) };
let entry = self
.table
.find_entry(hash, |s| std::ptr::eq(&**s, cursor))
.ok()
.expect("lockmap: entry in the LRU list must exist in the table");
let (state_box, _) = entry.remove();
let state = AliasableBox::into_unique(state_box);
let State { key, value, .. } = *state;
if let Some(value) = value.into_inner() {
return Some((key, value));
}
}
cursor = prev;
}
None
}
}
struct LruShardMap<K, V> {
inner: Mutex<LruShardInner<K, V>>,
}
impl<K, V> LruShardMap<K, V> {
fn with_capacity(max_size: usize, capacity: usize) -> Self {
Self {
inner: Mutex::new(LruShardInner::with_capacity(max_size, capacity)),
}
}
fn len(&self) -> usize {
self.inner.lock().table.len()
}
fn is_empty(&self) -> bool {
self.inner.lock().table.is_empty()
}
fn max_size(&self) -> usize {
self.inner.lock().max_size
}
fn set_max_size(&self, max_size: usize) {
self.inner.lock().max_size = max_size;
}
fn set_max_evict(&self, max_evict: usize) {
self.inner.lock().max_evict = max_evict.max(1);
}
}
pub struct LruLockMap<K, V, S = RandomState> {
shards: Vec<LruShardMap<K, V>>,
hasher: S,
pop_cursor: AtomicUsize,
}
impl<K: Eq + Hash, V> LruLockMap<K, V> {
pub fn new(max_size: usize) -> Self {
Self::with_options(max_size, 0, default_shard_amount())
}
pub fn with_options(max_size: usize, initial_capacity: usize, shard_amount: usize) -> Self {
Self::with_options_and_hasher(
max_size,
initial_capacity,
shard_amount,
RandomState::default(),
)
}
}
impl<K: Eq + Hash, V, S: BuildHasher> LruLockMap<K, V, S> {
pub fn with_hasher(max_size: usize, hasher: S) -> Self {
Self::with_options_and_hasher(max_size, 0, default_shard_amount(), hasher)
}
pub fn with_options_and_hasher(
max_size: usize,
initial_capacity: usize,
shard_amount: usize,
hasher: S,
) -> Self {
assert!(shard_amount > 0, "shard_amount must be greater than 0");
let per_shard_max_size = max_size.div_ceil(shard_amount);
let per_shard_capacity = initial_capacity.div_ceil(shard_amount);
Self {
shards: (0..shard_amount)
.map(|_| LruShardMap::with_capacity(per_shard_max_size, per_shard_capacity))
.collect(),
hasher,
pop_cursor: AtomicUsize::new(0),
}
}
}
impl<K, V, S> LruLockMap<K, V, S> {
pub fn len(&self) -> usize {
self.shards.iter().map(|s| s.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.shards.iter().all(|s| s.is_empty())
}
pub fn max_size(&self) -> usize {
let max_size = self.shards.first().map(|s| s.max_size()).unwrap_or(0);
self.shards.len() * max_size
}
pub fn set_max_size(&self, max_size: usize) {
let per_shard_max_size = max_size.div_ceil(self.shards.len());
for shard in &self.shards {
shard.set_max_size(per_shard_max_size);
}
}
pub fn set_max_evict(&self, max_evict: usize) {
for shard in &self.shards {
shard.set_max_evict(max_evict);
}
}
#[inline(always)]
fn shard_index(&self, hash: u64) -> usize {
(((hash >> 32) * self.shards.len() as u64) >> 32) as usize
}
#[inline(always)]
fn state_hasher() -> impl Fn(&AliasableBox<State<K, V>>) -> u64 {
|s| s.hash
}
}
impl<K: Eq + Hash, V, S: BuildHasher> LruLockMap<K, V, S> {
pub fn entry(&self, key: K) -> LruEntry<'_, K, V, S> {
let ptr = self.acquire_state(key);
self.guard(ptr)
}
pub fn try_entry(&self, key: K) -> Option<LruEntry<'_, K, V, S>> {
let ptr = self.acquire_state(key);
self.try_guard(ptr)
}
fn acquire_state(&self, key: K) -> *mut State<K, V> {
let hash = self.hasher.hash_one(&key);
let shard = &self.shards[self.shard_index(hash)];
let mut inner = shard.inner.lock();
let ptr = match inner
.table
.entry(hash, |s| s.key.borrow() == &key, Self::state_hasher())
{
Entry::Occupied(occupied) => {
let ptr = &**occupied.get() as *const State<K, V> as *mut State<K, V>;
unsafe { &*ptr }.inc_ref();
unsafe { inner.move_to_front(ptr) };
ptr
}
Entry::Vacant(vacant) => {
let state = State::new(key, None, 1, hash);
let ptr = &*state as *const State<K, V> as *mut State<K, V>;
vacant.insert(state);
unsafe { inner.push_front(ptr) };
ptr
}
};
inner.try_evict(ptr);
ptr
}
pub fn entry_by_ref<Q>(&self, key: &Q) -> LruEntry<'_, K, V, S>
where
K: Borrow<Q> + for<'c> From<&'c Q>,
Q: Eq + Hash + ?Sized,
{
let ptr = self.acquire_state_by_ref(key);
self.guard(ptr)
}
pub fn try_entry_by_ref<Q>(&self, key: &Q) -> Option<LruEntry<'_, K, V, S>>
where
K: Borrow<Q> + for<'c> From<&'c Q>,
Q: Eq + Hash + ?Sized,
{
let ptr = self.acquire_state_by_ref(key);
self.try_guard(ptr)
}
fn acquire_state_by_ref<Q>(&self, key: &Q) -> *mut State<K, V>
where
K: Borrow<Q> + for<'c> From<&'c Q>,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let mut inner = shard.inner.lock();
let ptr = match inner
.table
.entry(hash, |s| s.key.borrow() == key, Self::state_hasher())
{
Entry::Occupied(occupied) => {
let ptr = &**occupied.get() as *const State<K, V> as *mut State<K, V>;
unsafe { &*ptr }.inc_ref();
unsafe { inner.move_to_front(ptr) };
ptr
}
Entry::Vacant(vacant) => {
let owned_key: K = key.into();
let state = State::new(owned_key, None, 1, hash);
let ptr = &*state as *const State<K, V> as *mut State<K, V>;
vacant.insert(state);
unsafe { inner.push_front(ptr) };
ptr
}
};
inner.try_evict(ptr);
ptr
}
pub fn get<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
V: Clone,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let mut ptr: *mut State<K, V> = std::ptr::null_mut();
let value = {
let mut inner = shard.inner.lock();
let p = inner
.table
.find(hash, |s| s.key.borrow() == key)
.map(|s| &**s as *const State<K, V> as *mut State<K, V>)
.unwrap_or(std::ptr::null_mut());
if !p.is_null() {
unsafe { inner.move_to_front(p) };
let state = unsafe { &*p };
if state.flags().refcnt() == 0 {
unsafe { state.value_ref() }.clone()
} else {
state.inc_ref();
ptr = p;
None
}
} else {
None
}
};
if ptr.is_null() {
return value;
}
self.guard(ptr).get().clone()
}
pub fn peek<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
V: Clone,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let mut ptr: *mut State<K, V> = std::ptr::null_mut();
let value = {
let inner = shard.inner.lock();
let p = inner
.table
.find(hash, |s| s.key.borrow() == key)
.map(|s| &**s as *const State<K, V> as *mut State<K, V>)
.unwrap_or(std::ptr::null_mut());
if !p.is_null() {
let state = unsafe { &*p };
if state.flags().refcnt() == 0 {
unsafe { state.value_ref() }.clone()
} else {
state.inc_ref();
ptr = p;
None
}
} else {
None
}
};
if ptr.is_null() {
return value;
}
self.guard(ptr).get().clone()
}
pub fn insert(&self, key: K, value: V) -> Option<V> {
let hash = self.hasher.hash_one(&key);
let shard = &self.shards[self.shard_index(hash)];
let (ptr, old) = {
let mut inner = shard.inner.lock();
match inner
.table
.entry(hash, |s| s.key.borrow() == &key, Self::state_hasher())
{
Entry::Occupied(occupied) => {
let p = &**occupied.get() as *const State<K, V> as *mut State<K, V>;
unsafe { inner.move_to_front(p) };
let state = unsafe { &*p };
let flags = state.flags();
if flags.refcnt() == 0 {
let old = unsafe { state.value_mut() }.replace(value);
state.set_value_state(true);
(std::ptr::null_mut(), old)
} else {
state.inc_ref();
(p, Some(value))
}
}
Entry::Vacant(vacant) => {
let state = State::new(key, Some(value), 0, hash);
let new_ptr = &*state as *const State<K, V> as *mut State<K, V>;
vacant.insert(state);
unsafe { inner.push_front(new_ptr) };
inner.try_evict(new_ptr);
(std::ptr::null_mut(), None)
}
}
};
if ptr.is_null() {
return old;
}
self.guard(ptr).swap(old)
}
pub fn insert_by_ref<Q>(&self, key: &Q, value: V) -> Option<V>
where
K: Borrow<Q> + for<'c> From<&'c Q>,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let (ptr, old) = {
let mut inner = shard.inner.lock();
match inner
.table
.entry(hash, |s| s.key.borrow() == key, Self::state_hasher())
{
Entry::Occupied(occupied) => {
let p = &**occupied.get() as *const State<K, V> as *mut State<K, V>;
unsafe { inner.move_to_front(p) };
let state = unsafe { &*p };
let flags = state.flags();
if flags.refcnt() == 0 {
let old = unsafe { state.value_mut() }.replace(value);
state.set_value_state(true);
(std::ptr::null_mut(), old)
} else {
state.inc_ref();
(p, Some(value))
}
}
Entry::Vacant(vacant) => {
let owned_key: K = key.into();
let state = State::new(owned_key, Some(value), 0, hash);
let new_ptr = &*state as *const State<K, V> as *mut State<K, V>;
vacant.insert(state);
unsafe { inner.push_front(new_ptr) };
inner.try_evict(new_ptr);
(std::ptr::null_mut(), None)
}
}
};
if ptr.is_null() {
return old;
}
self.guard(ptr).swap(old)
}
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let mut ptr: *mut State<K, V> = std::ptr::null_mut();
let found = {
let mut inner = shard.inner.lock();
let p = inner
.table
.find(hash, |s| s.key.borrow() == key)
.map(|s| &**s as *const State<K, V> as *mut State<K, V>)
.unwrap_or(std::ptr::null_mut());
if !p.is_null() {
unsafe { inner.move_to_front(p) };
let state = unsafe { &*p };
if state.flags().refcnt() == 0 {
unsafe { state.value_ref() }.is_some()
} else {
state.inc_ref();
ptr = p;
false
}
} else {
false
}
};
if ptr.is_null() {
return found;
}
self.guard(ptr).get().is_some()
}
pub fn remove<Q>(&self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: Eq + Hash + ?Sized,
{
let hash = self.hasher.hash_one(key);
let shard = &self.shards[self.shard_index(hash)];
let ptr = {
let mut inner = shard.inner.lock();
let p = match inner.table.find_entry(hash, |s| s.key.borrow() == key) {
Ok(occupied) => {
let p = &**occupied.get() as *const State<K, V> as *mut State<K, V>;
let state = unsafe { &*p };
if state.flags().refcnt() == 0 {
let value = unsafe { state.value_mut() }.take();
let (state_box, _) = occupied.remove();
unsafe { inner.detach(p) };
drop(state_box);
return value;
}
state.inc_ref();
p
}
Err(_) => return None,
};
p
};
self.guard(ptr).remove()
}
pub fn batch_lock<'a, M>(&'a self, keys: BTreeSet<K>) -> M
where
K: Clone,
M: FromIterator<(K, LruEntry<'a, K, V, S>)>,
{
keys.into_iter()
.map(|key| (key.clone(), self.entry(key)))
.collect()
}
}
impl<K: Eq + Hash, V, S> LruLockMap<K, V, S> {
pub fn pop_lru(&self) -> Option<(K, V)> {
let shard_count = self.shards.len();
let start = self.pop_cursor.fetch_add(1, Ordering::Relaxed) % shard_count;
for i in 0..shard_count {
let shard = &self.shards[(start + i) % shard_count];
if let Some(kv) = shard.inner.lock().pop_lru() {
return Some(kv);
}
}
None
}
pub fn clear(&self) {
for shard in &self.shards {
let mut in_use: Vec<*mut State<K, V>> = Vec::new();
{
let mut inner = shard.inner.lock();
let mut cursor = inner.head;
while !cursor.is_null() {
let next = unsafe { *(*cursor).links.next.get() };
let state = unsafe { &*cursor };
if state.flags().refcnt() == 0 {
let hash = state.hash;
unsafe { inner.detach(cursor) };
if let Ok(entry) =
inner.table.find_entry(hash, |s| std::ptr::eq(&**s, cursor))
{
let _ = entry.remove();
}
} else {
state.inc_ref();
in_use.push(cursor);
}
cursor = next;
}
}
for ptr in in_use {
self.guard(ptr).remove();
}
}
}
pub fn for_each<F>(&self, mut f: F)
where
F: FnMut(&K, &V),
{
for shard in &self.shards {
let mut in_use: Vec<*mut State<K, V>> = Vec::new();
{
let inner = shard.inner.lock();
for s in inner.table.iter() {
if s.flags().refcnt() == 0 {
if let Some(v) = unsafe { s.value_ref() } {
f(&s.key, v);
}
} else {
s.inc_ref();
in_use.push(&**s as *const State<K, V> as *mut State<K, V>);
}
}
}
for ptr in in_use {
let entry = self.guard(ptr);
if let Some(v) = entry.get() {
f(entry.key(), v);
}
}
}
}
pub fn retain<F>(&self, mut f: F)
where
F: FnMut(&K, &mut V) -> bool,
{
for shard in &self.shards {
let mut in_use: Vec<*mut State<K, V>> = Vec::new();
{
let mut inner = shard.inner.lock();
let mut cursor = inner.head;
while !cursor.is_null() {
let next = unsafe { *(*cursor).links.next.get() };
let state = unsafe { &*cursor };
if state.flags().refcnt() == 0 {
let keep = match unsafe { state.value_mut() } {
Some(v) => f(&state.key, v),
None => false,
};
if !keep {
let hash = state.hash;
unsafe { inner.detach(cursor) };
if let Ok(entry) =
inner.table.find_entry(hash, |s| std::ptr::eq(&**s, cursor))
{
let _ = entry.remove();
}
}
} else {
state.inc_ref();
in_use.push(cursor);
}
cursor = next;
}
}
for ptr in in_use {
let mut entry = self.guard(ptr);
let key: &K = unsafe { &(*ptr).key };
let keep = match entry.get_mut() {
Some(v) => f(key, v),
None => true,
};
if !keep {
entry.remove();
}
}
}
}
fn guard(&self, ptr: *mut State<K, V>) -> LruEntry<'_, K, V, S> {
unsafe { (*ptr).mutex.lock() };
LruEntry {
map: self,
state: ptr,
}
}
fn try_guard(&self, ptr: *mut State<K, V>) -> Option<LruEntry<'_, K, V, S>> {
if unsafe { (*ptr).mutex.try_lock() } {
Some(LruEntry {
map: self,
state: ptr,
})
} else {
self.release_ref(ptr);
None
}
}
fn release_ref(&self, state: *mut State<K, V>) {
let state_ref = unsafe { &*state };
if !state_ref.release_ref_needs_cleanup() {
return;
}
let shard = &self.shards[self.shard_index(state_ref.hash)];
let mut inner = shard.inner.lock();
let final_flags = state_ref.dec_ref();
if final_flags.pending_cleanup() {
unsafe { inner.detach(state) };
let state_ptr = state as *const State<K, V>;
if let Ok(entry) = inner
.table
.find_entry(state_ref.hash, |s| std::ptr::eq(&**s, state_ptr))
{
let _ = entry.remove();
}
}
}
}
impl<K: Eq + Hash, V, S: BuildHasher + Default> Default for LruLockMap<K, V, S> {
fn default() -> Self {
Self::with_options_and_hasher(usize::MAX, 0, default_shard_amount(), S::default())
}
}
impl<K, V, S> std::fmt::Debug for LruLockMap<K, V, S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LruLockMap").finish()
}
}
pub struct LruEntry<'a, K: Eq + Hash, V, S = RandomState> {
map: &'a LruLockMap<K, V, S>,
state: *mut State<K, V>,
}
unsafe impl<K: Eq + Hash + Sync, V: Sync, S: Sync> Sync for LruEntry<'_, K, V, S> {}
impl<K: Eq + Hash, V, S> LruEntry<'_, K, V, S> {
pub fn key(&self) -> &K {
unsafe { &(*self.state).key }
}
pub fn get(&self) -> &Option<V> {
unsafe { (*self.state).value_ref() }
}
pub fn get_mut(&mut self) -> &mut Option<V> {
unsafe { (*self.state).value_mut() }
}
pub fn insert(&mut self, value: V) -> Option<V> {
self.get_mut().replace(value)
}
pub fn swap(&mut self, mut value: Option<V>) -> Option<V> {
std::mem::swap(self.get_mut(), &mut value);
value
}
pub fn remove(&mut self) -> Option<V> {
self.get_mut().take()
}
pub fn or_insert(&mut self, default: V) -> &mut V {
self.get_mut().get_or_insert(default)
}
pub fn or_insert_with<F: FnOnce() -> V>(&mut self, default: F) -> &mut V {
self.get_mut().get_or_insert_with(default)
}
}
impl<K: Eq + Hash + std::fmt::Debug, V: std::fmt::Debug, S> std::fmt::Debug
for LruEntry<'_, K, V, S>
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LruEntry")
.field("key", self.key())
.field("value", self.get())
.finish()
}
}
impl<K: Eq + Hash, V, S> Drop for LruEntry<'_, K, V, S> {
fn drop(&mut self) {
let has_value = self.get().is_some();
let state_ref = unsafe { &*self.state };
state_ref.set_value_state(has_value);
unsafe { state_ref.mutex.unlock() };
self.map.release_ref(self.state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{
atomic::{AtomicU32, Ordering},
Arc,
};
fn make_valueless(cache: &LruLockMap<u32, u32>, key: u32) {
for shard in &cache.shards {
let inner = shard.inner.lock();
for s in inner.table.iter() {
if s.key == key {
unsafe { (*s.value.get()).take() };
s.set_value_state(false);
}
}
}
}
#[test]
fn test_basic_insert_get_remove() {
let cache = LruLockMap::<String, u32>::new(100);
assert!(cache.is_empty());
assert_eq!(cache.len(), 0);
cache.insert("a".to_string(), 1);
assert_eq!(cache.get("a"), Some(1));
assert!(!cache.is_empty());
assert_eq!(cache.len(), 1);
assert_eq!(cache.insert("a".to_string(), 2), Some(1));
assert_eq!(cache.get("a"), Some(2));
assert_eq!(cache.remove("a"), Some(2));
assert_eq!(cache.get("a"), None);
assert!(cache.is_empty());
}
#[test]
fn test_insert_by_ref() {
let cache = LruLockMap::<String, u32>::new(100);
cache.insert_by_ref("key", 42);
assert_eq!(cache.get("key"), Some(42));
assert_eq!(cache.insert_by_ref("key", 99), Some(42));
assert_eq!(cache.get("key"), Some(99));
}
#[test]
fn test_contains_key() {
let cache = LruLockMap::<String, u32>::new(100);
assert!(!cache.contains_key("x"));
cache.insert("x".to_string(), 7);
assert!(cache.contains_key("x"));
cache.remove("x");
assert!(!cache.contains_key("x"));
}
#[test]
fn test_entry_by_val() {
let cache = LruLockMap::<u32, u32>::new(100);
{
let mut entry = cache.entry(1);
assert_eq!(*entry.key(), 1);
assert!(entry.get().is_none());
entry.insert(42);
assert_eq!(*entry.get(), Some(42));
println!("{:?}", entry);
}
assert_eq!(cache.get(&1), Some(42));
{
let mut entry = cache.entry(1);
assert_eq!(entry.remove(), Some(42));
}
assert_eq!(cache.get(&1), None);
}
#[test]
fn test_entry_by_ref() {
let cache = LruLockMap::<String, u32>::new(100);
{
let mut entry = cache.entry_by_ref("key");
assert_eq!(entry.key(), "key");
entry.insert(7);
println!("{:?}", entry);
}
assert_eq!(cache.get("key"), Some(7));
{
let mut entry = cache.entry_by_ref("key");
assert_eq!(entry.get_mut().take(), Some(7));
}
assert_eq!(cache.get("key"), None);
}
#[test]
fn test_default_and_debug() {
let cache = LruLockMap::<u32, u32>::default();
println!("{:?}", cache);
assert!(cache.is_empty());
}
#[test]
fn test_lru_zero_capacity() {
let cache = LruLockMap::<u32, u32>::with_options(0, 0, 1);
assert!(cache.is_empty());
assert_eq!(cache.insert(1, 10), None);
assert_eq!(cache.len(), 1);
assert_eq!(cache.insert(2, 20), None);
assert_eq!(cache.len(), 1); }
#[test]
fn test_set_max_size() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 4);
assert_eq!(cache.max_size(), 4);
cache.set_max_size(6);
assert_eq!(cache.max_size(), 8);
}
#[test]
fn test_lru_eviction_basic() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.len(), 3);
cache.insert(4, 40);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), Some(20));
assert_eq!(cache.get(&3), Some(30));
assert_eq!(cache.get(&4), Some(40));
}
#[test]
fn test_lru_access_promotes() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.get(&1), Some(10));
cache.insert(4, 40);
assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&1), Some(10)); assert_eq!(cache.get(&3), Some(30));
assert_eq!(cache.get(&4), Some(40));
}
#[test]
fn test_lru_entry_promotes() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
{
let entry = cache.entry(1);
assert_eq!(*entry.get(), Some(10));
}
cache.insert(4, 40);
assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&1), Some(10)); }
#[test]
fn test_lru_skip_in_use_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(3, 3, 1));
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
let _entry = cache.entry(1);
let cache2 = cache.clone();
let t = std::thread::spawn(move || {
cache2.insert(4, 40);
});
t.join().unwrap();
assert_eq!(*_entry.get(), Some(10));
assert_eq!(cache.get(&2), None);
assert_eq!(cache.get(&3), Some(30));
assert_eq!(cache.get(&4), Some(40));
drop(_entry);
assert!(cache.len() <= 4);
}
#[test]
fn test_lru_evict_skips_multiple_in_use() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
let _entry1 = cache.entry(1);
let _entry2 = cache.entry(2);
cache.insert(4, 40);
assert_eq!(*_entry1.get(), Some(10));
assert_eq!(*_entry2.get(), Some(20));
assert_eq!(cache.get(&3), None);
assert_eq!(cache.get(&4), Some(40));
drop(_entry2);
drop(_entry1);
}
#[test]
fn test_lru_insert_overwrite_no_evict() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
cache.insert(2, 200);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&1), Some(10));
assert_eq!(cache.get(&2), Some(200));
assert_eq!(cache.get(&3), Some(30));
}
#[test]
fn test_lru_remove_frees_slot() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
cache.remove(&2);
assert_eq!(cache.len(), 2);
cache.insert(4, 40);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&1), Some(10));
assert_eq!(cache.get(&3), Some(30));
assert_eq!(cache.get(&4), Some(40));
}
#[test]
fn test_concurrent_same_key() {
let cache = Arc::new(LruLockMap::<usize, usize>::new(1024));
let counter = Arc::new(AtomicU32::default());
#[cfg(not(miri))]
const N: usize = 1 << 16;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 4;
cache.insert(0, 0);
let threads: Vec<_> = (0..M)
.map(|_| {
let cache = cache.clone();
let counter = counter.clone();
std::thread::spawn(move || {
for _ in 0..N {
let mut entry = cache.entry(0);
let now = counter.fetch_add(1, Ordering::AcqRel);
assert_eq!(now, 0);
let v = entry.get_mut().as_mut().unwrap();
*v += 1;
let now = counter.fetch_sub(1, Ordering::AcqRel);
assert_eq!(now, 1);
}
})
})
.collect();
threads.into_iter().for_each(|t| t.join().unwrap());
let entry = cache.entry(0);
assert_eq!(*entry.get(), Some(N * M));
}
#[test]
fn test_concurrent_same_key_by_ref() {
let cache = Arc::new(LruLockMap::<String, usize>::new(1024));
let counter = Arc::new(AtomicU32::default());
#[cfg(not(miri))]
const N: usize = 1 << 16;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 4;
cache.insert_by_ref("hello", 0);
let threads: Vec<_> = (0..M)
.map(|_| {
let cache = cache.clone();
let counter = counter.clone();
std::thread::spawn(move || {
for _ in 0..N {
let mut entry = cache.entry_by_ref("hello");
let now = counter.fetch_add(1, Ordering::AcqRel);
assert_eq!(now, 0);
let v = entry.get_mut().as_mut().unwrap();
*v += 1;
let now = counter.fetch_sub(1, Ordering::AcqRel);
assert_eq!(now, 1);
}
})
})
.collect();
threads.into_iter().for_each(|t| t.join().unwrap());
let entry = cache.entry_by_ref("hello");
assert_eq!(*entry.get(), Some(N * M));
}
#[test]
fn test_concurrent_random_keys() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(256, 16, 1));
let total = Arc::new(AtomicU32::default());
#[cfg(not(miri))]
const N: usize = 1 << 12;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 8;
let threads: Vec<_> = (0..M)
.map(|_| {
let cache = cache.clone();
let total = total.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = rand::random::<u32>() % 32;
let mut entry = cache.entry(key);
assert!(entry.get().is_none());
entry.insert(1);
total.fetch_add(1, Ordering::AcqRel);
entry.remove();
}
})
})
.collect();
threads.into_iter().for_each(|t| t.join().unwrap());
assert_eq!(total.load(Ordering::Acquire) as usize, N * M);
}
#[test]
fn test_concurrent_get_set() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(256, 16, 1));
#[cfg(not(miri))]
const N: usize = 1 << 16;
#[cfg(miri)]
const N: usize = 1 << 6;
let entry_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = rand::random::<u32>() % 32;
let value = rand::random::<u32>() % 32;
let mut entry = cache.entry(key);
if value < 16 {
entry.get_mut().take();
} else {
entry.get_mut().replace(value);
}
}
})
};
let set_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = rand::random::<u32>() % 32;
let value = rand::random::<u32>() % 32;
if value < 16 {
cache.remove(&key);
} else {
cache.insert(key, value);
}
}
})
};
let get_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = rand::random::<u32>() % 32;
let value = cache.get(&key);
if let Some(v) = value {
assert!(v >= 16);
}
}
})
};
entry_thread.join().unwrap();
set_thread.join().unwrap();
get_thread.join().unwrap();
}
#[test]
fn test_concurrent_get_set_by_ref() {
let cache = Arc::new(LruLockMap::<String, u32>::with_options(256, 16, 1));
#[cfg(not(miri))]
const N: usize = 1 << 14;
#[cfg(miri)]
const N: usize = 1 << 6;
let entry_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = rand::random::<u32>() % 32;
let mut entry = cache.entry_by_ref(&key);
if value < 16 {
entry.get_mut().take();
} else {
entry.get_mut().replace(value);
}
}
})
};
let set_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = rand::random::<u32>() % 32;
if value < 16 {
cache.remove(&key);
} else {
cache.insert_by_ref(&key, value);
}
}
})
};
let get_thread = {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = cache.get(&key);
if let Some(v) = value {
assert!(v >= 16);
}
}
})
};
entry_thread.join().unwrap();
set_thread.join().unwrap();
get_thread.join().unwrap();
}
#[test]
fn test_concurrent_with_eviction() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(32, 4, 1));
#[cfg(not(miri))]
const N: usize = 1 << 14;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 8;
let threads: Vec<_> = (0..M)
.map(|_| {
let cache = cache.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = rand::random::<u32>() % 64;
let op = rand::random::<u32>() % 4;
match op {
0 => {
cache.insert(key, key);
}
1 => {
let _ = cache.get(&key);
}
2 => {
let _ = cache.remove(&key);
}
_ => {
let mut entry = cache.entry(key);
entry.insert(key);
drop(entry);
}
}
}
})
})
.collect();
for t in threads {
t.join().unwrap();
}
assert!(cache.len() <= 64);
}
#[test]
fn test_swap() {
let cache = LruLockMap::<u32, u32>::new(100);
cache.insert(1, 10);
{
let mut entry = cache.entry(1);
let old = entry.swap(Some(20));
assert_eq!(old, Some(10));
}
assert_eq!(cache.get(&1), Some(20));
}
#[test]
fn test_lockmap_same_key_by_ref() {
let lock_map = Arc::new(LruLockMap::<String, usize>::new(1 << 20));
let current = Arc::new(AtomicU32::default());
#[cfg(not(miri))]
const N: usize = 1 << 20;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 4;
const S: &str = "hello";
lock_map.insert_by_ref(S, 0);
let threads = (0..M)
.map(|_| {
let lock_map = lock_map.clone();
let current = current.clone();
std::thread::spawn(move || {
for _ in 0..N {
let mut entry = lock_map.entry_by_ref(S);
let now = current.fetch_add(1, Ordering::AcqRel);
assert_eq!(now, 0);
let v = entry.get_mut().as_mut().unwrap();
*v += 1;
let now = current.fetch_sub(1, Ordering::AcqRel);
assert_eq!(now, 1);
}
})
})
.collect::<Vec<_>>();
threads.into_iter().for_each(|t| t.join().unwrap());
let mut entry = lock_map.entry_by_ref(S);
println!("{:?}", entry);
assert_eq!(entry.key(), S);
assert_eq!(*entry.get(), Some(N * M));
assert_eq!(entry.insert(0).unwrap(), N * M);
}
#[test]
fn test_lockmap_get_set_by_ref() {
let lock_map = Arc::new(LruLockMap::<String, u32>::with_options(1 << 20, 16, 1));
#[cfg(not(miri))]
const N: usize = 1 << 18;
#[cfg(miri)]
const N: usize = 1 << 6;
let entry_thread = {
let lock_map = lock_map.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = rand::random::<u32>() % 32;
let mut entry = lock_map.entry_by_ref(&key);
if value < 16 {
entry.get_mut().take();
} else {
entry.get_mut().replace(value);
}
}
})
};
let set_thread = {
let lock_map = lock_map.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = rand::random::<u32>() % 32;
if value < 16 {
lock_map.remove(&key);
} else {
lock_map.insert_by_ref(&key, value);
}
}
})
};
let get_thread = {
let lock_map = lock_map.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = lock_map.get(&key);
if let Some(v) = value {
assert!(v >= 16)
}
}
})
};
entry_thread.join().unwrap();
set_thread.join().unwrap();
get_thread.join().unwrap();
}
#[test]
fn test_lockmap_insert_remove() {
let lock_map = Arc::new(LruLockMap::<String, u32>::with_options(1 << 20, 16, 1));
#[cfg(not(miri))]
const N: usize = 1 << 22;
#[cfg(miri)]
const N: usize = 1 << 6;
let entry_thread = {
let lock_map = lock_map.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let mut entry = lock_map.entry_by_ref(&key);
entry.remove();
}
})
};
let set_thread = {
let lock_map = lock_map.clone();
std::thread::spawn(move || {
for _ in 0..N {
let key = (rand::random::<u32>() % 32).to_string();
let value = rand::random::<u32>() % 32;
lock_map.insert_by_ref(&key, value);
}
})
};
entry_thread.join().unwrap();
set_thread.join().unwrap();
}
#[test]
fn test_lockmap_heavy_contention() {
let lock_map = Arc::new(LruLockMap::<u32, u32>::new(1 << 20));
#[cfg(not(miri))]
const THREADS: usize = 16;
#[cfg(miri)]
const THREADS: usize = 4;
#[cfg(not(miri))]
const OPS_PER_THREAD: usize = 10000;
#[cfg(miri)]
const OPS_PER_THREAD: usize = 10;
const HOT_KEYS: u32 = 5;
let counter = Arc::new(AtomicU32::new(0));
let threads: Vec<_> = (0..THREADS)
.map(|_| {
let lock_map = lock_map.clone();
let counter = counter.clone();
std::thread::spawn(move || {
for _ in 0..OPS_PER_THREAD {
let key = rand::random::<u32>() % HOT_KEYS;
let mut entry = lock_map.entry(key);
std::thread::sleep(std::time::Duration::from_nanos(10));
match entry.get_mut() {
Some(value) => {
*value = value.wrapping_add(1);
counter.fetch_add(1, Ordering::Relaxed);
}
None => {
entry.insert(1);
counter.fetch_add(1, Ordering::Relaxed);
}
}
drop(entry);
assert!(lock_map.contains_key(&key), "Key {} should exist", key);
}
})
})
.collect();
for thread in threads {
thread.join().unwrap();
}
assert_eq!(
counter.load(Ordering::Relaxed),
THREADS as u32 * OPS_PER_THREAD as u32
);
}
#[test]
fn test_max_evict_default_unlimited() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
for i in 0..5u32 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
cache.set_max_size(1);
cache.insert(5, 50);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&5), Some(50));
assert!(cache.get(&0).is_none());
assert!(cache.get(&1).is_none());
assert!(cache.get(&2).is_none());
assert!(cache.get(&3).is_none());
assert!(cache.get(&4).is_none());
}
#[test]
fn test_max_evict_limited() {
let cache = LruLockMap::<u32, u32>::with_options(2, 2, 1);
cache.set_max_evict(1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), Some(20));
assert_eq!(cache.get(&3), Some(30));
}
#[test]
fn test_max_evict_zero_treated_as_one() {
let cache = LruLockMap::<u32, u32>::with_options(2, 2, 1);
cache.set_max_evict(0); cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.get(&1), None); assert_eq!(cache.get(&2), Some(20));
assert_eq!(cache.get(&3), Some(30));
}
#[test]
fn test_max_evict_still_respects_in_use() {
let cache = LruLockMap::<u32, u32>::with_options(1, 1, 1);
cache.set_max_evict(1);
cache.insert(1, 10);
let _entry = cache.entry(1);
cache.insert(2, 20); assert_eq!(*_entry.get(), Some(10)); assert_eq!(cache.get(&2), Some(20));
}
#[test]
fn test_max_evict_after_shrinking_capacity() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
for i in 0..5u32 {
cache.insert(i, i * 10);
}
assert_eq!(cache.len(), 5);
cache.set_max_size(2);
cache.set_max_evict(2);
cache.insert(5, 50);
assert_eq!(cache.len(), 4);
assert_eq!(cache.get(&0), None);
assert_eq!(cache.get(&1), None);
assert!(cache.get(&2).is_some());
assert!(cache.get(&3).is_some());
assert!(cache.get(&4).is_some());
assert_eq!(cache.get(&5), Some(50));
}
#[test]
fn test_lru_try_entry() {
let cache = LruLockMap::<String, u32>::new(100);
{
let mut entry = cache.try_entry("key".to_string()).unwrap();
entry.insert(1);
assert!(cache.try_entry("key".to_string()).is_none());
assert!(cache.try_entry_by_ref("key").is_none());
}
assert_eq!(cache.get("key"), Some(1));
{
let mut entry = cache.try_entry_by_ref("key").unwrap();
assert_eq!(entry.remove(), Some(1));
}
let held = cache.entry("held".to_string());
assert!(cache.try_entry("held".to_string()).is_none());
drop(held);
assert!(cache.is_empty());
}
#[test]
fn test_lru_try_entry_promotes() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert!(cache.try_entry(1).is_some());
cache.insert(4, 40);
assert_eq!(cache.get(&2), None); assert_eq!(cache.get(&1), Some(10)); }
#[test]
fn test_lru_clear() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
cache.clear();
assert!(cache.is_empty());
cache.insert(4, 40);
cache.insert(5, 50);
cache.insert(6, 60);
cache.insert(7, 70);
assert_eq!(cache.len(), 3);
assert_eq!(cache.get(&4), None); assert_eq!(cache.get(&5), Some(50));
assert_eq!(cache.get(&6), Some(60));
assert_eq!(cache.get(&7), Some(70));
cache.clear();
assert!(cache.is_empty());
}
#[test]
fn test_lru_random_batch_lock() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(256, 256, 16));
let total = Arc::new(AtomicU32::default());
#[cfg(not(miri))]
const N: usize = 1 << 14;
#[cfg(miri)]
const N: usize = 1 << 6;
const M: usize = 8;
let threads = (0..M)
.map(|_| {
let cache = cache.clone();
let total = total.clone();
std::thread::spawn(move || {
for _ in 0..N {
let keys = (0..3).map(|_| rand::random::<u32>() % 32).collect();
let mut entries: std::collections::HashMap<_, _> = cache.batch_lock(keys);
for entry in entries.values_mut() {
assert!(entry.get().is_none());
entry.insert(1);
}
total.fetch_add(1, Ordering::AcqRel);
for entry in entries.values_mut() {
entry.remove();
}
}
})
})
.collect::<Vec<_>>();
threads.into_iter().for_each(|t| t.join().unwrap());
assert_eq!(total.load(Ordering::Acquire) as usize, N * M);
}
#[test]
fn test_lru_peek_basic() {
let cache = LruLockMap::<String, u32>::new(100);
assert_eq!(cache.peek("missing"), None);
cache.insert("key".to_string(), 42);
assert_eq!(cache.peek("key"), Some(42));
assert_eq!(cache.get("key"), Some(42));
}
#[test]
fn test_lru_peek_does_not_promote() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.peek(&1), Some(10));
cache.insert(4, 40);
assert_eq!(cache.peek(&1), None); assert_eq!(cache.peek(&2), Some(20));
assert_eq!(cache.peek(&3), Some(30));
assert_eq!(cache.peek(&4), Some(40));
}
#[test]
fn test_lru_peek_held_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 10);
let entry = cache.entry(1);
let peeker = {
let cache = cache.clone();
std::thread::spawn(move || cache.peek(&1))
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(entry);
assert_eq!(peeker.join().unwrap(), Some(10));
}
#[test]
fn test_lru_pop_lru_order() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
assert_eq!(cache.get(&1), Some(10));
assert_eq!(cache.pop_lru(), Some((2, 20)));
assert_eq!(cache.pop_lru(), Some((3, 30)));
assert_eq!(cache.pop_lru(), Some((1, 10)));
assert_eq!(cache.pop_lru(), None);
assert!(cache.is_empty());
}
#[test]
fn test_lru_pop_lru_skips_in_use() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
cache.insert(1, 10);
cache.insert(2, 20);
let held = cache.entry(1);
assert_eq!(cache.pop_lru(), Some((2, 20)));
assert_eq!(cache.pop_lru(), None); drop(held);
assert_eq!(cache.pop_lru(), Some((1, 10)));
}
#[test]
fn test_lru_pop_lru_skips_empty_entry() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
let held = cache.entry(1);
assert_eq!(cache.pop_lru(), None);
drop(held);
assert_eq!(cache.pop_lru(), None);
assert!(cache.is_empty());
}
#[test]
fn test_lru_pop_lru_skips_unheld_entry_without_value() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
cache.insert(1, 10); cache.insert(2, 20); make_valueless(&cache, 1);
assert_eq!(cache.pop_lru(), Some((2, 20)));
assert!(cache.is_empty()); assert_eq!(cache.pop_lru(), None);
cache.insert(3, 30);
make_valueless(&cache, 3);
assert_eq!(cache.pop_lru(), None);
assert!(cache.is_empty());
}
#[test]
fn test_lru_pop_lru_multi_shard() {
let cache = LruLockMap::<u32, u32>::with_options(100, 0, 4);
for i in 0..10 {
cache.insert(i, i * 10);
}
let mut popped = std::collections::BTreeSet::new();
while let Some((k, v)) = cache.pop_lru() {
assert_eq!(v, k * 10);
assert!(popped.insert(k), "duplicate key {k}");
}
assert_eq!(popped.len(), 10);
assert!(cache.is_empty());
}
#[test]
fn test_lru_pop_lru_concurrent() {
let cache = Arc::new(LruLockMap::<u32, u32>::with_options(1 << 16, 0, 4));
#[cfg(not(miri))]
const N: u32 = 1 << 10;
#[cfg(miri)]
const N: u32 = 1 << 5;
const M: usize = 4;
for i in 0..N {
cache.insert(i, i);
}
let total = Arc::new(AtomicU32::new(0));
let threads: Vec<_> = (0..M)
.map(|_| {
let cache = cache.clone();
let total = total.clone();
std::thread::spawn(move || {
while let Some((k, v)) = cache.pop_lru() {
assert_eq!(k, v);
total.fetch_add(1, Ordering::AcqRel);
}
})
})
.collect();
threads.into_iter().for_each(|t| t.join().unwrap());
assert_eq!(total.load(Ordering::Acquire), N);
assert!(cache.is_empty());
}
#[test]
fn test_lru_custom_hasher() {
use std::collections::hash_map::RandomState as StdRandomState;
let cache = LruLockMap::<String, u32, _>::with_hasher(100, StdRandomState::new());
cache.insert("a".to_string(), 1);
assert_eq!(cache.get("a"), Some(1));
{
let mut entry = cache.entry("b".to_string());
entry.insert(2);
}
assert_eq!(cache.remove("b"), Some(2));
let cache =
LruLockMap::<u32, u32, _>::with_options_and_hasher(3, 3, 1, StdRandomState::new());
for i in 0..5 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 3);
let cache: LruLockMap<u32, u32, StdRandomState> = LruLockMap::default();
cache.insert(1, 1);
assert!(cache.contains_key(&1));
}
#[test]
fn test_lru_for_each_does_not_promote() {
let cache = LruLockMap::<u32, u32>::with_options(3, 3, 1);
cache.insert(1, 10);
cache.insert(2, 20);
cache.insert(3, 30);
let mut sum = 0;
cache.for_each(|_, v| sum += *v);
assert_eq!(sum, 60);
cache.insert(4, 40);
assert_eq!(cache.peek(&1), None);
assert_eq!(cache.peek(&2), Some(20));
}
#[test]
fn test_lru_retain() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
for i in 0..10 {
cache.insert(i, i);
}
cache.retain(|_, v| *v % 2 == 0);
assert_eq!(cache.len(), 5);
for i in 10..18 {
cache.insert(i, i);
}
assert_eq!(cache.len(), 10);
let mut popped = 0;
while cache.pop_lru().is_some() {
popped += 1;
}
assert_eq!(popped, 10);
assert!(cache.is_empty());
}
#[test]
fn test_lru_retain_with_held_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 1);
let mut held = cache.entry(2);
held.insert(2);
let retainer = {
let cache = cache.clone();
std::thread::spawn(move || cache.retain(|_, v| *v % 2 == 0))
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
retainer.join().unwrap();
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), Some(2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_lru_retain_removes_held_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(2, 2);
let mut held = cache.entry(1);
held.insert(1);
let retainer = {
let cache = cache.clone();
std::thread::spawn(move || cache.retain(|_, v| *v % 2 == 0))
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
retainer.join().unwrap();
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), Some(2));
assert_eq!(cache.len(), 1);
}
#[test]
fn test_lru_retain_held_entry_without_value() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 1);
let held = cache.entry(2);
let calls = Arc::new(AtomicU32::new(0));
let retainer = {
let cache = cache.clone();
let calls = calls.clone();
std::thread::spawn(move || {
cache.retain(|_, _| {
calls.fetch_add(1, Ordering::AcqRel);
false
})
})
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
retainer.join().unwrap();
assert_eq!(calls.load(Ordering::Acquire), 1);
assert!(cache.is_empty());
}
#[test]
fn test_lru_retain_unheld_entry_without_value() {
let cache = LruLockMap::<u32, u32>::with_options(10, 10, 1);
cache.insert(1, 1);
cache.insert(2, 2);
make_valueless(&cache, 1);
let mut visited = Vec::new();
cache.for_each(|k, v| visited.push((*k, *v)));
assert_eq!(visited, vec![(2, 2)]);
cache.retain(|k, _| {
assert_ne!(*k, 1, "valueless entry must not be visited");
true
});
assert_eq!(cache.len(), 1);
assert_eq!(cache.peek(&1), None);
assert_eq!(cache.peek(&2), Some(2));
assert_eq!(cache.pop_lru(), Some((2, 2)));
assert_eq!(cache.pop_lru(), None);
assert!(cache.is_empty());
}
#[test]
fn test_lru_for_each_with_held_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 10);
let mut held = cache.entry(2);
held.insert(20);
let visitor = {
let cache = cache.clone();
std::thread::spawn(move || {
let mut visited = Vec::new();
cache.for_each(|k, v| visited.push((*k, *v)));
visited.sort();
visited
})
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
assert_eq!(visitor.join().unwrap(), vec![(1, 10), (2, 20)]);
}
#[test]
fn test_lru_for_each_held_entry_without_value() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 10);
let held = cache.entry(2);
let visitor = {
let cache = cache.clone();
std::thread::spawn(move || {
let mut visited = Vec::new();
cache.for_each(|k, v| visited.push((*k, *v)));
visited
})
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
assert_eq!(visitor.join().unwrap(), vec![(1, 10)]);
}
#[test]
fn test_lru_or_insert() {
let cache = LruLockMap::<&str, u32>::new(100);
*cache.entry("counter").or_insert(0) += 1;
*cache.entry("counter").or_insert(0) += 1;
assert_eq!(cache.get("counter"), Some(2));
let cache = LruLockMap::<&str, Vec<u32>>::new(100);
cache.entry("list").or_insert_with(Vec::new).push(1);
cache.entry("list").or_insert_with(Vec::new).push(2);
assert_eq!(cache.get("list"), Some(vec![1, 2]));
}
#[test]
fn test_lru_clear_with_held_entry() {
let cache = Arc::new(LruLockMap::<u32, u32>::new(100));
cache.insert(1, 10);
let mut held = cache.entry(2);
held.insert(20);
let cleaner = {
let cache = cache.clone();
std::thread::spawn(move || cache.clear())
};
std::thread::sleep(std::time::Duration::from_millis(10));
drop(held);
cleaner.join().unwrap();
assert!(cache.is_empty());
assert_eq!(cache.get(&1), None);
assert_eq!(cache.get(&2), None);
}
}