use async_trait::async_trait;
use futures::future::{Either, select};
use hitbox_core::{CacheKey, Offload};
use std::future::Future;
use super::CompositionWritePolicy;
use crate::BackendError;
use crate::composition::CompositionError;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum RaceLoserPolicy {
#[default]
Offload,
Drop,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct RaceWritePolicy {
loser_policy: RaceLoserPolicy,
}
impl RaceWritePolicy {
pub fn new() -> Self {
Self::default()
}
pub fn loser_policy(mut self, policy: RaceLoserPolicy) -> Self {
self.loser_policy = policy;
self
}
}
#[async_trait]
impl CompositionWritePolicy for RaceWritePolicy {
#[tracing::instrument(skip(self, key, write_l1, write_l2, offload), level = "trace")]
async fn execute_with<F1, F2, Fut1, Fut2, O>(
&self,
key: CacheKey,
write_l1: F1,
write_l2: F2,
offload: &O,
) -> Result<(), BackendError>
where
F1: FnOnce(CacheKey) -> Fut1 + Send,
F2: FnOnce(CacheKey) -> Fut2 + Send,
Fut1: Future<Output = Result<(), BackendError>> + Send + 'static,
Fut2: Future<Output = Result<(), BackendError>> + Send + 'static,
O: Offload<'static>,
{
let l1_fut = Box::pin(write_l1(key.clone()));
let l2_fut = Box::pin(write_l2(key));
match select(l1_fut, l2_fut).await {
Either::Left((l1_result, l2_fut)) => {
match l1_result {
Ok(()) => {
tracing::trace!("L1 write succeeded (won race)");
match self.loser_policy {
RaceLoserPolicy::Offload => {
offload.register(
hitbox_core::OffloadKey::auto("race_write_l2_loser"),
async move {
let _ = l2_fut.await;
},
);
}
RaceLoserPolicy::Drop => {
drop(l2_fut);
}
}
Ok(())
}
Err(e1) => {
tracing::trace!("L1 write failed, waiting for L2");
match l2_fut.await {
Ok(()) => {
tracing::trace!("L2 write succeeded after L1 failure");
Ok(())
}
Err(e2) => {
tracing::error!(
l1_error = ?e1,
l2_error = ?e2,
"Both L1 and L2 writes failed"
);
Err(BackendError::InternalError(Box::new(
CompositionError::BothLayersFailed { l1: e1, l2: e2 },
)))
}
}
}
}
}
Either::Right((l2_result, l1_fut)) => {
match l2_result {
Ok(()) => {
tracing::trace!("L2 write succeeded (won race)");
match self.loser_policy {
RaceLoserPolicy::Offload => {
offload.register(
hitbox_core::OffloadKey::auto("race_write_l1_loser"),
async move {
let _ = l1_fut.await;
},
);
}
RaceLoserPolicy::Drop => {
drop(l1_fut);
}
}
Ok(())
}
Err(e2) => {
tracing::trace!("L2 write failed, waiting for L1");
match l1_fut.await {
Ok(()) => {
tracing::trace!("L1 write succeeded after L2 failure");
Ok(())
}
Err(e1) => {
tracing::error!(
l1_error = ?e1,
l2_error = ?e2,
"Both L1 and L2 writes failed"
);
Err(BackendError::InternalError(Box::new(
CompositionError::BothLayersFailed { l1: e1, l2: e2 },
)))
}
}
}
}
}
}
}
}