use std::hash::{Hash, Hasher};
use std::sync::Mutex;
use crate::error::CacheError;
use crate::integrity::IntegrityCheck;
use crate::key::KeyRef;
use crate::tier::fixed_tier_stub::FixedTierStub;
pub const DEFAULT_SHARDS: usize = 4;
pub const DEFAULT_CAPACITY_PER_SHARD: usize = 256;
#[derive(Debug)]
pub struct ShardedTierStub<V> {
shards: Vec<Mutex<FixedTierStub<V>>>,
mask: usize,
}
impl<V> ShardedTierStub<V> {
#[allow(clippy::expect_used)] pub fn new() -> Self {
Self::with_shards(DEFAULT_SHARDS, DEFAULT_CAPACITY_PER_SHARD)
.expect("ShardedTierStub init: shard allocation failed at startup")
}
pub fn with_shards(shards: usize, capacity_per_shard: usize) -> Result<Self, CacheError> {
let requested = shards.max(1);
let n = requested.next_power_of_two();
let mut built = Vec::with_capacity(n);
for _ in 0..n {
built.push(Mutex::new(FixedTierStub::with_capacity(
capacity_per_shard,
)?));
}
Ok(Self {
shards: built,
mask: n - 1,
})
}
#[must_use]
pub fn shard_mask(&self) -> usize {
self.mask
}
#[must_use]
pub fn shard_count(&self) -> usize {
self.shards.len()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.shards
.iter()
.map(|s| s.lock().map_or(0, |g| g.capacity()))
.sum()
}
fn shard_for(&self, key: &KeyRef<'_>) -> &Mutex<FixedTierStub<V>> {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut hasher);
let h = hasher.finish() as usize;
&self.shards[h & self.mask]
}
pub fn get(&self, key: &KeyRef<'_>) -> Result<Option<V>, CacheError>
where
V: Clone + IntegrityCheck,
{
self.shard_for(key).lock().map_err(poisoned)?.get(key)
}
pub fn set(
&self,
key: &KeyRef<'_>,
value: V,
ttl: Option<std::time::Duration>,
) -> Result<(), CacheError>
where
V: IntegrityCheck,
{
self.shard_for(key)
.lock()
.map_err(poisoned)?
.set(key, value, ttl)
}
pub fn remove(&self, key: &KeyRef<'_>) -> Result<(), CacheError> {
self.shard_for(key).lock().map_err(poisoned)?.remove(key)
}
pub fn contains(&self, key: &KeyRef<'_>) -> Result<bool, CacheError> {
self.shard_for(key).lock().map_err(poisoned)?.contains(key)
}
}
fn poisoned<T>(_: T) -> CacheError {
CacheError::ConfigurationError
}
impl<V> ShardedTierStub<V> {
pub fn eviction_candidate(&self) -> Option<crate::integrity::KeyAddress> {
let n = self.shards.len();
if n == 0 {
return None;
}
let start = self.next_shard() % n;
for offset in 0..n {
let idx = (start + offset) % n;
if let Ok(mut shard) = self.shards[idx].lock()
&& let Some(candidate) = shard.eviction_candidate()
{
return Some(candidate);
}
}
None
}
pub fn remove_if_address(
&self,
address: crate::integrity::KeyAddress,
) -> Result<bool, CacheError> {
for shard in &self.shards {
if let Ok(mut s) = shard.lock()
&& s.remove_if_address(address)?
{
return Ok(true);
}
}
Ok(false)
}
fn next_shard(&self) -> usize {
use std::sync::atomic::{AtomicUsize, Ordering};
static CURSOR: AtomicUsize = AtomicUsize::new(0);
CURSOR.fetch_add(1, Ordering::Relaxed)
}
}