90 lines
3.2 KiB
Rust
90 lines
3.2 KiB
Rust
//! Incremental file based incremental rendering
|
|
|
|
#![allow(non_snake_case)]
|
|
|
|
use chrono::offset::Utc;
|
|
use chrono::DateTime;
|
|
use rustc_hash::FxHasher;
|
|
use std::{hash::BuildHasherDefault, num::NonZeroUsize};
|
|
|
|
use super::freshness::RenderFreshness;
|
|
|
|
pub(crate) struct InMemoryCache {
|
|
#[allow(clippy::type_complexity)]
|
|
lru: Option<lru::LruCache<String, (DateTime<Utc>, Vec<u8>), BuildHasherDefault<FxHasher>>>,
|
|
invalidate_after: Option<std::time::Duration>,
|
|
}
|
|
|
|
impl InMemoryCache {
|
|
pub fn new(memory_cache_limit: usize, invalidate_after: Option<std::time::Duration>) -> Self {
|
|
Self {
|
|
lru: NonZeroUsize::new(memory_cache_limit)
|
|
.map(|limit| lru::LruCache::with_hasher(limit, Default::default())),
|
|
invalidate_after,
|
|
}
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
if let Some(cache) = &mut self.lru {
|
|
cache.clear();
|
|
}
|
|
}
|
|
|
|
pub fn put(&mut self, route: String, timestamp: DateTime<Utc>, data: Vec<u8>) {
|
|
if let Some(cache) = &mut self.lru {
|
|
cache.put(route, (timestamp, data));
|
|
}
|
|
}
|
|
|
|
pub fn invalidate(&mut self, route: &str) {
|
|
if let Some(cache) = &mut self.lru {
|
|
cache.pop(route);
|
|
}
|
|
}
|
|
|
|
pub fn try_get_or_insert<'a, F: FnOnce() -> Result<(DateTime<Utc>, Vec<u8>), E>, E>(
|
|
&'a mut self,
|
|
route: &str,
|
|
or_insert: F,
|
|
) -> Result<Option<(RenderFreshness, &'a [u8])>, E> {
|
|
if let Some(memory_cache) = self.lru.as_mut() {
|
|
let (timestamp, _) = memory_cache.try_get_or_insert(route.to_string(), or_insert)?;
|
|
|
|
let now = Utc::now();
|
|
let elapsed = timestamp.signed_duration_since(now);
|
|
let age = elapsed.num_seconds();
|
|
// The cache entry is out of date, so we need to remove it.
|
|
if let Some(invalidate_after) = self.invalidate_after {
|
|
// If we can't convert to a std duration, the duration is negative and hasn't elapsed yet.
|
|
if let Ok(std_elapsed) = elapsed.to_std() {
|
|
if std_elapsed > invalidate_after {
|
|
tracing::trace!("memory cache out of date");
|
|
memory_cache.pop(route);
|
|
return Ok(None);
|
|
}
|
|
}
|
|
}
|
|
|
|
// We need to reborrow because we may have invalidated the lifetime if the route was removed.
|
|
// We know it wasn't because we returned... but rust doesn't understand that.
|
|
let (timestamp, cache_hit) = memory_cache.get(route).unwrap();
|
|
|
|
return match self.invalidate_after {
|
|
Some(invalidate_after) => {
|
|
tracing::trace!("memory cache hit");
|
|
let max_age = invalidate_after.as_secs();
|
|
let freshness = RenderFreshness::new(age as u64, max_age, *timestamp);
|
|
Ok(Some((freshness, cache_hit)))
|
|
}
|
|
None => {
|
|
tracing::trace!("memory cache hit");
|
|
let freshness = RenderFreshness::new_age(age as u64, *timestamp);
|
|
Ok(Some((freshness, cache_hit)))
|
|
}
|
|
};
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
}
|