use std::time::{Duration, Instant};
use chrono::Utc;
use moka::Expiry;
use moka::future::{Cache, CacheBuilder};
use moka::policy::EvictionPolicy;
use crate::backend::MokaBackend;
use hitbox::{BackendLabel, CacheKey, CacheValue, Raw};
use hitbox_backend::format::{Format, JsonFormat};
use hitbox_backend::{CacheKeyFormat, Compressor, PassthroughCompressor};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Expiration;
impl Expiry<CacheKey, CacheValue<Raw>> for Expiration {
fn expire_after_create(
&self,
_key: &CacheKey,
value: &CacheValue<Raw>,
_created_at: Instant,
) -> Option<Duration> {
Self::calculate_ttl(value)
}
fn expire_after_update(
&self,
_key: &CacheKey,
value: &CacheValue<Raw>,
_updated_at: Instant,
_duration_until_expiry: Option<Duration>,
) -> Option<Duration> {
Self::calculate_ttl(value)
}
}
impl Expiration {
fn calculate_ttl(value: &CacheValue<Raw>) -> Option<Duration> {
value.expire().map(|expiration| {
let delta = expiration - Utc::now();
let millis = delta.num_milliseconds();
if millis <= 0 {
Duration::ZERO
} else {
Duration::from_millis(millis as u64)
}
})
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoCapacity;
#[derive(Debug, Clone, Copy)]
pub struct EntryCapacity(pub(crate) u64);
#[derive(Debug, Clone, Copy)]
pub struct ByteCapacity(pub(crate) u64);
pub struct MokaBackendBuilder<Cap, S = JsonFormat, C = PassthroughCompressor>
where
S: Format,
C: Compressor,
{
capacity: Cap,
key_format: CacheKeyFormat,
serializer: S,
compressor: C,
label: BackendLabel,
eviction_policy: Option<EvictionPolicy>,
}
impl MokaBackendBuilder<NoCapacity, JsonFormat, PassthroughCompressor> {
pub fn new() -> Self {
Self {
capacity: NoCapacity,
key_format: CacheKeyFormat::Bitcode,
serializer: JsonFormat,
compressor: PassthroughCompressor,
label: BackendLabel::new_static("moka"),
eviction_policy: None,
}
}
}
impl Default for MokaBackendBuilder<NoCapacity, JsonFormat, PassthroughCompressor> {
fn default() -> Self {
Self::new()
}
}
impl<S, C> MokaBackendBuilder<NoCapacity, S, C>
where
S: Format,
C: Compressor,
{
pub fn max_entries(self, capacity: u64) -> MokaBackendBuilder<EntryCapacity, S, C> {
MokaBackendBuilder {
capacity: EntryCapacity(capacity),
key_format: self.key_format,
serializer: self.serializer,
compressor: self.compressor,
label: self.label,
eviction_policy: self.eviction_policy,
}
}
pub fn max_bytes(self, bytes: u64) -> MokaBackendBuilder<ByteCapacity, S, C> {
MokaBackendBuilder {
capacity: ByteCapacity(bytes),
key_format: self.key_format,
serializer: self.serializer,
compressor: self.compressor,
label: self.label,
eviction_policy: self.eviction_policy,
}
}
}
impl<Cap, S, C> MokaBackendBuilder<Cap, S, C>
where
S: Format,
C: Compressor,
{
pub fn label(mut self, label: impl Into<BackendLabel>) -> Self {
self.label = label.into();
self
}
pub fn key_format(mut self, format: CacheKeyFormat) -> Self {
self.key_format = format;
self
}
pub fn eviction_policy(mut self, policy: EvictionPolicy) -> Self {
self.eviction_policy = Some(policy);
self
}
pub fn value_format<NewS>(self, serializer: NewS) -> MokaBackendBuilder<Cap, NewS, C>
where
NewS: Format,
{
MokaBackendBuilder {
capacity: self.capacity,
key_format: self.key_format,
serializer,
compressor: self.compressor,
label: self.label,
eviction_policy: self.eviction_policy,
}
}
pub fn compressor<NewC>(self, compressor: NewC) -> MokaBackendBuilder<Cap, S, NewC>
where
NewC: Compressor,
{
MokaBackendBuilder {
capacity: self.capacity,
key_format: self.key_format,
serializer: self.serializer,
compressor,
label: self.label,
eviction_policy: self.eviction_policy,
}
}
}
impl<S, C> MokaBackendBuilder<EntryCapacity, S, C>
where
S: Format,
C: Compressor,
{
pub fn build(self) -> MokaBackend<S, C> {
let policy = self
.eviction_policy
.unwrap_or_else(EvictionPolicy::tiny_lfu);
let cache: Cache<CacheKey, CacheValue<Raw>> = CacheBuilder::new(self.capacity.0)
.eviction_policy(policy)
.expire_after(Expiration)
.build();
MokaBackend {
cache,
key_format: self.key_format,
serializer: self.serializer,
compressor: self.compressor,
label: self.label,
}
}
}
impl<S, C> MokaBackendBuilder<ByteCapacity, S, C>
where
S: Format + 'static,
C: Compressor + 'static,
{
pub fn build(self) -> MokaBackend<S, C> {
let policy = self.eviction_policy.unwrap_or_else(EvictionPolicy::lru);
let cache: Cache<CacheKey, CacheValue<Raw>> = CacheBuilder::new(self.capacity.0)
.weigher(Self::byte_weigher)
.eviction_policy(policy)
.expire_after(Expiration)
.build();
MokaBackend {
cache,
key_format: self.key_format,
serializer: self.serializer,
compressor: self.compressor,
label: self.label,
}
}
fn byte_weigher(key: &CacheKey, value: &CacheValue<Raw>) -> u32 {
(key.memory_size() + value.memory_size()).min(u32::MAX as usize) as u32
}
}