use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use dashmap::DashMap;
use tokio::sync::broadcast;
use tower::{Layer, Service};
use super::cache::{CachedResponse, record_cache_state};
use super::types::{LlmRequest, LlmRequestKind, LlmResponse};
use crate::client::BoxFuture;
use crate::error::{LiterLlmError, Result};
use crate::observability::usage::CacheState;
type InFlightMap = Arc<DashMap<u64, broadcast::Sender<SingleflightResult>>>;
pub type SingleflightResult = std::result::Result<CachedResponse, Arc<LiterLlmError>>;
pub enum SingleflightHandle {
Leader {
complete: Box<dyn FnOnce(SingleflightResult) + Send>,
},
Follower {
recv: broadcast::Receiver<SingleflightResult>,
},
}
#[cfg_attr(alef, alef(skip))]
pub trait SingleflightCoordinator: Send + Sync + 'static {
fn join<'a>(&'a self, key: u64) -> Pin<Box<dyn Future<Output = SingleflightHandle> + Send + 'a>>;
}
#[cfg_attr(alef, alef(skip))]
pub struct InMemorySingleflight {
in_flight: InFlightMap,
}
impl Default for InMemorySingleflight {
fn default() -> Self {
Self {
in_flight: Arc::new(DashMap::new()),
}
}
}
impl InMemorySingleflight {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl SingleflightCoordinator for InMemorySingleflight {
fn join<'a>(&'a self, key: u64) -> Pin<Box<dyn Future<Output = SingleflightHandle> + Send + 'a>> {
Box::pin(async move {
use dashmap::mapref::entry::Entry;
match self.in_flight.entry(key) {
Entry::Vacant(slot) => {
let (tx, _) = broadcast::channel::<SingleflightResult>(1);
slot.insert(tx.clone());
let map = Arc::clone(&self.in_flight);
let guard = LeaderDropGuard {
map: Arc::clone(&map),
key,
disarmed: false,
};
let complete = Box::new(move |result: SingleflightResult| {
let mut g = guard;
g.disarmed = true;
let _ = tx.send(result);
map.remove(&key);
});
SingleflightHandle::Leader { complete }
}
Entry::Occupied(entry) => {
let recv = entry.get().subscribe();
SingleflightHandle::Follower { recv }
}
}
})
}
}
struct LeaderDropGuard {
map: InFlightMap,
key: u64,
disarmed: bool,
}
impl Drop for LeaderDropGuard {
fn drop(&mut self) {
if !self.disarmed {
self.map.remove(&self.key);
}
}
}
#[cfg_attr(alef, alef(skip))]
pub struct SingleflightLayer<C: SingleflightCoordinator> {
coordinator: Arc<C>,
}
impl<C: SingleflightCoordinator> SingleflightLayer<C> {
#[must_use]
pub fn new(coordinator: Arc<C>) -> Self {
Self { coordinator }
}
}
impl<C: SingleflightCoordinator, S> Layer<S> for SingleflightLayer<C> {
type Service = SingleflightService<C, S>;
fn layer(&self, inner: S) -> Self::Service {
SingleflightService {
coordinator: Arc::clone(&self.coordinator),
inner,
}
}
}
#[cfg_attr(alef, alef(skip))]
pub struct SingleflightService<C: SingleflightCoordinator, S> {
coordinator: Arc<C>,
inner: S,
}
impl<C: SingleflightCoordinator, S: Clone> Clone for SingleflightService<C, S> {
fn clone(&self) -> Self {
Self {
coordinator: Arc::clone(&self.coordinator),
inner: self.inner.clone(),
}
}
}
fn singleflight_key(req: &LlmRequest) -> Option<u64> {
use std::hash::{DefaultHasher, Hash, Hasher};
let json = match &req.kind {
LlmRequestKind::Chat(r) => serde_json::to_string(r).ok()?,
LlmRequestKind::Embed(r) => serde_json::to_string(r).ok()?,
_ => return None,
};
let mut hasher = DefaultHasher::new();
req.tenant_id.hash(&mut hasher);
json.hash(&mut hasher);
Some(hasher.finish())
}
impl<C, S> Service<LlmRequest> for SingleflightService<C, S>
where
C: SingleflightCoordinator,
S: Service<LlmRequest, Response = LlmResponse, Error = LiterLlmError> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = LlmResponse;
type Error = LiterLlmError;
type Future = BoxFuture<'static, Result<LlmResponse>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<()>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: LlmRequest) -> Self::Future {
let key = singleflight_key(&req);
let Some(key) = key else {
let fut = self.inner.call(req);
#[allow(clippy::redundant_async_block)]
return Box::pin(async move { fut.await });
};
let coordinator = Arc::clone(&self.coordinator);
let clone = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, clone);
Box::pin(async move {
match coordinator.join(key).await {
SingleflightHandle::Leader { complete } => {
let result = inner.call(req).await;
let sf_result: SingleflightResult = match &result {
Ok(resp) => match resp {
LlmResponse::Chat(r) => Ok(CachedResponse::Chat(r.clone())),
LlmResponse::Embed(r) => Ok(CachedResponse::Embed(r.clone())),
_ => Err(Arc::new(LiterLlmError::InternalError {
message: "singleflight: non-cacheable response variant in leader".into(),
})),
},
Err(e) => Err(Arc::new(e.to_singleflight_error())),
};
complete(sf_result);
result
}
SingleflightHandle::Follower { mut recv } => {
drop(inner);
match recv.recv().await {
Ok(Ok(cached)) => {
record_cache_state(CacheState::ExactHit);
cached.into_llm_response()
}
Ok(Err(arc_err)) => {
Err(Arc::try_unwrap(arc_err).unwrap_or_else(|arc| arc.to_singleflight_error()))
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(n)) => {
tracing::debug!(skipped = n, "singleflight follower lagged; resubscribing");
let mut rx2 = recv.resubscribe();
match rx2.recv().await {
Ok(Ok(cached)) => {
record_cache_state(CacheState::ExactHit);
cached.into_llm_response()
}
Ok(Err(arc_err)) => {
Err(Arc::try_unwrap(arc_err).unwrap_or_else(|arc| arc.to_singleflight_error()))
}
Err(_) => Err(LiterLlmError::InternalError {
message: "singleflight: follower lagged and retry also failed".into(),
}),
}
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => Err(LiterLlmError::InternalError {
message: "singleflight: leader closed channel without sending a result".into(),
}),
}
}
}
})
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::Ordering;
use super::*;
use crate::tower::service::LlmService;
use crate::tower::tests_common::{MockClient, chat_req};
use crate::tower::types::LlmRequest;
#[derive(Clone)]
struct SlowClient {
inner: MockClient,
delay: std::time::Duration,
}
impl SlowClient {
fn ok_with_delay(delay: std::time::Duration) -> Self {
Self {
inner: MockClient::ok(),
delay,
}
}
}
impl crate::client::LlmClient for SlowClient {
fn chat(
&self,
req: crate::types::ChatCompletionRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::ChatCompletionResponse>> {
let delay = self.delay;
let inner_fut = self.inner.chat(req);
Box::pin(async move {
tokio::time::sleep(delay).await;
inner_fut.await
})
}
fn chat_stream(
&self,
req: crate::types::ChatCompletionRequest,
) -> crate::client::BoxFuture<
'_,
crate::error::Result<
crate::client::BoxStream<'static, crate::error::Result<crate::types::ChatCompletionChunk>>,
>,
> {
self.inner.chat_stream(req)
}
fn embed(
&self,
req: crate::types::EmbeddingRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::EmbeddingResponse>> {
self.inner.embed(req)
}
fn list_models(&self) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::ModelsListResponse>> {
self.inner.list_models()
}
fn image_generate(
&self,
req: crate::types::image::CreateImageRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::image::ImagesResponse>> {
self.inner.image_generate(req)
}
fn speech(
&self,
req: crate::types::audio::CreateSpeechRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<bytes::Bytes>> {
self.inner.speech(req)
}
fn transcribe(
&self,
req: crate::types::audio::CreateTranscriptionRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::audio::TranscriptionResponse>> {
self.inner.transcribe(req)
}
fn moderate(
&self,
req: crate::types::moderation::ModerationRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::moderation::ModerationResponse>> {
self.inner.moderate(req)
}
fn rerank(
&self,
req: crate::types::rerank::RerankRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::rerank::RerankResponse>> {
self.inner.rerank(req)
}
fn search(
&self,
req: crate::types::search::SearchRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::search::SearchResponse>> {
self.inner.search(req)
}
fn ocr(
&self,
req: crate::types::ocr::OcrRequest,
) -> crate::client::BoxFuture<'_, crate::error::Result<crate::types::ocr::OcrResponse>> {
self.inner.ocr(req)
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_leader_runs_upstream_once_under_burst() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(100));
let handles: Vec<_> = (0..100)
.map(|_| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
use tower::Service as _;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 100, "all 100 callers should get a successful response");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 1,
"inner service must be called exactly once under burst; got {calls}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_followers_get_same_result() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let models: Vec<String> = results
.into_iter()
.map(|join_result| {
let llm_resp = join_result
.expect("task did not panic")
.expect("service call succeeded");
match llm_resp {
LlmResponse::Chat(r) => r.model,
_ => panic!("expected Chat response"),
}
})
.collect();
let first = &models[0];
assert!(
models.iter().all(|m| m == first),
"all followers must receive the same result"
);
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 1,
"followers must actually be deduplicated, not merely coincide on a fixed mock \
response; inner service called {calls} times, expected exactly 1"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_leader_error_propagates_to_followers() {
let inner_client = MockClient::failing_rate_limited();
let slow_client = SlowClient {
inner: inner_client,
delay: std::time::Duration::from_millis(50),
};
let call_count = Arc::clone(&slow_client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let svc = layer.layer(LlmService::new(slow_client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let error_count = results.iter().filter(|r| r.as_ref().unwrap().is_err()).count();
assert_eq!(error_count, 10, "all callers must receive the leader's error");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 1,
"inner should be called exactly once under singleflight; got {calls}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_follower_does_not_call_inner_service() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
use tower::Service as _;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 10, "all 10 callers should succeed");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 1,
"inner service must be called exactly once (leader only); followers must not call it; got {calls}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_concurrent_keys_dont_dedupe() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(20));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10u32)
.map(|i| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
use tower::Service as _;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req(&format!("gpt-4-model-{i}")))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 10, "all 10 distinct-key callers should succeed");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 10,
"each distinct key must produce its own upstream call; got {calls}"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_n100_burst_one_inner_call_only() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(100));
let handles: Vec<_> = (0..100)
.map(|_| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 100, "all 100 callers should get a successful response");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(calls, 1, "inner service called {calls} times; expected exactly 1");
let models: Vec<String> = results
.into_iter()
.map(|r| match r.unwrap().unwrap() {
LlmResponse::Chat(resp) => resp.model,
_ => panic!("expected Chat response"),
})
.collect();
let first = &models[0];
assert!(
models.iter().all(|m| m == first),
"all 100 callers must receive identical responses"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_leader_cancelled_followers_receive_cancellation() {
let coordinator = Arc::new(InMemorySingleflight::new());
let key: u64 = 0xDEAD_BEEF;
let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<()>();
let all_subscribed = Arc::new(tokio::sync::Barrier::new(11));
let leader_handle = tokio::spawn({
let coordinator = Arc::clone(&coordinator);
async move {
let handle = coordinator.join(key).await;
match handle {
SingleflightHandle::Leader { complete: _complete } => {
let _ = ready_tx.send(());
std::future::pending::<()>().await;
}
SingleflightHandle::Follower { .. } => panic!("first join must be Leader"),
}
}
});
ready_rx.await.expect("leader must signal readiness");
let follower_handles: Vec<_> = (0..10)
.map(|_| {
let coordinator = Arc::clone(&coordinator);
let barrier = Arc::clone(&all_subscribed);
tokio::spawn(async move {
let recv = match coordinator.join(key).await {
SingleflightHandle::Follower { recv } => recv,
SingleflightHandle::Leader { .. } => panic!("subsequent joins must be Follower"),
};
barrier.wait().await;
let mut recv = recv;
recv.recv().await
})
})
.collect();
all_subscribed.wait().await;
leader_handle.abort();
let _ = leader_handle.await;
for handle in follower_handles {
let result = handle.await.expect("follower task must not panic");
assert!(
matches!(result, Err(tokio::sync::broadcast::error::RecvError::Closed)),
"follower must receive RecvError::Closed when leader is cancelled; got {result:?}"
);
}
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_leader_error_broadcast_to_followers() {
let inner_client = MockClient::failing_rate_limited();
let slow_client = SlowClient {
inner: inner_client,
delay: std::time::Duration::from_millis(50),
};
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let svc = layer.layer(LlmService::new(slow_client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
svc.call(LlmRequest::Chat(chat_req("gpt-4"))).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
for (i, result) in results.into_iter().enumerate() {
let err = result
.unwrap_or_else(|e| panic!("task {i} panicked: {e}"))
.expect_err("all callers must receive an error");
assert!(
matches!(err, LiterLlmError::RateLimited { .. }),
"caller {i} got {err:?}; expected RateLimited (variant must be preserved across broadcast)"
);
}
}
#[tokio::test]
async fn singleflight_follower_subscribed_before_complete_gets_result() {
let coordinator = Arc::new(InMemorySingleflight::new());
let key: u64 = 0xC0FF_EE00;
let complete = match coordinator.join(key).await {
SingleflightHandle::Leader { complete } => complete,
SingleflightHandle::Follower { .. } => panic!("first join must be Leader"),
};
let mut recv = match coordinator.join(key).await {
SingleflightHandle::Follower { recv } => recv,
SingleflightHandle::Leader { .. } => panic!("second join must be Follower"),
};
complete(Ok(CachedResponse::Chat(
crate::tower::tests_common::make_chat_response("gpt-4"),
)));
let received = recv.recv().await.expect("follower must receive leader result");
assert!(received.is_ok(), "follower must receive success result");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_cross_tenant_identical_body_does_not_dedupe() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(2));
let tenants = ["tenant-a", "tenant-b"];
let handles: Vec<_> = tenants
.iter()
.map(|tenant| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
let tenant = (*tenant).to_owned();
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
let req = LlmRequest::Chat(chat_req("gpt-4")).with_tenant_id(tenant);
svc.call(req).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 2, "both distinct-tenant callers should succeed");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 2,
"identical bodies from different tenants must NOT dedupe; got {calls} upstream call(s)"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn singleflight_same_tenant_identical_requests_still_dedupe() {
let client = SlowClient::ok_with_delay(std::time::Duration::from_millis(50));
let call_count = Arc::clone(&client.inner.call_count);
let coordinator = Arc::new(InMemorySingleflight::new());
let layer = SingleflightLayer::new(Arc::clone(&coordinator));
let barrier = Arc::new(tokio::sync::Barrier::new(10));
let handles: Vec<_> = (0..10)
.map(|_| {
let svc = layer.layer(LlmService::new(client.clone()));
let barrier = Arc::clone(&barrier);
tokio::spawn(async move {
barrier.wait().await;
let mut svc = svc;
futures_util::future::poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
let req = LlmRequest::Chat(chat_req("gpt-4")).with_tenant_id("tenant-a");
svc.call(req).await
})
})
.collect();
let results: Vec<_> = futures_util::future::join_all(handles).await;
let success_count = results.iter().filter(|r| r.as_ref().unwrap().is_ok()).count();
assert_eq!(success_count, 10, "all same-tenant callers should succeed");
let calls = call_count.load(Ordering::SeqCst);
assert_eq!(
calls, 1,
"identical same-tenant requests must still dedupe; got {calls} upstream call(s)"
);
}
#[test]
fn singleflight_key_none_tenant_is_unambiguous_vs_empty_and_literal_none() {
let none_req = LlmRequest::Chat(chat_req("gpt-4"));
let empty_req = LlmRequest::Chat(chat_req("gpt-4")).with_tenant_id("");
let literal_req = LlmRequest::Chat(chat_req("gpt-4")).with_tenant_id("none");
let none_key = singleflight_key(&none_req).expect("chat requests are keyable");
let empty_key = singleflight_key(&empty_req).expect("chat requests are keyable");
let literal_key = singleflight_key(&literal_req).expect("chat requests are keyable");
assert_ne!(none_key, empty_key, "None tenant must not collide with tenant \"\"");
assert_ne!(
none_key, literal_key,
"None tenant must not collide with tenant \"none\""
);
assert_ne!(
empty_key, literal_key,
"tenant \"\" must not collide with tenant \"none\""
);
}
}