diff --git a/caching/src/lib.rs b/caching/src/lib.rs index 73f5842..2210447 100644 --- a/caching/src/lib.rs +++ b/caching/src/lib.rs @@ -3,3 +3,4 @@ pub(crate) mod date; pub mod multi; pub mod locked; pub mod redis; +pub mod utils; diff --git a/caching/src/local/caching_local.rs b/caching/src/local/caching_local.rs index 507e511..b0fdcf0 100644 --- a/caching/src/local/caching_local.rs +++ b/caching/src/local/caching_local.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::fmt::{Debug, Formatter}; use std::future::Future; use std::sync::Arc; use std::time::Duration; @@ -14,17 +15,67 @@ use novax::errors::CachingError; use novax::errors::NovaXError; use crate::date::get_current_timestamp::{get_current_timestamp, GetDuration}; +use crate::utils::lock::MutexLike; -#[derive(Clone, Debug)] -pub struct CachingLocal { +pub type CachingLocal = BaseCachingLocal>>, Mutex>, Mutex, Mutex>; + +pub struct BaseCachingLocal +where + MutexValue: MutexLike>>, + MutexExpiration: MutexLike>, + MutexCleanupInterval: MutexLike, + MutexIsCleanupProcessStarted: MutexLike +{ duration_strategy: CachingDurationStrategy, - value_map: Arc>>>, - expiration_timestamp_map: Arc>>, - cleanup_interval: Arc>, - is_cleanup_process_started: Arc>, + value_map: Arc, + expiration_timestamp_map: Arc, + cleanup_interval: Arc, + is_cleanup_process_started: Arc, } -impl CachingLocal { +impl Clone for BaseCachingLocal +where + MutexValue: MutexLike>>, + MutexExpiration: MutexLike>, + MutexCleanupInterval: MutexLike, + MutexIsCleanupProcessStarted: MutexLike +{ + fn clone(&self) -> Self { + Self { + duration_strategy: self.duration_strategy.clone(), + value_map: self.value_map.clone(), + expiration_timestamp_map: self.expiration_timestamp_map.clone(), + cleanup_interval: self.cleanup_interval.clone(), + is_cleanup_process_started: self.is_cleanup_process_started.clone(), + } + } +} + +impl Debug for BaseCachingLocal +where + MutexValue: MutexLike>>, + MutexExpiration: MutexLike>, + MutexCleanupInterval: MutexLike, + MutexIsCleanupProcessStarted: MutexLike +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BaseCachingLocal") + .field("duration_strategy", &self.duration_strategy) + .field("value_map", &self.value_map) + .field("expiration_timestamp_map", &self.expiration_timestamp_map) + .field("cleanup_interval", &self.cleanup_interval) + .field("is_cleanup_process_started", &self.is_cleanup_process_started) + .finish() + } +} + +impl BaseCachingLocal +where + MutexValue: MutexLike>>, + MutexExpiration: MutexLike>, + MutexCleanupInterval: MutexLike, + MutexIsCleanupProcessStarted: MutexLike +{ pub fn empty(duration_strategy: CachingDurationStrategy) -> CachingLocal { CachingLocal { duration_strategy, @@ -35,32 +86,11 @@ impl CachingLocal { } } - pub async fn empty_with_auto_cleanup( - duration_strategy: CachingDurationStrategy, - cleanup_interval: Duration - ) -> Result { - let caching = CachingLocal::empty(duration_strategy); - - { - let mut locked = caching.cleanup_interval.lock().await; - *locked = cleanup_interval; - } - - caching.start_cleanup_process_if_needed().await; - - Ok(caching) - } - async fn remove_key(&self, key: u64) { let _ = self.expiration_timestamp_map.lock().await.remove(&key); let _ = self.value_map.lock().await.remove(&key); } - async fn clear(&self) { - self.expiration_timestamp_map.lock().await.clear(); - self.value_map.lock().await.clear(); - } - async fn set_value(&self, key: u64, value: &T) -> Result<(), NovaXError> { let expiration_timestamp = self.duration_strategy.get_duration_timestamp(&get_current_timestamp()?)?; self.expiration_timestamp_map.lock().await.insert(key, expiration_timestamp); @@ -77,6 +107,25 @@ impl CachingLocal { *locked = interval; } +} + +impl CachingLocal +{ + pub async fn empty_with_auto_cleanup( + duration_strategy: CachingDurationStrategy, + cleanup_interval: Duration + ) -> Result { + let caching = CachingLocal::empty(duration_strategy); + + { + let mut locked = caching.cleanup_interval.lock().await; + *locked = cleanup_interval; + } + + caching.start_cleanup_process_if_needed().await; + + Ok(caching) + } async fn start_cleanup_process_if_needed(&self) { { @@ -132,7 +181,13 @@ impl CachingLocal { } #[async_trait] -impl CachingStrategy for CachingLocal { +impl CachingStrategy for BaseCachingLocal +where + MutexValue: MutexLike>>, + MutexExpiration: MutexLike>, + MutexCleanupInterval: MutexLike, + MutexIsCleanupProcessStarted: MutexLike +{ async fn get_cache(&self, key: u64) -> Result, NovaXError> { let Some(expiration_timestamp) = self.expiration_timestamp_map.lock().await.get(&key).cloned() else { return Ok(None) }; @@ -169,13 +224,14 @@ impl CachingStrategy for CachingLocal { } async fn clear(&self) -> Result<(), NovaXError> { - self.clear().await; + self.expiration_timestamp_map.lock().await.clear(); + self.value_map.lock().await.clear(); Ok(()) } fn with_duration_strategy(&self, strategy: CachingDurationStrategy) -> Self { - CachingLocal { + Self { duration_strategy: strategy, value_map: self.value_map.clone(), expiration_timestamp_map: self.expiration_timestamp_map.clone(), @@ -385,7 +441,7 @@ mod test { caching.set_cache(1, &"test".to_string()).await?; caching.set_cache(2, &"test2".to_string()).await?; - caching.clear().await; + caching.clear().await?; assert!(caching.value_map.lock().await.is_empty()); assert!(caching.expiration_timestamp_map.lock().await.is_empty()); diff --git a/caching/src/locked/caching.rs b/caching/src/locked/caching.rs index ca9e6a1..8d9d968 100644 --- a/caching/src/locked/caching.rs +++ b/caching/src/locked/caching.rs @@ -1,63 +1,84 @@ use std::collections::HashMap; -use std::fmt::Debug; +use std::fmt::{Debug, Formatter}; use std::future::Future; use std::sync::Arc; use async_trait::async_trait; use serde::de::DeserializeOwned; use serde::Serialize; -use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; +use tokio::sync::{Mutex, RwLock}; +use crate::utils::lock::{Locker, MutexLike}; use novax::caching::{CachingDurationStrategy, CachingStrategy}; use novax::errors::NovaXError; #[allow(type_alias_bounds)] -pub type CachingLocked = BaseCachingLocked>>; - -#[async_trait] -pub trait Locker: Send + Sync + Clone + Debug { - fn new() -> Self; - async fn read(&self) -> RwLockReadGuard<'_, ()>; - async fn write(&self) -> RwLockWriteGuard<'_, ()>; +pub type CachingLocked = BaseCachingLocked, Mutex>>>>; + +pub struct BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ + pub caching: C, + _lockers_map: Arc } -#[async_trait] -impl Locker for Arc> { - fn new() -> Self { - Self::new(RwLock::new(())) - } - - async fn read(&self) -> RwLockReadGuard<'_, ()> { - self.as_ref().read().await - } - - async fn write(&self) -> RwLockWriteGuard<'_, ()> { - self.as_ref().write().await +impl Clone for BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ + fn clone(&self) -> Self { + Self { + caching: self.caching.clone(), + _lockers_map: self._lockers_map.clone(), + } } } -#[derive(Clone, Debug)] -pub struct BaseCachingLocked { - pub caching: C, - _lockers_map: Arc>> +impl Debug for BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BaseCachingLocked") + .field("caching", &self.caching) + .field("_lockers_map", &self._lockers_map) + .finish() + } } -impl BaseCachingLocked { - pub fn new(caching: C) -> BaseCachingLocked { +impl BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ + pub fn new(caching: C) -> BaseCachingLocked { BaseCachingLocked { caching, - _lockers_map: Arc::new(Mutex::new(HashMap::new())) + _lockers_map: Arc::new(M::new(HashMap::new())) } } } -impl BaseCachingLocked { - async fn get_locker(&self, key: u64) -> Result { +impl BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ + async fn get_locker(&self, key: u64) -> Result, NovaXError> { let mut lockers_map = self._lockers_map.lock().await; let locker = if let Some(locker) = lockers_map.get(&key) { locker.clone() } else { - let locker = L::new(); + let locker = Arc::new(L::new()); lockers_map.insert(key, locker.clone()); locker }; @@ -67,7 +88,12 @@ impl BaseCachingLocked { } #[async_trait] -impl CachingStrategy for BaseCachingLocked { +impl CachingStrategy for BaseCachingLocked +where + C: CachingStrategy, + L: Locker, + M: MutexLike>>, +{ async fn get_cache(&self, key: u64) -> Result, NovaXError> { let locker = self.get_locker(key).await?; let lock_value = locker.read().await; diff --git a/caching/src/utils/lock.rs b/caching/src/utils/lock.rs new file mode 100644 index 0000000..a4e6291 --- /dev/null +++ b/caching/src/utils/lock.rs @@ -0,0 +1,46 @@ +use async_trait::async_trait; +use std::fmt::Debug; +use tokio::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[async_trait] +pub trait MutexLike: Send + Sync + Debug { + type T: Send; + fn new(value: Self::T) -> Self; + + async fn lock(&self) -> MutexGuard<'_, Self::T>; +} + +#[async_trait] +impl MutexLike for Mutex { + type T = T; + + fn new(value: Self::T) -> Self { + Self::new(value) + } + + async fn lock(&self) -> MutexGuard<'_, Self::T> { + Mutex::lock(self).await + } +} + +#[async_trait] +pub trait Locker: Send + Sync { + fn new() -> Self; + async fn read(&self) -> RwLockReadGuard<'_, ()>; + async fn write(&self) -> RwLockWriteGuard<'_, ()>; +} + +#[async_trait] +impl Locker for RwLock<()> { + fn new() -> Self { + Self::new(()) + } + + async fn read(&self) -> RwLockReadGuard<'_, ()> { + RwLock::read(self).await + } + + async fn write(&self) -> RwLockWriteGuard<'_, ()> { + RwLock::write(self).await + } +} \ No newline at end of file diff --git a/caching/src/utils/mod.rs b/caching/src/utils/mod.rs new file mode 100644 index 0000000..40e9b6d --- /dev/null +++ b/caching/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod lock; \ No newline at end of file