use std::sync::Arc;
use super::ChannelRuntimeConfig;
use super::*;
use crate::connector::cache_backend::CacheBackend;
use crate::errors::OrionError;
use crate::metrics;
pub struct DedupClaim {
store: Arc<dyn CacheBackend>,
key: String,
window_secs: u64,
}
pub(super) const DEDUP_SETTLED: &str = "settled";
impl DedupClaim {
pub async fn confirm(self) {
if let Err(e) = self
.store
.set_ex(&self.key, DEDUP_SETTLED, self.window_secs)
.await
{
tracing::warn!(
key = %self.key,
error = %e,
"Could not mark the idempotency key settled; a redelivery of this message would be reprocessed"
);
}
}
pub async fn release(self) {
if let Err(e) = self.store.remove(&self.key).await {
tracing::warn!(
key = %self.key,
error = %e,
"Could not release the idempotency key after an unsettled delivery"
);
}
}
}
pub(super) async fn check_deduplication(
channel: &str,
channel_config: &Option<Arc<ChannelRuntimeConfig>>,
header: HeaderLookup<'_>,
key_fallback: Option<&str>,
owner: Option<&str>,
) -> Result<Option<DedupClaim>, OrionError> {
let Some(cfg) = channel_config else {
return Ok(None);
};
let Some(ref dedup) = cfg.parsed_config.deduplication else {
return Ok(None);
};
let Some(ref store) = cfg.dedup_store else {
return Ok(None);
};
let Some(key) = header(&dedup.header).or_else(|| key_fallback.map(str::to_string)) else {
return Ok(None);
};
let window = dedup.window_secs.unwrap_or(300);
let scoped_key = format!("dedup:{channel}:{key}");
let one_shot;
let owner = match owner {
Some(owner) => owner,
None => {
one_shot = uuid::Uuid::new_v4().simple().to_string();
one_shot.as_str()
}
};
let holder = match store.claim_dedup_key(&scoped_key, owner, window).await {
Ok(holder) => holder,
Err(e) => {
metrics::record_error("dedup_backend");
match dedup.on_backend_error {
crate::channel::BackendErrorPolicy::Allow => {
tracing::warn!(
channel = %channel,
error = %e,
header = %dedup.header,
"Dedup backend error; failing open (request allowed without dedup check)"
);
return Ok(None);
}
crate::channel::BackendErrorPolicy::Deny => {
tracing::warn!(
channel = %channel,
error = %e,
header = %dedup.header,
"Dedup backend error; failing closed (request refused)"
);
return Err(OrionError::unavailable(
crate::errors::Unavailable::GuardBackend,
format!(
"Channel '{channel}' cannot verify the idempotency key: the \
deduplication backend is unavailable and the channel is \
configured to fail closed"
),
));
}
}
}
};
match holder {
None => {}
Some(ref held) if held == owner => {
tracing::debug!(
channel = %channel,
key = %key,
"Redelivery of an unsettled message; the idempotency claim is its own"
);
}
Some(_) => {
return Err(OrionError::Conflict(format!(
"Duplicate request: idempotency key '{key}' already seen"
)));
}
}
Ok(Some(DedupClaim {
store: store.clone(),
key: scoped_key,
window_secs: window,
}))
}