use std::{
alloc::{alloc, dealloc, Layout},
borrow::Borrow,
hash::Hash,
iter::FusedIterator,
marker::PhantomData,
mem::ManuallyDrop,
time::Duration
};
use hashbrown::raw::{RawIter, RawTable};
pub type DefaultHashBuilder = ahash::RandomState;
#[cfg(not(feature = "sn_fake_clock"))]
use std::time::Instant;
#[cfg(feature = "sn_fake_clock")]
use sn_fake_clock::FakeClock as Instant;
#[cfg_attr(feature = "inline-more", inline)]
pub(crate) fn make_hasher<K, Q, V>(
hash_builder: &DefaultHashBuilder
) -> impl Fn(&(Q, V)) -> u64 + '_
where
K: Borrow<Q>,
Q: Hash
{
move |val| make_hash::<K, Q>(hash_builder, &val.0)
}
#[cfg_attr(feature = "inline-more", inline)]
#[allow(clippy::extra_unused_type_parameters)]
pub(crate) fn make_hash<K, Q>(
hash_builder: &DefaultHashBuilder,
val: &Q
) -> u64
where
K: Borrow<Q>,
Q: Hash + ?Sized
{
hash_builder.hash_one(val)
}
#[cfg_attr(feature = "inline-more", inline)]
fn equivalent_key<Q, K, V>(k: &Q) -> impl Fn(&(K, V)) -> bool + '_
where
K: Borrow<Q>,
Q: ?Sized + Eq
{
move |x| k.eq(x.0.borrow())
}
#[cfg_attr(feature = "inline-more", inline)]
pub(crate) fn make_insert_hash<K>(
hash_builder: &DefaultHashBuilder,
val: &K
) -> u64
where
K: Hash
{
hash_builder.hash_one(val)
}
struct LruNode<V> {
next: *mut LruNode<V>,
prev: *mut LruNode<V>,
timeout: Instant,
keyhash: u64,
value: ManuallyDrop<V>
}
struct InnerLruMap<K, V>
where
K: Eq + Hash
{
hash_builder: DefaultHashBuilder,
table: RawTable<(K, *mut LruNode<V>)>,
marker: PhantomData<LruNode<V>>,
lru_head: *mut LruNode<V>,
lru_tail: *mut LruNode<V>
}
impl<K, V> InnerLruMap<K, V>
where
K: Eq + Hash
{
fn unlink_node(&mut self, n: *mut LruNode<V>) {
if n == self.lru_head {
self.lru_head = unsafe { (*self.lru_head).next };
if self.lru_head.is_null() {
self.lru_tail = std::ptr::null_mut();
} else {
unsafe {
(*self.lru_head).prev = std::ptr::null_mut();
}
}
return;
}
if n == self.lru_tail {
self.lru_tail = unsafe { (*self.lru_tail).prev };
unsafe {
(*self.lru_tail).next = std::ptr::null_mut();
}
return;
}
unsafe {
(*(*n).prev).next = (*n).next;
(*(*n).next).prev = (*n).prev;
}
}
fn move_to_front(&mut self, n: *mut LruNode<V>) {
if n == self.lru_head {
return;
}
if n == self.lru_tail {
unsafe {
self.lru_tail = (*n).prev;
(*self.lru_tail).next = std::ptr::null_mut();
(*n).next = self.lru_head;
(*(*n).next).prev = n;
self.lru_head = n;
(*n).prev = std::ptr::null_mut();
}
return;
}
unsafe {
(*(*n).prev).next = (*n).next;
(*(*n).next).prev = (*n).prev;
(*self.lru_head).prev = n;
(*n).next = self.lru_head;
(*n).prev = std::ptr::null_mut();
self.lru_head = n;
}
}
fn remove_by_node_ptr(
&mut self,
node: *mut LruNode<V>
) -> Option<(K, *mut LruNode<V>)> {
let hash = unsafe { (*node).keyhash };
match self.table.find(hash, |(_k, n)| node == *n) {
Some(bucket) => {
self.unlink_node(node);
Some(unsafe { self.table.remove(bucket).0 })
}
None => None
}
}
#[cfg_attr(feature = "inline-more", inline)]
fn remove<Q>(&mut self, k: &Q) -> Option<(K, *mut LruNode<V>)>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
let hash = make_hash::<K, Q>(&self.hash_builder, k);
if let Some((k, n)) = self.table.remove_entry(hash, equivalent_key(k)) {
self.unlink_node(n);
Some((k, n))
} else {
None
}
}
}
pub struct LruMap<K, V>
where
K: Eq + Hash
{
inner: InnerLruMap<K, V>,
ttl: Option<Duration>
}
#[expect(clippy::non_send_fields_in_send_ty)]
unsafe impl<K, V> Send for LruMap<K, V> where K: Hash + Eq {}
unsafe impl<K, V> Sync for LruMap<K, V> where K: Hash + Eq {}
impl<K, V> LruMap<K, V>
where
K: Eq + Hash
{
#[must_use]
pub fn new() -> Self {
Self {
inner: InnerLruMap {
hash_builder: DefaultHashBuilder::default(),
table: RawTable::new(),
marker: PhantomData,
lru_head: std::ptr::null_mut(),
lru_tail: std::ptr::null_mut()
},
ttl: None
}
}
#[must_use]
pub fn with_ttl(dur: Duration) -> Self {
Self {
inner: InnerLruMap {
hash_builder: DefaultHashBuilder::default(),
table: RawTable::new(),
marker: PhantomData,
lru_head: std::ptr::null_mut(),
lru_tail: std::ptr::null_mut()
},
ttl: Some(dur)
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.table.len()
}
pub fn clear(&mut self) {
self.inner.table.clear();
self.clear_lru_chain();
}
fn clear_lru_chain(&mut self) {
let mut head = self.inner.lru_head;
self.inner.lru_head = std::ptr::null_mut();
self.inner.lru_tail = std::ptr::null_mut();
while !head.is_null() {
let next = unsafe { (*head).next };
drop_node(head);
head = next;
}
}
pub fn insert(&mut self, key: K, value: V) -> Option<V> {
let croaktime = self
.ttl
.map_or_else(Instant::now, |ttl| Instant::now() + ttl);
if let Some((_k, n)) = self.get_inner_mut(&key) {
let n = *n;
self.inner.move_to_front(n);
let v = unsafe {
let v = ManuallyDrop::take(&mut (*n).value);
(*n).value = ManuallyDrop::new(value);
(*n).timeout = croaktime;
v
};
Some(v)
} else {
let hash = make_insert_hash(&self.inner.hash_builder, &key);
let layout = Layout::new::<LruNode<V>>();
let ptr = unsafe { alloc(layout) };
let p = ptr.cast::<LruNode<V>>();
unsafe {
(*p).keyhash = hash;
if self.inner.lru_head.is_null() {
(*p).next = std::ptr::null_mut();
self.inner.lru_head = p;
self.inner.lru_tail = p;
} else {
(*p).next = self.inner.lru_head;
(*(*p).next).prev = p;
self.inner.lru_head = p;
}
(*p).prev = std::ptr::null_mut();
(*p).timeout = croaktime;
(*p).value = ManuallyDrop::new(value);
}
self.inner.table.insert(
hash,
(key, p),
make_hasher::<K, _, *mut LruNode<V>>(&self.inner.hash_builder)
);
None
}
}
fn touch_node(&mut self, n: *mut LruNode<V>) {
if let Some(ttl) = self.ttl {
let croaktime = Instant::now() + ttl;
unsafe {
(*n).timeout = croaktime;
}
}
self.inner.move_to_front(n);
}
#[inline]
fn get_inner<Q>(&self, k: &Q) -> Option<&(K, *mut LruNode<V>)>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
if self.inner.table.is_empty() {
None
} else {
let hash = make_hash::<K, Q>(&self.inner.hash_builder, k);
self.inner.table.get(hash, equivalent_key(k))
}
}
#[inline]
pub fn get<Q>(&mut self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
match self.get_inner(k) {
Some((_, n)) => {
let n = unsafe { &mut *(*n) };
self.touch_node(n);
Some(&n.value)
}
None => None
}
}
#[cfg_attr(feature = "inline-more", inline)]
pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
match self.get_inner_mut(k) {
Some(&mut (_, ref mut v)) => {
let n = unsafe { &mut *(*v) };
self.touch_node(n);
Some(&mut n.value)
}
None => None
}
}
#[inline]
fn get_inner_mut<Q>(&mut self, k: &Q) -> Option<&mut (K, *mut LruNode<V>)>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
if self.is_empty() {
None
} else {
let hash = make_hash::<K, Q>(&self.inner.hash_builder, k);
self.inner.table.get_mut(hash, equivalent_key(k))
}
}
pub fn remove<Q>(&mut self, k: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
if let Some((_k, n)) = self.inner.remove(k) {
Some(into_node_value(n))
} else {
None
}
}
pub fn touch<Q>(&mut self, k: &Q) -> bool
where
K: Borrow<Q>,
Q: ?Sized + Hash + Eq
{
self.get(k).is_some()
}
pub fn pop(&mut self) -> Option<(K, V)> {
if self.is_empty() {
return None;
}
self
.inner
.remove_by_node_ptr(self.inner.lru_tail)
.map(|(k, n)| (k, into_node_value(n)))
}
pub fn pop_youngest(&mut self) -> Option<(K, V)> {
if self.is_empty() {
return None;
}
self
.inner
.remove_by_node_ptr(self.inner.lru_head)
.map(|(k, n)| (k, into_node_value(n)))
}
#[must_use]
pub fn youngest(&self) -> Option<(&K, &V)> {
let hash = unsafe { (*self.inner.lru_head).keyhash };
self.lookup_key(hash, self.inner.lru_head).map(|k| {
let v: &V = unsafe { &(*self.inner.lru_head).value };
(k, v)
})
}
#[must_use]
pub fn oldest(&self) -> Option<(&K, &V)> {
let hash = unsafe { (*self.inner.lru_tail).keyhash };
self.lookup_key(hash, self.inner.lru_tail).map(|k| {
let v: &V = unsafe { &(*self.inner.lru_tail).value };
(k, v)
})
}
fn lookup_key(&self, hash: u64, node: *mut LruNode<V>) -> Option<&K> {
match self.inner.table.get(hash, |(_, n)| &node == n) {
Some((key, _)) => Some(key),
None => None
}
}
#[must_use]
pub const fn iter(&self) -> Iter<'_, K, V> {
Iter {
map: self,
node: self.inner.lru_head
}
}
pub const fn iter_mut(&mut self) -> IterMut<'_, K, V> {
IterMut {
map: self,
node: self.inner.lru_head,
marker: PhantomData
}
}
pub fn drain(&mut self) -> Drain<'_, K, V> {
Drain {
iter: unsafe { self.inner.table.iter() },
inner_map: &mut self.inner
}
}
pub const fn drain_ordered(&mut self) -> DrainOrdered<'_, K, V> {
DrainOrdered {
inner_map: &mut self.inner
}
}
#[cfg_attr(feature = "inline-more", inline)]
pub fn drain_filter<F>(&mut self, f: F) -> DrainFilter<'_, K, V, F>
where
F: FnMut(&K, &mut V) -> bool
{
DrainFilter {
f,
iter: unsafe { self.inner.table.iter() },
inner_map: &mut self.inner
}
}
pub fn drain_timedout(&mut self) -> DrainTimedout<'_, K, V> {
DrainTimedout {
now: Instant::now(),
inner_map: &mut self.inner
}
}
pub fn drain_overflow(&mut self, lim: usize) -> DrainOverflow<'_, K, V> {
let nodes = self.len();
let remain = nodes.saturating_sub(lim);
DrainOverflow {
remain,
inner_map: &mut self.inner
}
}
pub fn drain_old(&mut self, cap: Option<usize>) -> DrainOld<'_, K, V> {
let nodes = self.len();
let remain = cap.map_or(0, |lim| nodes.saturating_sub(lim));
DrainOld {
now: Instant::now(),
remain,
inner_map: &mut self.inner
}
}
pub fn remove_old(&mut self, cap: Option<usize>) {
let nodes = self.len();
let remain = cap.map_or(0, |lim| nodes.saturating_sub(lim));
let drain = DrainOld {
now: Instant::now(),
remain,
inner_map: &mut self.inner
};
for _ in drain {}
}
}
impl<K, V> Default for LruMap<K, V>
where
K: Eq + Hash
{
fn default() -> Self {
Self::new()
}
}
impl<K, V> Drop for LruMap<K, V>
where
K: Eq + Hash
{
fn drop(&mut self) {
self.clear_lru_chain();
}
}
fn drop_node<T>(n: *mut LruNode<T>) {
let layout = Layout::new::<LruNode<T>>();
unsafe {
if std::mem::needs_drop::<T>() {
ManuallyDrop::drop(&mut (*n).value);
}
let p = n.cast::<u8>();
dealloc(p, layout);
}
}
fn into_node_value<T>(n: *mut LruNode<T>) -> T {
let v = unsafe { ManuallyDrop::take(&mut (*n).value) };
let p = n.cast::<u8>();
let layout = Layout::new::<LruNode<T>>();
unsafe {
dealloc(p, layout);
}
v
}
impl<'a, K, V> IntoIterator for &'a LruMap<K, V>
where
K: Eq + Hash
{
type Item = (&'a K, &'a V);
type IntoIter = Iter<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a, K, V> IntoIterator for &'a mut LruMap<K, V>
where
K: Eq + Hash
{
type Item = (&'a K, &'a mut V);
type IntoIter = IterMut<'a, K, V>;
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}
impl<K, V> IntoIterator for LruMap<K, V>
where
K: Eq + Hash
{
type Item = (K, V);
type IntoIter = IntoIter<K, V>;
fn into_iter(self) -> Self::IntoIter {
IntoIter { inner: self }
}
}
pub struct IntoIter<K, V>
where
K: Eq + Hash
{
inner: LruMap<K, V>
}
impl<K, V> Iterator for IntoIter<K, V>
where
K: Eq + Hash
{
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
self.inner.pop_youngest()
}
}
pub struct Iter<'a, K, V>
where
K: Eq + Hash
{
map: &'a LruMap<K, V>,
node: *mut LruNode<V>
}
impl<'a, K, V> Iterator for Iter<'a, K, V>
where
K: Eq + Hash
{
type Item = (&'a K, &'a V);
fn next(&mut self) -> Option<Self::Item> {
if self.node.is_null() {
return None;
}
unsafe {
let current_node = &*self.node;
self.node = current_node.next;
self
.map
.lookup_key(
current_node.keyhash,
std::ptr::from_ref(current_node).cast_mut()
)
.map(|key| (key, &*current_node.value))
}
}
}
pub struct IterMut<'a, K, V>
where
K: Eq + Hash
{
map: &'a LruMap<K, V>,
node: *mut LruNode<V>,
marker: PhantomData<&'a mut LruMap<K, V>>
}
impl<'a, K, V> Iterator for IterMut<'a, K, V>
where
K: Eq + Hash
{
type Item = (&'a K, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
if self.node.is_null() {
return None;
}
unsafe {
let current_node = &mut *self.node;
self.node = current_node.next;
self
.map
.lookup_key(
current_node.keyhash,
std::ptr::from_mut(current_node).cast()
)
.map(|key| (key, &mut *current_node.value))
}
}
}
pub struct Drain<'a, K, V>
where
K: Eq + Hash
{
iter: RawIter<(K, *mut LruNode<V>)>,
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V> Iterator for Drain<'_, K, V>
where
K: Eq + Hash
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
(self.iter).next().map(|item| {
let (k, n) = self.inner_map.table.remove(item).0;
self.inner_map.unlink_node(n);
(k, into_node_value(n))
})
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.iter.size_hint().1)
}
}
impl<K, V> Drop for Drain<'_, K, V>
where
K: Eq + Hash
{
#[cfg_attr(feature = "inline-more", inline)]
fn drop(&mut self) {
for (_k, _v) in self.by_ref() {}
}
}
impl<K, V> FusedIterator for Drain<'_, K, V> where K: Eq + Hash {}
pub struct DrainFilter<'a, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool
{
f: F,
iter: RawIter<(K, *mut LruNode<V>)>,
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V, F> Iterator for DrainFilter<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
for item in &mut self.iter {
let &mut (ref key, ref mut value) = item.as_mut();
if (self.f)(key, &mut (*(*value)).value) {
let (k, n) = self.inner_map.table.remove(item).0;
self.inner_map.unlink_node(n);
return Some((k, into_node_value(n)));
}
}
}
None
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(0, self.iter.size_hint().1)
}
}
impl<K, V, F> Drop for DrainFilter<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool
{
#[cfg_attr(feature = "inline-more", inline)]
fn drop(&mut self) {
for (_k, _v) in self.by_ref() {}
}
}
impl<K, V, F> FusedIterator for DrainFilter<'_, K, V, F>
where
K: Eq + Hash,
F: FnMut(&K, &mut V) -> bool
{
}
pub struct DrainTimedout<'a, K, V>
where
K: Eq + Hash
{
now: Instant,
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V> Iterator for DrainTimedout<'_, K, V>
where
K: Eq + Hash
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
unsafe {
let n = self.inner_map.lru_tail;
if n.is_null() {
return None;
}
if self.now >= (*n).timeout {
self
.inner_map
.remove_by_node_ptr(n)
.map(|(k, n)| (k, into_node_value(n)))
} else {
None
}
}
}
}
impl<K, V> Drop for DrainTimedout<'_, K, V>
where
K: Eq + Hash
{
fn drop(&mut self) {
loop {
let n = self.inner_map.lru_tail;
if n.is_null() {
break;
}
unsafe {
if self.now >= (*self.inner_map.lru_tail).timeout {
if let Some((_k, n)) = self.inner_map.remove_by_node_ptr(n) {
let v = into_node_value(n);
drop(v);
}
} else {
break;
}
}
}
}
}
impl<K, V> FusedIterator for DrainTimedout<'_, K, V> where K: Eq + Hash {}
pub struct DrainOverflow<'a, K, V>
where
K: Eq + Hash
{
remain: usize,
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V> Iterator for DrainOverflow<'_, K, V>
where
K: Eq + Hash
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
if self.remain == 0 {
return None;
}
let n = self.inner_map.lru_tail;
if n.is_null() {
return None;
}
match self.inner_map.remove_by_node_ptr(n) {
Some((k, n)) => {
self.remain -= 1;
Some((k, into_node_value(n)))
}
None => None
}
}
}
impl<K, V> Drop for DrainOverflow<'_, K, V>
where
K: Eq + Hash
{
fn drop(&mut self) {
while self.remain > 0 {
let n = self.inner_map.lru_tail;
debug_assert!(!n.is_null());
if let Some((_k, n)) = self.inner_map.remove_by_node_ptr(n) {
self.remain -= 1;
let v = into_node_value(n);
drop(v);
}
}
}
}
impl<K, V> FusedIterator for DrainOverflow<'_, K, V> where K: Eq + Hash {}
pub struct DrainOrdered<'a, K, V>
where
K: Eq + Hash
{
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V> Iterator for DrainOrdered<'_, K, V>
where
K: Eq + Hash
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
let n = self.inner_map.lru_head;
if n.is_null() {
return None;
}
self
.inner_map
.remove_by_node_ptr(n)
.map(|(k, n)| (k, into_node_value(n)))
}
}
impl<K, V> Drop for DrainOrdered<'_, K, V>
where
K: Eq + Hash
{
fn drop(&mut self) {
loop {
let n = self.inner_map.lru_head;
if n.is_null() {
break;
}
if let Some((_k, n)) = self.inner_map.remove_by_node_ptr(n) {
let v = into_node_value(n);
drop(v);
}
}
}
}
impl<K, V> FusedIterator for DrainOrdered<'_, K, V> where K: Eq + Hash {}
pub struct DrainOld<'a, K, V>
where
K: Eq + Hash
{
remain: usize,
now: Instant,
inner_map: &'a mut InnerLruMap<K, V>
}
impl<K, V> Iterator for DrainOld<'_, K, V>
where
K: Eq + Hash
{
type Item = (K, V);
#[cfg_attr(feature = "inline-more", inline)]
fn next(&mut self) -> Option<Self::Item> {
let n = self.inner_map.lru_tail;
if n.is_null() {
return None;
}
if self.remain > 0 {
if let Some((k, n)) = self.inner_map.remove_by_node_ptr(n) {
self.remain -= 1;
Some((k, into_node_value(n)))
} else {
None
}
} else if self.now >= unsafe { (*n).timeout } {
self
.inner_map
.remove_by_node_ptr(n)
.map(|(k, n)| (k, into_node_value(n)))
} else {
None
}
}
}
impl<K, V> Drop for DrainOld<'_, K, V>
where
K: Eq + Hash
{
fn drop(&mut self) {
for (_k, _v) in self.by_ref() {}
}
}
impl<K, V> FusedIterator for DrainOld<'_, K, V> where K: Eq + Hash {}
#[cfg(test)]
mod tests {
use super::LruMap;
#[test]
fn replace() {
let mut lrumap = LruMap::<usize, usize>::new();
lrumap.insert(1, 2);
assert_eq!(lrumap.len(), 1);
let o = lrumap.insert(1, 3);
assert_eq!(lrumap.len(), 1);
assert_eq!(o, Some(2));
assert_eq!(lrumap.get(&1), Some(&3));
}
#[test]
fn oldest() {
let mut lrumap = LruMap::<usize, usize>::new();
lrumap.insert(1, 2);
lrumap.insert(2, 4);
lrumap.insert(3, 6);
assert_eq!(lrumap.len(), 3);
let n = lrumap.oldest();
assert_eq!(n, Some((&1, &2)));
let o = lrumap.pop();
assert_eq!(o, Some((1, 2)));
}
#[test]
fn send() {
let mut lrumap = LruMap::<usize, &str>::new();
lrumap.insert(1, "elena");
let handle = std::thread::spawn(|| {
lrumap.insert(2, "drake");
lrumap
});
let lrumap = handle.join().unwrap();
assert_eq!(lrumap.len(), 2);
}
#[test]
fn sync() {
use std::sync::Arc;
let mut lrumap = LruMap::<usize, &str>::new();
lrumap.insert(1, "chloe");
let lrumap = Arc::new(lrumap);
let _handle = std::thread::spawn({
let lrumap = lrumap.clone();
move || {
let _ = lrumap.len();
}
});
}
}