use std::hash::{Hash, Hasher};
use std::sync::Mutex;
use crate::error::CacheError;
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.len() * DEFAULT_CAPACITY_PER_SHARD
}
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,
{
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> {
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
}