use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use super::spool::SpoolEntry;
#[derive(Debug, Clone, Default)]
pub struct ClaimSet {
held: Arc<Mutex<HashSet<PathBuf>>>,
}
impl ClaimSet {
pub fn new() -> Self {
Self::default()
}
pub fn claim(&self, path: &Path) -> Option<Claim> {
let mut held = self.held.lock().unwrap_or_else(|e| e.into_inner());
if !held.insert(path.to_path_buf()) {
return None;
}
Some(Claim {
held: Arc::clone(&self.held),
path: path.to_path_buf(),
})
}
pub fn len(&self) -> usize {
self.held.lock().unwrap_or_else(|e| e.into_inner()).len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Debug)]
pub struct Claim {
held: Arc<Mutex<HashSet<PathBuf>>>,
path: PathBuf,
}
impl Drop for Claim {
fn drop(&mut self) {
self.held
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&self.path);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BackoffPolicy {
pub first_attempt_grace: Duration,
pub base: Duration,
pub ceiling: Duration,
pub max_attempts: u32,
}
impl Default for BackoffPolicy {
fn default() -> Self {
Self {
first_attempt_grace: super::relay::DEFAULT_RELAY_TIMEOUT,
base: Duration::from_secs(30),
ceiling: Duration::from_secs(3600),
max_attempts: 24,
}
}
}
impl BackoffPolicy {
pub fn delay_after(&self, attempts: u32) -> Duration {
if attempts == 0 {
return self.first_attempt_grace;
}
let shift = (attempts - 1).min(32);
let scaled = self
.base
.as_millis()
.saturating_mul(1u128 << shift)
.min(self.ceiling.as_millis());
Duration::from_millis(scaled.min(u128::from(u64::MAX)) as u64)
}
pub fn is_due(&self, entry: &SpoolEntry, now_unix_ms: u64) -> bool {
if entry.attempts >= self.max_attempts {
return false;
}
let since = entry
.last_attempt_at_unix_ms
.unwrap_or(entry.received_at_unix_ms);
let elapsed_ms = u128::from(now_unix_ms.saturating_sub(since));
elapsed_ms >= self.delay_after(entry.attempts).as_millis()
}
pub fn is_exhausted(&self, entry: &SpoolEntry) -> bool {
entry.attempts >= self.max_attempts
}
}