use chrono::{DateTime, Duration, Utc};
use serde::{Serialize, de::DeserializeOwned};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tokio::sync::{Mutex, Semaphore};
use crate::error::{CoreError, Result};
use crate::utils::cache::Cache;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WriteMode {
WriteThrough,
WriteBack,
WriteAround,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CoherencyProtocol {
None,
Invalidate,
Update,
MESI, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CacheEntryState {
Modified,
Exclusive,
Shared,
Invalid,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
struct CoherentCacheEntry<T> {
value: T,
state: CacheEntryState,
expires_at: Option<DateTime<Utc>>,
created_at: DateTime<Utc>,
}
#[allow(dead_code)]
impl<T> CoherentCacheEntry<T> {
fn new(value: T, state: CacheEntryState, ttl: Option<Duration>) -> Self {
let now = Utc::now();
Self {
value,
state,
expires_at: ttl.map(|d| now + d),
created_at: now,
}
}
fn is_expired(&self) -> bool {
self.expires_at.map(|exp| Utc::now() > exp).unwrap_or(false)
}
fn is_valid(&self) -> bool {
self.state != CacheEntryState::Invalid && !self.is_expired()
}
}
#[derive(Clone)]
pub struct StampedeProtection {
loading_locks: Arc<RwLock<HashMap<String, Arc<Mutex<()>>>>>,
load_semaphore: Arc<Semaphore>,
}
impl StampedeProtection {
pub fn new(max_concurrent_loads: usize) -> Self {
Self {
loading_locks: Arc::new(RwLock::new(HashMap::new())),
load_semaphore: Arc::new(Semaphore::new(max_concurrent_loads)),
}
}
fn get_lock(&self, key: &str) -> Arc<Mutex<()>> {
let mut locks = self.loading_locks.write().unwrap();
locks
.entry(key.to_string())
.or_insert_with(|| Arc::new(Mutex::new(())))
.clone()
}
pub async fn protected_load<F, T>(&self, key: &str, loader: F) -> Result<T>
where
F: std::future::Future<Output = Result<T>>,
{
let lock = self.get_lock(key);
let _permit =
self.load_semaphore.acquire().await.map_err(|e| {
CoreError::Configuration(format!("Failed to acquire semaphore: {}", e))
})?;
let _guard = lock.lock().await;
loader.await
}
pub fn cleanup(&self) {
let mut locks = self.loading_locks.write().unwrap();
locks.retain(|_, lock| Arc::strong_count(lock) > 1);
}
}
pub struct WriteBackBuffer<T> {
buffer: Arc<RwLock<HashMap<String, T>>>,
max_size: usize,
}
impl<T: Clone> WriteBackBuffer<T> {
pub fn new(max_size: usize) -> Self {
Self {
buffer: Arc::new(RwLock::new(HashMap::new())),
max_size,
}
}
pub fn add(&self, key: String, value: T) -> bool {
let mut buffer = self.buffer.write().unwrap();
buffer.insert(key, value);
buffer.len() >= self.max_size
}
pub fn flush(&self) -> HashMap<String, T> {
let mut buffer = self.buffer.write().unwrap();
std::mem::take(&mut *buffer)
}
pub fn len(&self) -> usize {
let buffer = self.buffer.read().unwrap();
buffer.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct DistributedCache<C: Cache> {
local_cache: C,
write_mode: WriteMode,
coherency_protocol: CoherencyProtocol,
stampede_protection: StampedeProtection,
write_back_buffer: Option<WriteBackBuffer<Vec<u8>>>,
entry_states: Arc<RwLock<HashMap<String, CacheEntryState>>>,
}
impl<C: Cache> DistributedCache<C> {
pub fn new(local_cache: C) -> Self {
Self {
local_cache,
write_mode: WriteMode::WriteThrough,
coherency_protocol: CoherencyProtocol::Invalidate,
stampede_protection: StampedeProtection::new(100),
write_back_buffer: None,
entry_states: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn with_config(
local_cache: C,
write_mode: WriteMode,
coherency_protocol: CoherencyProtocol,
max_concurrent_loads: usize,
) -> Self {
let write_back_buffer = if write_mode == WriteMode::WriteBack {
Some(WriteBackBuffer::new(1000))
} else {
None
};
Self {
local_cache,
write_mode,
coherency_protocol,
stampede_protection: StampedeProtection::new(max_concurrent_loads),
write_back_buffer,
entry_states: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn get_with_loader<T, F, Fut>(
&self,
key: &str,
loader: F,
ttl: Option<Duration>,
) -> Result<T>
where
T: DeserializeOwned + Serialize + Send + Sync + Clone + 'static,
F: FnOnce() -> Fut + Send + 'static,
Fut: std::future::Future<Output = Result<T>> + Send,
{
if let Some(value) = self.local_cache.get::<T>(key).await? {
if self.coherency_protocol != CoherencyProtocol::None {
if self.is_entry_valid(key) {
return Ok(value);
}
} else {
return Ok(value);
}
}
let value = self
.stampede_protection
.protected_load(key, async move { loader().await })
.await?;
self.set_internal(key, &value, ttl, CacheEntryState::Exclusive)
.await?;
Ok(value)
}
pub async fn set<T>(&self, key: &str, value: &T, ttl: Option<Duration>) -> Result<()>
where
T: Serialize + Send + Sync,
{
self.set_internal(key, value, ttl, CacheEntryState::Modified)
.await
}
async fn set_internal<T>(
&self,
key: &str,
value: &T,
ttl: Option<Duration>,
state: CacheEntryState,
) -> Result<()>
where
T: Serialize + Send + Sync,
{
self.local_cache.set(key, value, ttl).await?;
if self.coherency_protocol != CoherencyProtocol::None {
let mut states = self.entry_states.write().unwrap();
states.insert(key.to_string(), state);
}
match self.write_mode {
WriteMode::WriteThrough => {
self.write_to_storage(key, value).await?;
}
WriteMode::WriteBack => {
if let Some(ref buffer) = self.write_back_buffer {
let bytes = serde_json::to_vec(value)
.map_err(|e| CoreError::Serialization(e.to_string()))?;
if buffer.add(key.to_string(), bytes) {
self.flush_write_back_buffer().await?;
}
}
}
WriteMode::WriteAround => {
self.write_to_storage(key, value).await?;
self.local_cache.delete(key).await?;
}
}
self.handle_coherency(key, value).await?;
Ok(())
}
async fn write_to_storage<T>(&self, _key: &str, _value: &T) -> Result<()>
where
T: Serialize + Send + Sync,
{
Ok(())
}
async fn handle_coherency<T>(&self, key: &str, _value: &T) -> Result<()>
where
T: Serialize + Send + Sync,
{
match self.coherency_protocol {
CoherencyProtocol::None => {
}
CoherencyProtocol::Invalidate => {
self.broadcast_invalidate(key).await?;
}
CoherencyProtocol::Update => {
}
CoherencyProtocol::MESI => {
let mut states = self.entry_states.write().unwrap();
states.insert(key.to_string(), CacheEntryState::Modified);
}
}
Ok(())
}
async fn broadcast_invalidate(&self, key: &str) -> Result<()> {
if self.coherency_protocol == CoherencyProtocol::MESI {
let mut states = self.entry_states.write().unwrap();
states.insert(key.to_string(), CacheEntryState::Invalid);
}
Ok(())
}
fn is_entry_valid(&self, key: &str) -> bool {
let states = self.entry_states.read().unwrap();
if let Some(state) = states.get(key) {
*state != CacheEntryState::Invalid
} else {
false
}
}
pub async fn invalidate(&self, key: &str) -> Result<()> {
self.local_cache.delete(key).await?;
if self.coherency_protocol == CoherencyProtocol::MESI {
let mut states = self.entry_states.write().unwrap();
states.insert(key.to_string(), CacheEntryState::Invalid);
}
self.broadcast_invalidate(key).await?;
Ok(())
}
pub async fn flush_write_back_buffer(&self) -> Result<usize> {
if let Some(ref buffer) = self.write_back_buffer {
let writes = buffer.flush();
let count = writes.len();
for (key, _value_bytes) in writes {
let _ = key;
}
return Ok(count);
}
Ok(0)
}
pub fn stats(&self) -> CacheStatistics {
let write_back_pending = self
.write_back_buffer
.as_ref()
.map(|b| b.len())
.unwrap_or(0);
let state_counts = if self.coherency_protocol == CoherencyProtocol::MESI {
let states = self.entry_states.read().unwrap();
let mut counts = HashMap::new();
for state in states.values() {
*counts.entry(*state).or_insert(0) += 1;
}
counts
} else {
HashMap::new()
};
CacheStatistics {
write_mode: self.write_mode,
coherency_protocol: self.coherency_protocol,
write_back_pending,
state_counts,
}
}
pub fn cleanup(&self) {
self.stampede_protection.cleanup();
}
}
#[derive(Debug)]
pub struct CacheStatistics {
pub write_mode: WriteMode,
pub coherency_protocol: CoherencyProtocol,
pub write_back_pending: usize,
pub state_counts: HashMap<CacheEntryState, usize>,
}
#[async_trait::async_trait]
pub trait RedisCache: Send + Sync {
async fn redis_get(&self, key: &str) -> Result<Option<Vec<u8>>>;
async fn redis_set(&self, key: &str, value: &[u8], ttl: Option<Duration>) -> Result<()>;
async fn redis_delete(&self, key: &str) -> Result<()>;
async fn redis_exists(&self, key: &str) -> Result<bool>;
async fn redis_publish(&self, channel: &str, message: &str) -> Result<()>;
async fn redis_subscribe(&self, channel: &str) -> Result<()>;
}
pub struct MockRedisCache {
data: Arc<RwLock<HashMap<String, Vec<u8>>>>,
}
impl Default for MockRedisCache {
fn default() -> Self {
Self::new()
}
}
impl MockRedisCache {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
data: Arc::new(RwLock::new(HashMap::new())),
}
}
}
#[async_trait::async_trait]
impl RedisCache for MockRedisCache {
async fn redis_get(&self, key: &str) -> Result<Option<Vec<u8>>> {
let data = self.data.read().unwrap();
Ok(data.get(key).cloned())
}
async fn redis_set(&self, key: &str, value: &[u8], _ttl: Option<Duration>) -> Result<()> {
let mut data = self.data.write().unwrap();
data.insert(key.to_string(), value.to_vec());
Ok(())
}
async fn redis_delete(&self, key: &str) -> Result<()> {
let mut data = self.data.write().unwrap();
data.remove(key);
Ok(())
}
async fn redis_exists(&self, key: &str) -> Result<bool> {
let data = self.data.read().unwrap();
Ok(data.contains_key(key))
}
async fn redis_publish(&self, _channel: &str, _message: &str) -> Result<()> {
Ok(())
}
async fn redis_subscribe(&self, _channel: &str) -> Result<()> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::utils::cache::MemoryCache;
#[tokio::test]
async fn test_stampede_protection() {
let protection = StampedeProtection::new(10);
let counter = Arc::new(RwLock::new(0));
let mut handles = vec![];
for _ in 0..10 {
let p = protection.clone();
let c = counter.clone();
let handle = tokio::spawn(async move {
p.protected_load("key1", async {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
let mut count = c.write().unwrap();
*count += 1;
Ok::<i32, CoreError>(*count)
})
.await
});
handles.push(handle);
}
for handle in handles {
let _ = handle.await;
}
let final_count = *counter.read().unwrap();
assert!((1..=10).contains(&final_count));
}
#[tokio::test]
async fn test_write_back_buffer() {
let buffer = WriteBackBuffer::new(3);
buffer.add("key1".to_string(), vec![1, 2, 3]);
assert_eq!(buffer.len(), 1);
buffer.add("key2".to_string(), vec![4, 5, 6]);
assert_eq!(buffer.len(), 2);
let should_flush = buffer.add("key3".to_string(), vec![7, 8, 9]);
assert!(should_flush);
assert_eq!(buffer.len(), 3);
let flushed = buffer.flush();
assert_eq!(flushed.len(), 3);
assert_eq!(buffer.len(), 0);
}
#[tokio::test]
async fn test_distributed_cache_write_through() {
let local = MemoryCache::new();
let cache = DistributedCache::new(local);
cache.set("key1", &"value1", None).await.unwrap();
let stats = cache.stats();
assert_eq!(stats.write_mode, WriteMode::WriteThrough);
assert_eq!(stats.write_back_pending, 0);
}
#[tokio::test]
async fn test_distributed_cache_write_back() {
let local = MemoryCache::new();
let cache = DistributedCache::with_config(
local,
WriteMode::WriteBack,
CoherencyProtocol::None,
100,
);
cache.set("key1", &"value1", None).await.unwrap();
let stats = cache.stats();
assert_eq!(stats.write_mode, WriteMode::WriteBack);
}
#[tokio::test]
async fn test_cache_invalidation() {
let local = MemoryCache::new();
let cache = DistributedCache::with_config(
local.clone(),
WriteMode::WriteThrough,
CoherencyProtocol::Invalidate,
100,
);
cache.set("key1", &"value1", None).await.unwrap();
assert!(local.exists("key1").await.unwrap());
cache.invalidate("key1").await.unwrap();
assert!(!local.exists("key1").await.unwrap());
}
#[tokio::test]
async fn test_mesi_protocol() {
let local = MemoryCache::new();
let cache = DistributedCache::with_config(
local,
WriteMode::WriteThrough,
CoherencyProtocol::MESI,
100,
);
cache.set("key1", &"value1", None).await.unwrap();
let stats = cache.stats();
assert_eq!(stats.coherency_protocol, CoherencyProtocol::MESI);
let modified_count = stats
.state_counts
.get(&CacheEntryState::Modified)
.copied()
.unwrap_or(0);
assert!(modified_count > 0);
}
#[tokio::test]
async fn test_get_with_loader() {
let local = MemoryCache::new();
let cache = DistributedCache::new(local);
let load_count = Arc::new(RwLock::new(0));
let load_count_clone = load_count.clone();
let value = cache
.get_with_loader(
"key1",
move || {
let lc = load_count_clone.clone();
async move {
*lc.write().unwrap() += 1;
Ok::<String, CoreError>("loaded_value".to_string())
}
},
None,
)
.await
.unwrap();
assert_eq!(value, "loaded_value");
assert_eq!(*load_count.read().unwrap(), 1);
let value2 = cache
.get_with_loader(
"key1",
move || async move { Ok::<String, CoreError>("should_not_load".to_string()) },
None,
)
.await
.unwrap();
assert_eq!(value2, "loaded_value");
assert_eq!(*load_count.read().unwrap(), 1); }
#[test]
fn test_coherent_cache_entry() {
let entry = CoherentCacheEntry::new(
"value".to_string(),
CacheEntryState::Exclusive,
Some(Duration::seconds(60)),
);
assert!(entry.is_valid());
assert_eq!(entry.state, CacheEntryState::Exclusive);
assert!(!entry.is_expired());
}
#[test]
fn test_coherent_cache_entry_invalid() {
let entry = CoherentCacheEntry::new(
"value".to_string(),
CacheEntryState::Invalid,
Some(Duration::seconds(60)),
);
assert!(!entry.is_valid());
}
}