use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone, Debug, Default)]
pub struct RefreshInflight {
inner: Arc<Mutex<HashSet<(i64, String)>>>,
}
impl RefreshInflight {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(HashSet::new())),
}
}
pub async fn try_claim(&self, key: (i64, String)) -> bool {
let mut set = self.inner.lock().await;
set.insert(key)
}
pub async fn release(&self, key: &(i64, String)) {
let mut set = self.inner.lock().await;
set.remove(key);
}
#[cfg(test)]
pub async fn contains(&self, key: &(i64, String)) -> bool {
let set = self.inner.lock().await;
set.contains(key)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn try_claim_succeeds_for_free_slot() {
let inflight = RefreshInflight::new();
let key = (42, "duffel_token".to_string());
assert!(inflight.try_claim(key.clone()).await);
assert!(inflight.contains(&key).await);
}
#[tokio::test]
async fn try_claim_fails_for_held_slot() {
let inflight = RefreshInflight::new();
let key = (42, "duffel_token".to_string());
assert!(inflight.try_claim(key.clone()).await);
assert!(!inflight.try_claim(key.clone()).await);
}
#[tokio::test]
async fn release_allows_reclaim() {
let inflight = RefreshInflight::new();
let key = (42, "duffel_token".to_string());
assert!(inflight.try_claim(key.clone()).await);
inflight.release(&key).await;
assert!(!inflight.contains(&key).await);
assert!(inflight.try_claim(key.clone()).await);
}
#[tokio::test]
async fn distinct_keys_dont_collide() {
let inflight = RefreshInflight::new();
let key_a = (42, "duffel_token".to_string());
let key_b = (42, "openai_key".to_string());
let key_c = (43, "duffel_token".to_string());
assert!(inflight.try_claim(key_a.clone()).await);
assert!(inflight.try_claim(key_b.clone()).await);
assert!(inflight.try_claim(key_c.clone()).await);
assert!(inflight.contains(&key_a).await);
assert!(inflight.contains(&key_b).await);
assert!(inflight.contains(&key_c).await);
}
#[tokio::test]
async fn release_is_idempotent() {
let inflight = RefreshInflight::new();
let key = (42, "missing".to_string());
inflight.release(&key).await;
assert!(!inflight.contains(&key).await);
}
#[tokio::test]
async fn clone_shares_inner_state() {
let inflight = RefreshInflight::new();
let clone = inflight.clone();
let key = (42, "duffel_token".to_string());
assert!(inflight.try_claim(key.clone()).await);
assert!(!clone.try_claim(key.clone()).await);
clone.release(&key).await;
assert!(inflight.try_claim(key.clone()).await);
}
}