use super::*;
pub const CLUSTER_PEER_FETCH_BASE_URL_METADATA_KEY: &str = "hydracache.peer_fetch.base_url";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClusterPeerFetchRequest {
pub owner: ClusterNodeId,
pub key: String,
pub generation: Option<ClusterGeneration>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClusterPeerFetchGenerationMismatch {
pub requested: ClusterGeneration,
pub current: ClusterGeneration,
}
impl ClusterPeerFetchRequest {
pub fn new(owner: impl Into<ClusterNodeId>, key: impl Into<String>) -> Self {
Self {
owner: owner.into(),
key: key.into(),
generation: None,
}
}
pub fn generation(mut self, generation: ClusterGeneration) -> Self {
self.generation = Some(generation);
self
}
pub fn has_generation(&self) -> bool {
self.generation.is_some()
}
pub fn matches_generation(&self, current: ClusterGeneration) -> bool {
self.generation_mismatch(current).is_none()
}
pub fn generation_mismatch(
&self,
current: ClusterGeneration,
) -> Option<ClusterPeerFetchGenerationMismatch> {
match self.generation {
Some(requested) if requested != current => {
Some(ClusterPeerFetchGenerationMismatch { requested, current })
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ClusterPeerFetchResponse {
pub owner: ClusterNodeId,
pub key: String,
pub value: Option<Bytes>,
}
impl ClusterPeerFetchResponse {
pub fn hit(owner: impl Into<ClusterNodeId>, key: impl Into<String>, value: Bytes) -> Self {
Self {
owner: owner.into(),
key: key.into(),
value: Some(value),
}
}
pub fn miss(owner: impl Into<ClusterNodeId>, key: impl Into<String>) -> Self {
Self {
owner: owner.into(),
key: key.into(),
value: None,
}
}
pub fn is_hit(&self) -> bool {
self.value.is_some()
}
pub fn is_miss(&self) -> bool {
self.value.is_none()
}
}
#[async_trait::async_trait]
pub trait ClusterPeerFetch: Send + Sync {
async fn fetch(&self, request: ClusterPeerFetchRequest) -> Result<ClusterPeerFetchResponse>;
}
#[derive(Debug, Clone, Default)]
pub struct InMemoryPeerFetch {
state: Arc<Mutex<InMemoryPeerFetchState>>,
}
#[derive(Debug, Default)]
struct InMemoryPeerFetchState {
values: BTreeMap<(ClusterNodeId, String), Bytes>,
hits: u64,
misses: u64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ClusterPeerFetchDiagnostics {
pub stored_values: usize,
pub hits: u64,
pub misses: u64,
}
impl ClusterPeerFetchDiagnostics {
pub fn total_requests(&self) -> u64 {
self.hits.saturating_add(self.misses)
}
pub fn hit_ratio(&self) -> Option<f64> {
let total = self.total_requests();
if total == 0 {
None
} else {
Some(self.hits as f64 / total as f64)
}
}
}
impl InMemoryPeerFetch {
pub fn new() -> Self {
Self::default()
}
pub fn put(
&self,
owner: impl Into<ClusterNodeId>,
key: impl Into<String>,
value: impl Into<Bytes>,
) {
self.state
.lock()
.expect("peer fetch state poisoned")
.values
.insert((owner.into(), key.into()), value.into());
}
pub fn remove(&self, owner: &ClusterNodeId, key: &str) -> Option<Bytes> {
self.state
.lock()
.expect("peer fetch state poisoned")
.values
.remove(&(owner.clone(), key.to_owned()))
}
pub fn len(&self) -> usize {
self.state
.lock()
.expect("peer fetch state poisoned")
.values
.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn diagnostics(&self) -> ClusterPeerFetchDiagnostics {
let state = self.state.lock().expect("peer fetch state poisoned");
ClusterPeerFetchDiagnostics {
stored_values: state.values.len(),
hits: state.hits,
misses: state.misses,
}
}
}
#[async_trait::async_trait]
impl ClusterPeerFetch for InMemoryPeerFetch {
async fn fetch(&self, request: ClusterPeerFetchRequest) -> Result<ClusterPeerFetchResponse> {
let mut state = self.state.lock().expect("peer fetch state poisoned");
let value = state
.values
.get(&(request.owner.clone(), request.key.clone()))
.cloned();
if value.is_some() {
state.hits = state.hits.saturating_add(1);
} else {
state.misses = state.misses.saturating_add(1);
}
Ok(ClusterPeerFetchResponse {
owner: request.owner,
key: request.key,
value,
})
}
}