#![doc = include_str!("../README.md")]
#![deny(missing_docs)]
#![allow(clippy::too_many_arguments, clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, allow(unused_attributes))]
mod bbloom;
mod cache;
mod error;
mod histogram;
mod metrics;
#[allow(dead_code)]
mod policy;
mod ring;
mod sketch;
mod store;
mod ttl;
pub(crate) mod utils;
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub(crate) mod axync {
pub(crate) use async_channel::{Receiver, Sender, bounded, unbounded};
pub(crate) use futures::{channel::oneshot, select};
pub(crate) struct Waiter(oneshot::Sender<()>);
impl Waiter {
pub(crate) fn new() -> (Self, oneshot::Receiver<()>) {
let (tx, rx) = oneshot::channel();
(Waiter(tx), rx)
}
pub(crate) fn done(self) {
let _ = self.0.send(());
}
}
pub(crate) fn stop_channel() -> (Sender<()>, Receiver<()>) {
bounded(1)
}
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use cache::{AsyncCache, AsyncCacheBuilder};
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub use agnostic_lite::tokio::TokioRuntime;
#[cfg(feature = "smol")]
#[cfg_attr(docsrs, doc(cfg(feature = "smol")))]
pub use agnostic_lite::smol::SmolRuntime;
#[cfg(all(feature = "async", feature = "tokio"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", feature = "tokio"))))]
pub type TokioCache<
K,
V,
KH = DefaultKeyBuilder<K>,
C = DefaultCoster<V>,
U = DefaultUpdateValidator<V>,
CB = DefaultCacheCallback<V>,
S = std::collections::hash_map::RandomState,
> = AsyncCache<K, V, agnostic_lite::tokio::TokioRuntime, KH, C, U, CB, S>;
#[cfg(all(feature = "async", feature = "smol"))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "async", feature = "smol"))))]
pub type SmolCache<
K,
V,
KH = DefaultKeyBuilder<K>,
C = DefaultCoster<V>,
U = DefaultUpdateValidator<V>,
CB = DefaultCacheCallback<V>,
S = std::collections::hash_map::RandomState,
> = AsyncCache<K, V, agnostic_lite::smol::SmolRuntime, KH, C, U, CB, S>;
#[cfg(feature = "sync")]
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub(crate) mod sync {
pub(crate) use crossbeam_channel::{Receiver, Sender, bounded, select};
pub(crate) use std::{
thread::{JoinHandle, spawn},
time::Instant,
};
pub(crate) type WaitGroup = wg::WaitGroup;
pub(crate) struct Signal(Option<WaitGroup>);
impl Signal {
#[inline]
pub(crate) fn new(wg: WaitGroup) -> Self {
Self(Some(wg))
}
#[inline]
pub(crate) fn done(mut self) {
if let Some(wg) = self.0.take() {
wg.done();
}
}
}
impl Drop for Signal {
fn drop(&mut self) {
if let Some(wg) = self.0.take() {
wg.done();
}
}
}
pub(crate) fn stop_channel() -> (Sender<()>, Receiver<()>) {
bounded(0)
}
}
#[cfg(feature = "sync")]
#[cfg_attr(docsrs, doc(cfg(feature = "sync")))]
pub use cache::{Cache, CacheBuilder};
pub use error::CacheError;
pub use histogram::Histogram;
pub use metrics::{MetricType, Metrics};
pub use utils::{ValueRef, ValueRefMut};
use crate::ttl::Time;
use seahash::SeaHasher;
use std::{
fmt::{Debug, Formatter},
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
marker::PhantomData,
};
pub struct Item<V> {
pub val: Option<V>,
pub index: u64,
pub conflict: u64,
pub cost: i64,
pub exp: Time,
}
impl<V: Clone> Clone for Item<V> {
fn clone(&self) -> Self {
Self {
val: self.val.clone(),
index: self.index,
conflict: self.conflict,
cost: self.cost,
exp: self.exp,
}
}
}
impl<V: Copy> Copy for Item<V> {}
impl<V: Debug> Debug for Item<V> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Item")
.field("value", &self.val)
.field("cost", &self.cost)
.field("ttl", &self.exp)
.finish()
}
}
pub trait UpdateValidator: Send + Sync + 'static {
type Value: Send + Sync + 'static;
fn should_update(&self, prev: &Self::Value, curr: &Self::Value) -> bool;
}
#[doc(hidden)]
pub struct DefaultUpdateValidator<V> {
_marker: PhantomData<fn(V)>,
}
impl<V: Send + Sync> Default for DefaultUpdateValidator<V> {
fn default() -> Self {
Self {
_marker: PhantomData::<fn(V)>,
}
}
}
impl<V: Send + Sync + 'static> UpdateValidator for DefaultUpdateValidator<V> {
type Value = V;
#[cfg_attr(not(tarpaulin), inline(always))]
fn should_update(&self, _prev: &Self::Value, _curr: &Self::Value) -> bool {
true
}
}
pub trait CacheCallback: Send + Sync + 'static {
type Value: Send + Sync + 'static;
fn on_exit(&self, val: Option<Self::Value>);
fn on_evict(&self, item: Item<Self::Value>) {
self.on_exit(item.val)
}
fn on_reject(&self, item: Item<Self::Value>) {
self.on_exit(item.val)
}
}
#[derive(Clone, Debug)]
#[doc(hidden)]
pub struct DefaultCacheCallback<V> {
_marker: PhantomData<V>,
}
impl<V> Default for DefaultCacheCallback<V> {
fn default() -> Self {
Self {
_marker: Default::default(),
}
}
}
impl<V: Send + Sync + 'static> CacheCallback for DefaultCacheCallback<V> {
type Value = V;
fn on_exit(&self, _val: Option<Self::Value>) {}
}
pub trait Coster: Send + Sync + 'static {
type Value: Send + Sync + 'static;
fn cost(&self, val: &Self::Value) -> i64;
}
pub struct DefaultCoster<V> {
_marker: PhantomData<fn(V)>,
}
impl<V> Default for DefaultCoster<V> {
fn default() -> Self {
Self {
_marker: Default::default(),
}
}
}
impl<V: Send + Sync + 'static> Coster for DefaultCoster<V> {
type Value = V;
#[cfg_attr(not(tarpaulin), inline(always))]
fn cost(&self, _val: &V) -> i64 {
0
}
}
pub trait KeyBuilder {
type Key: Hash + Eq + ?Sized;
fn hash_index<Q>(&self, key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized;
fn hash_conflict<Q>(&self, key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
let _ = key;
0
}
fn build_key<Q>(&self, k: &Q) -> (u64, u64)
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
(self.hash_index(k), self.hash_conflict(k))
}
}
pub struct DefaultKeyBuilder<K> {
xx: xxhash_rust::xxh64::Xxh64Builder,
sea: BuildHasherDefault<SeaHasher>,
_marker: PhantomData<K>,
}
impl<K> Debug for DefaultKeyBuilder<K> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DefaultKeyBuilder").finish()
}
}
impl<K> Default for DefaultKeyBuilder<K> {
fn default() -> Self {
let seed = rand::random::<u64>();
Self {
xx: xxhash_rust::xxh64::Xxh64Builder::new(seed),
sea: Default::default(),
_marker: Default::default(),
}
}
}
impl<K: Hash + Eq> KeyBuilder for DefaultKeyBuilder<K> {
type Key = K;
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash_index<Q>(&self, key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
self.sea.hash_one(key)
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash_conflict<Q>(&self, key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
self.xx.hash_one(key)
}
}
#[derive(Default, Copy, Clone, Eq, PartialEq, Debug)]
#[repr(transparent)]
pub struct TransparentHasher {
data: u64,
}
impl Hasher for TransparentHasher {
#[cfg_attr(not(tarpaulin), inline(always))]
fn finish(&self) -> u64 {
self.data
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn write(&mut self, bytes: &[u8]) {
let mut data = [0u8; core::mem::size_of::<u64>()];
if bytes.len() > core::mem::size_of::<u64>() {
data.copy_from_slice(&bytes[..core::mem::size_of::<u64>()]);
} else {
data[..bytes.len()].copy_from_slice(bytes);
}
self.data = u64::from_ne_bytes(data);
}
fn write_u8(&mut self, i: u8) {
self.data = i as u64;
}
fn write_u16(&mut self, i: u16) {
self.data = i as u64;
}
fn write_u32(&mut self, i: u32) {
self.data = i as u64;
}
fn write_u64(&mut self, i: u64) {
self.data = i;
}
fn write_u128(&mut self, i: u128) {
self.data = i as u64;
}
fn write_usize(&mut self, i: usize) {
self.data = i as u64;
}
fn write_i8(&mut self, i: i8) {
self.data = i as u64;
}
fn write_i16(&mut self, i: i16) {
self.data = i as u64;
}
fn write_i32(&mut self, i: i32) {
self.data = i as u64;
}
fn write_i64(&mut self, i: i64) {
self.data = i as u64;
}
fn write_i128(&mut self, i: i128) {
self.data = i as u64;
}
fn write_isize(&mut self, i: isize) {
self.data = i as u64;
}
}
pub trait TransparentKey: Hash + Eq {
fn to_u64(&self) -> u64;
}
#[derive(Default, Clone, Eq, PartialEq, Debug)]
pub struct TransparentKeyBuilder<K: TransparentKey> {
_marker: PhantomData<K>,
}
impl<K: TransparentKey> KeyBuilder for TransparentKeyBuilder<K> {
type Key = K;
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash_index<Q>(&self, key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
let mut hasher = TransparentHasher { data: 0 };
key.hash(&mut hasher);
hasher.finish()
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn hash_conflict<Q>(&self, _key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
0
}
}
macro_rules! impl_transparent_key {
($($t:ty),*) => {
$(
impl TransparentKey for $t {
fn to_u64(&self) -> u64 {
*self as u64
}
}
)*
}
}
impl_transparent_key! {
bool,
u8,
u16,
u32,
u64,
usize,
i8,
i16,
i32,
i64,
isize
}
#[cfg(test)]
mod lib_tests {
use super::*;
#[test]
fn test_item_clone_and_debug() {
let item: Item<u64> = Item {
val: Some(42),
index: 1,
conflict: 2,
cost: 3,
exp: Time::now(),
};
#[allow(clippy::clone_on_copy)]
let cloned = item.clone();
assert_eq!(cloned.val, Some(42));
assert_eq!(cloned.index, 1);
assert_eq!(cloned.conflict, 2);
assert_eq!(cloned.cost, 3);
let s = format!("{:?}", item);
assert!(s.contains("Item"));
assert!(s.contains("42"));
}
#[test]
fn test_default_key_builder_debug() {
let kb = DefaultKeyBuilder::<u64>::default();
let s = format!("{:?}", kb);
assert!(s.contains("DefaultKeyBuilder"));
}
#[test]
fn test_transparent_hasher_write_variants() {
let mut h = TransparentHasher::default();
h.write_u8(7);
assert_eq!(h.finish(), 7);
h.write_u16(8);
assert_eq!(h.finish(), 8);
h.write_u32(9);
assert_eq!(h.finish(), 9);
h.write_u64(10);
assert_eq!(h.finish(), 10);
h.write_u128(11);
assert_eq!(h.finish(), 11);
h.write_usize(12);
assert_eq!(h.finish(), 12);
h.write_i8(-1);
assert_eq!(h.finish(), u64::MAX);
h.write_i16(-2);
h.write_i32(-3);
h.write_i64(-4);
h.write_i128(-5);
h.write_isize(-6);
let mut h2 = TransparentHasher::default();
h2.write(&[1, 2, 3, 4, 5, 6, 7, 8, 9]);
let mut h3 = TransparentHasher::default();
h3.write(&[1, 2, 3]);
}
#[test]
fn test_transparent_key_to_u64_all_types() {
assert_eq!(true.to_u64(), 1);
assert_eq!(false.to_u64(), 0);
assert_eq!(1u8.to_u64(), 1);
assert_eq!(1u16.to_u64(), 1);
assert_eq!(1u32.to_u64(), 1);
assert_eq!(1u64.to_u64(), 1);
assert_eq!(1usize.to_u64(), 1);
assert_eq!(1i8.to_u64(), 1);
assert_eq!(1i16.to_u64(), 1);
assert_eq!(1i32.to_u64(), 1);
assert_eq!(1i64.to_u64(), 1);
assert_eq!(1isize.to_u64(), 1);
}
#[test]
fn test_key_builder_default_methods() {
struct MinimalKB;
impl KeyBuilder for MinimalKB {
type Key = u64;
fn hash_index<Q>(&self, _key: &Q) -> u64
where
Self::Key: core::borrow::Borrow<Q>,
Q: core::hash::Hash + Eq + ?Sized,
{
7
}
}
let kb = MinimalKB;
assert_eq!(kb.hash_conflict(&1u64), 0);
assert_eq!(kb.build_key(&1u64), (7, 0));
}
#[test]
fn test_default_update_validator_coster_callback() {
let uv = DefaultUpdateValidator::<u64>::default();
assert!(uv.should_update(&1, &2));
let coster = DefaultCoster::<u64>::default();
assert_eq!(coster.cost(&100), 0);
let cb = DefaultCacheCallback::<u64>::default();
cb.on_exit(Some(1));
let item: Item<u64> = Item {
val: Some(1),
index: 0,
conflict: 0,
cost: 0,
exp: Time::now(),
};
#[allow(clippy::clone_on_copy)]
cb.on_evict(item.clone());
cb.on_reject(item);
let s = format!("{:?}", cb);
assert!(s.contains("DefaultCacheCallback"));
}
}