use async_trait::async_trait;
use hitbox_core::{BoxContext, CacheContext, CacheKey, CacheValue, Offload};
use std::future::Future;
use super::{CompositionReadPolicy, ReadResult};
use crate::composition::CompositionLayer;
#[derive(Debug, Clone, Copy, Default)]
pub struct ParallelReadPolicy;
impl ParallelReadPolicy {
pub fn new() -> Self {
Self
}
}
#[async_trait]
impl CompositionReadPolicy for ParallelReadPolicy {
#[tracing::instrument(skip(self, key, read_l1, read_l2, _offload), level = "trace")]
async fn execute_with<T, E, F1, F2, Fut1, Fut2, O>(
&self,
key: CacheKey,
read_l1: F1,
read_l2: F2,
_offload: &O,
) -> Result<ReadResult<T>, E>
where
T: Send + 'static,
E: Send + std::fmt::Debug + 'static,
F1: FnOnce(CacheKey) -> Fut1 + Send,
F2: FnOnce(CacheKey) -> Fut2 + Send,
Fut1: Future<Output = (Result<Option<CacheValue<T>>, E>, BoxContext)> + Send + 'static,
Fut2: Future<Output = (Result<Option<CacheValue<T>>, E>, BoxContext)> + Send + 'static,
O: Offload<'static>,
{
let ((l1_result, l1_ctx), (l2_result, l2_ctx)) =
futures::join!(read_l1(key.clone()), read_l2(key));
match (l1_result, l2_result) {
(Ok(Some(l1_value)), Ok(Some(l2_value))) => {
match (l1_value.ttl(), l2_value.ttl()) {
(Some(l1_ttl), Some(l2_ttl)) if l2_ttl > l1_ttl => {
tracing::trace!("Both hit, preferring L2 (fresher TTL)");
Ok(ReadResult {
value: Some(l2_value),
source: CompositionLayer::L2,
context: l2_ctx,
})
}
(Some(_), None) => {
tracing::trace!("Both hit, preferring L2 (no expiry)");
Ok(ReadResult {
value: Some(l2_value),
source: CompositionLayer::L2,
context: l2_ctx,
})
}
_ => {
tracing::trace!("Both hit, preferring L1 (fresher or equal TTL)");
Ok(ReadResult {
value: Some(l1_value),
source: CompositionLayer::L1,
context: l1_ctx,
})
}
}
}
(Ok(Some(value)), _) => {
tracing::trace!("L1 hit, L2 miss/error");
Ok(ReadResult {
value: Some(value),
source: CompositionLayer::L1,
context: l1_ctx,
})
}
(_, Ok(Some(value))) => {
tracing::trace!("L2 hit, L1 miss/error");
Ok(ReadResult {
value: Some(value),
source: CompositionLayer::L2,
context: l2_ctx,
})
}
(Ok(None), Ok(None)) => {
tracing::trace!("Both layers miss");
Ok(ReadResult {
value: None,
source: CompositionLayer::L2,
context: CacheContext::default().boxed(),
})
}
(Err(e1), Err(e2)) => {
tracing::error!(l1_error = ?e1, l2_error = ?e2, "Both layers failed");
Err(e2)
}
(Ok(None), Err(e)) | (Err(e), Ok(None)) => {
tracing::warn!(error = ?e, "One layer failed, one missed");
Ok(ReadResult {
value: None,
source: CompositionLayer::L2,
context: CacheContext::default().boxed(),
})
}
}
}
}