magnetar/src/service/generic_id_cache.rs

97 lines
2.6 KiB
Rust

use crate::web::ApiError;
use lru::LruCache;
use magnetar_model::sea_orm::{EntityTrait, PrimaryKeyTrait};
use magnetar_model::{CalckeyDbError, CalckeyModel};
use std::marker::PhantomData;
use std::sync::Arc;
use std::time::{Duration, Instant};
use strum::EnumVariantNames;
use thiserror::Error;
use tokio::sync::Mutex;
#[derive(Debug, Error, EnumVariantNames)]
pub enum GenericIdCacheError {
#[error("Database error: {0}")]
DbError(#[from] CalckeyDbError),
}
impl From<GenericIdCacheError> for ApiError {
fn from(err: GenericIdCacheError) -> Self {
let mut api_error: ApiError = match err {
GenericIdCacheError::DbError(err) => err.into(),
};
api_error.message = format!("Generic ID cache error: {}", api_error.message);
api_error
}
}
#[derive(Debug)]
struct CacheEntry<E: EntityTrait> {
created: Instant,
data: Arc<E::Model>,
}
impl<E: EntityTrait> CacheEntry<E> {
fn new(data: Arc<E::Model>) -> Self {
Self {
created: Instant::now(),
data,
}
}
}
pub struct GenericIdCacheService<E: EntityTrait> {
cache: Mutex<LruCache<String, CacheEntry<E>>>,
lifetime_max: Duration,
db: CalckeyModel,
_entity_type: PhantomData<E>,
}
impl<E: EntityTrait> GenericIdCacheService<E> {
pub(super) fn new(db: CalckeyModel, cache_size: usize, entry_lifetime: Duration) -> Self {
const CACHE_SIZE: usize = 4096;
Self {
cache: Mutex::new(LruCache::new(
cache_size
.try_into()
.unwrap_or(CACHE_SIZE.try_into().unwrap()),
)),
lifetime_max: entry_lifetime,
db,
_entity_type: PhantomData,
}
}
pub async fn get<'a>(&self, id: &'a str) -> Result<Option<Arc<E::Model>>, GenericIdCacheError>
where
<<E as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType: From<&'a str>,
{
let mut read = self.cache.lock().await;
if let Some(item) = read.peek(id) {
if item.created + self.lifetime_max >= Instant::now() {
let data = item.data.clone();
read.promote(id);
return Ok(Some(data));
}
}
drop(read);
let val = E::find_by_id(id)
.one(self.db.inner())
.await
.map_err(CalckeyDbError::from)?;
if val.is_none() {
return Ok(None);
}
let mut write = self.cache.lock().await;
let data = Arc::new(val.unwrap());
write.put(id.to_string(), CacheEntry::new(data.clone()));
Ok(Some(data))
}
}