use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use parking_lot::Mutex;
use tokio::sync::Notify;
struct Slot<T> {
result: Mutex<Option<T>>,
ready: Notify,
}
impl<T> Slot<T> {
fn new() -> Self {
Slot {
result: Mutex::new(None),
ready: Notify::new(),
}
}
}
type SlotMap<T> = Arc<Mutex<HashMap<String, Arc<Slot<T>>>>>;
#[derive(Clone)]
pub struct InflightMap<T> {
map: SlotMap<T>,
}
impl<T> Default for InflightMap<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> InflightMap<T> {
pub fn new() -> Self {
InflightMap {
map: Arc::new(Mutex::new(HashMap::new())),
}
}
}
struct FetchGuard<T> {
map: SlotMap<T>,
key: String,
slot: Arc<Slot<T>>,
registry: &'static str,
}
impl<T> Drop for FetchGuard<T> {
fn drop(&mut self) {
{
let mut map = self.map.lock();
if let Entry::Occupied(occ) = map.entry(self.key.clone()) {
if Arc::ptr_eq(occ.get(), &self.slot) {
occ.remove();
}
}
} self.slot.ready.notify_waiters();
crate::metrics::PROXY_INFLIGHT
.with_label_values(&[self.registry])
.dec();
}
}
impl<T> InflightMap<T>
where
T: Clone,
{
pub async fn coalesced<F, Fut>(
&self,
key: &str,
registry: &'static str,
budget: Duration,
fetch: F,
) -> Option<T>
where
F: FnOnce() -> Fut,
Fut: Future<Output = Option<T>>,
{
debug_assert!(!key.is_empty(), "coalesce key must not be empty");
let role = {
let mut map = self.map.lock();
match map.entry(key.to_string()) {
Entry::Vacant(v) => {
let slot = Arc::new(Slot::new());
v.insert(Arc::clone(&slot));
Role::Leader(slot)
}
Entry::Occupied(o) => Role::Follower(Arc::clone(o.get())),
}
};
match role {
Role::Leader(slot) => {
crate::metrics::PROXY_INFLIGHT
.with_label_values(&[registry])
.inc();
let _guard = FetchGuard {
map: Arc::clone(&self.map),
key: key.to_string(),
slot: Arc::clone(&slot),
registry,
};
let out = fetch().await;
if let Some(ref value) = out {
*slot.result.lock() = Some(value.clone());
}
out
}
Role::Follower(slot) => {
let notified = slot.ready.notified();
tokio::pin!(notified);
if let Some(value) = slot.result.lock().clone() {
return Some(Self::record_follower(registry, value));
}
let reason = match tokio::time::timeout(budget, &mut notified).await {
Ok(()) => {
if let Some(value) = slot.result.lock().clone() {
return Some(Self::record_follower(registry, value));
}
"leader"
}
Err(_) => {
"budget"
}
};
crate::metrics::PROXY_COALESCE_FALLTHROUGH_TOTAL
.with_label_values(&[registry, reason])
.inc();
fetch().await
}
}
}
fn record_follower(registry: &'static str, value: T) -> T {
crate::metrics::PROXY_COALESCED_TOTAL
.with_label_values(&[registry])
.inc();
value
}
}
enum Role<T> {
Leader(Arc<Slot<T>>),
Follower(Arc<Slot<T>>),
}
pub fn follower_budget(proxy_timeout_secs: u64) -> Duration {
Duration::from_secs(2 * proxy_timeout_secs + 3)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use tokio::sync::Barrier;
fn budget() -> Duration {
Duration::from_secs(5)
}
#[tokio::test]
async fn coalesces_concurrent_callers_to_single_fetch() {
let map: InflightMap<u64> = InflightMap::new();
let calls = Arc::new(AtomicUsize::new(0));
const M: usize = 32;
let gate = Arc::new(Barrier::new(M));
let mut handles = Vec::new();
for _ in 0..M {
let map = map.clone();
let calls = Arc::clone(&calls);
let gate = Arc::clone(&gate);
handles.push(tokio::spawn(async move {
gate.wait().await;
map.coalesced("pkg", "test", budget(), || async {
calls.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(50)).await;
Some(7u64)
})
.await
}));
}
let results: Vec<_> = futures::future::join_all(handles).await;
for r in results {
assert_eq!(r.unwrap(), Some(7u64), "every caller gets the value");
}
assert_eq!(
calls.load(Ordering::SeqCst),
1,
"exactly one upstream fetch for M concurrent callers"
);
assert!(
map.map.lock().is_empty(),
"slot is removed after the leader finishes"
);
}
#[tokio::test]
async fn distinct_keys_do_not_block() {
let map: InflightMap<u64> = InflightMap::new();
let calls = Arc::new(AtomicUsize::new(0));
let a = {
let map = map.clone();
let calls = Arc::clone(&calls);
tokio::spawn(async move {
map.coalesced("a", "test", budget(), || async {
calls.fetch_add(1, Ordering::SeqCst);
Some(1u64)
})
.await
})
};
let b = {
let map = map.clone();
let calls = Arc::clone(&calls);
tokio::spawn(async move {
map.coalesced("b", "test", budget(), || async {
calls.fetch_add(1, Ordering::SeqCst);
Some(2u64)
})
.await
})
};
assert_eq!(a.await.unwrap(), Some(1));
assert_eq!(b.await.unwrap(), Some(2));
assert_eq!(calls.load(Ordering::SeqCst), 2, "each distinct key fetched");
}
#[tokio::test]
async fn leader_failure_falls_through_to_followers() {
let map: InflightMap<u64> = InflightMap::new();
let calls = Arc::new(AtomicUsize::new(0));
const M: usize = 8;
let gate = Arc::new(Barrier::new(M));
let mut handles = Vec::new();
for _ in 0..M {
let map = map.clone();
let calls = Arc::clone(&calls);
let gate = Arc::clone(&gate);
handles.push(tokio::spawn(async move {
gate.wait().await;
map.coalesced("pkg", "test", budget(), || async {
let n = calls.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(30)).await;
if n == 0 {
None
} else {
Some(99u64)
}
})
.await
}));
}
let results: Vec<_> = futures::future::join_all(handles).await;
let successes = results
.iter()
.filter(|r| r.as_ref().unwrap() == &Some(99))
.count();
assert!(
successes >= 1,
"followers fall through and fetch after the leader fails"
);
assert!(
calls.load(Ordering::SeqCst) >= 2,
"leader failure forces followers to their own fetch"
);
}
#[tokio::test]
async fn leader_cancellation_releases_key() {
let map: InflightMap<u64> = InflightMap::new();
let calls = Arc::new(AtomicUsize::new(0));
let leader = {
let map = map.clone();
let calls = Arc::clone(&calls);
tokio::spawn(async move {
map.coalesced("pkg", "test", budget(), || async {
calls.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_secs(60)).await;
Some(1u64)
})
.await
})
};
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(!map.map.lock().is_empty(), "leader registered its slot");
leader.abort();
tokio::time::sleep(Duration::from_millis(50)).await;
assert!(
map.map.lock().is_empty(),
"cancelled leader's slot is released on drop"
);
let fresh = map
.coalesced("pkg", "test", budget(), || async {
calls.fetch_add(1, Ordering::SeqCst);
Some(2u64)
})
.await;
assert_eq!(fresh, Some(2), "subsequent request fetches with no stall");
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"cancelled leader + one clean refetch"
);
}
#[test]
fn budget_scales_with_timeout() {
assert_eq!(follower_budget(30), Duration::from_secs(63));
assert_eq!(follower_budget(0), Duration::from_secs(3));
}
}