use std::future::Future;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use dashmap::DashMap;
use futures_util::FutureExt;
use futures_util::future::{BoxFuture, Shared};
use tonic::Status;
use tracing::Instrument;
pub trait KeyFn: Send + Sync + 'static {
fn derive(&self, service: &str, method: &str, request_bytes: &[u8]) -> Vec<u8>;
}
pub struct DefaultKeyFn;
impl KeyFn for DefaultKeyFn {
fn derive(&self, service: &str, method: &str, request_bytes: &[u8]) -> Vec<u8> {
let mut key =
Vec::with_capacity(service.len() + 1 + method.len() + 1 + request_bytes.len());
key.extend_from_slice(service.as_bytes());
key.push(0);
key.extend_from_slice(method.as_bytes());
key.push(0);
key.extend_from_slice(request_bytes);
key
}
}
#[derive(Clone, PartialEq, Eq)]
struct FlightKey(Vec<u8>);
impl Hash for FlightKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
pub fn key_hash_hex(key: &[u8]) -> String {
use std::collections::hash_map::DefaultHasher;
let mut h = DefaultHasher::new();
key.hash(&mut h);
let v = std::hash::Hasher::finish(&h);
format!("{v:016x}")
}
type Flight<R> = Shared<BoxFuture<'static, Result<R, Status>>>;
pub struct Singleflight<R> {
in_flight: Arc<DashMap<FlightKey, Flight<R>>>,
}
impl<R: Clone + Send + Sync + 'static> Singleflight<R> {
pub fn new() -> Self {
Self {
in_flight: Arc::new(DashMap::new()),
}
}
pub async fn run<F>(&self, key: Vec<u8>, service: &str, method: &str, f: F) -> Result<R, Status>
where
F: Future<Output = Result<R, Status>> + Send + 'static,
{
let fkey = FlightKey(key.clone());
let hash = key_hash_hex(&key);
let (handle, role) = {
use dashmap::mapref::entry::Entry;
match self.in_flight.entry(fkey.clone()) {
Entry::Occupied(e) => (e.get().clone(), "waiter"),
Entry::Vacant(e) => {
let map = Arc::clone(&self.in_flight);
let fkey2 = fkey.clone();
let shared: Flight<R> = async move {
let result = f.await;
map.remove(&fkey2);
result
}
.boxed()
.shared();
e.insert(shared.clone());
(shared, "driver")
}
}
};
let span = tracing::debug_span!(
"coalesce",
flight.role = role,
flight.key_hash = %hash,
otel.name = format!("{service}/{method}"),
);
handle.instrument(span).await
}
pub fn in_flight_count(&self) -> usize {
self.in_flight.len()
}
}
impl<R: Clone + Send + Sync + 'static> Default for Singleflight<R> {
fn default() -> Self {
Self::new()
}
}
impl<R: Clone + Send + Sync + 'static> Clone for Singleflight<R> {
fn clone(&self) -> Self {
Self {
in_flight: Arc::clone(&self.in_flight),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
#[tokio::test]
async fn concurrent_identical_keys_share_one_call() {
let sf: Arc<Singleflight<String>> = Arc::new(Singleflight::new());
let counter = Arc::new(AtomicU32::new(0));
let mut handles = Vec::new();
for _ in 0..8 {
let sf = sf.clone();
let counter = counter.clone();
handles.push(tokio::spawn(async move {
sf.run(
b"svc\x00method\x00req".to_vec(),
"svc",
"method",
async move {
counter.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(20)).await;
Ok::<String, Status>("result".into())
},
)
.await
}));
}
let results: Vec<_> = futures_util::future::join_all(handles)
.await
.into_iter()
.map(|r| r.expect("task panicked"))
.collect();
assert_eq!(counter.load(Ordering::SeqCst), 1);
for r in results {
assert_eq!(r.unwrap(), "result");
}
assert_eq!(sf.in_flight_count(), 0);
}
#[tokio::test]
async fn different_keys_are_not_coalesced() {
let sf: Arc<Singleflight<String>> = Arc::new(Singleflight::new());
let counter = Arc::new(AtomicU32::new(0));
let mut handles = Vec::new();
for i in 0u8..4 {
let sf = sf.clone();
let counter = counter.clone();
let key = vec![i];
handles.push(tokio::spawn(async move {
sf.run(key, "svc", "method", async move {
counter.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(10)).await;
Ok::<String, Status>(format!("result-{i}"))
})
.await
}));
}
futures_util::future::join_all(handles).await;
assert_eq!(counter.load(Ordering::SeqCst), 4);
}
#[tokio::test]
async fn differing_only_in_nested_field_not_coalesced() {
let sf: Arc<Singleflight<u32>> = Arc::new(Singleflight::new());
let counter = Arc::new(AtomicU32::new(0));
let key_a = b"req\x01consistency=strong".to_vec();
let key_b = b"req\x01consistency=eventual".to_vec();
let sf_a = sf.clone();
let c_a = counter.clone();
let h_a = tokio::spawn(async move {
sf_a.run(key_a, "svc", "m", async move {
c_a.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(10)).await;
Ok::<u32, Status>(1)
})
.await
});
let sf_b = sf.clone();
let c_b = counter.clone();
let h_b = tokio::spawn(async move {
sf_b.run(key_b, "svc", "m", async move {
c_b.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(10)).await;
Ok::<u32, Status>(2)
})
.await
});
let (ra, rb) = tokio::join!(h_a, h_b);
assert_eq!(ra.unwrap().unwrap(), 1);
assert_eq!(rb.unwrap().unwrap(), 2);
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
#[tokio::test]
async fn error_is_shared_but_not_cached() {
let sf: Arc<Singleflight<String>> = Arc::new(Singleflight::new());
let counter = Arc::new(AtomicU32::new(0));
let mut handles = Vec::new();
for _ in 0..3 {
let sf = sf.clone();
let counter = counter.clone();
handles.push(tokio::spawn(async move {
sf.run(b"key".to_vec(), "s", "m", async move {
counter.fetch_add(1, Ordering::SeqCst);
tokio::time::sleep(Duration::from_millis(5)).await;
Err::<String, Status>(Status::internal("boom"))
})
.await
}));
}
let results = futures_util::future::join_all(handles).await;
assert_eq!(counter.load(Ordering::SeqCst), 1); for r in results {
assert!(r.unwrap().is_err()); }
let counter2 = counter.clone();
let result = sf
.run(b"key".to_vec(), "s", "m", async move {
counter2.fetch_add(1, Ordering::SeqCst);
Ok::<String, Status>("retry ok".into())
})
.await;
assert_eq!(counter.load(Ordering::SeqCst), 2); assert_eq!(result.unwrap(), "retry ok");
}
#[tokio::test]
async fn dropped_waiter_does_not_break_others() {
let sf: Arc<Singleflight<String>> = Arc::new(Singleflight::new());
let sf2 = sf.clone();
let survivor = tokio::spawn(async move {
sf2.run(b"k".to_vec(), "s", "m", async {
tokio::time::sleep(Duration::from_millis(30)).await;
Ok::<String, Status>("alive".into())
})
.await
});
let sf3 = sf.clone();
let doomed = tokio::spawn(async move {
sf3.run(b"k".to_vec(), "s", "m", async {
tokio::time::sleep(Duration::from_millis(30)).await;
Ok::<String, Status>("alive".into())
})
.await
});
tokio::time::sleep(Duration::from_millis(5)).await;
doomed.abort();
let r = survivor.await.expect("task panicked");
assert_eq!(r.unwrap(), "alive");
}
}