fix(routing): harden routed pool scheduling

This commit is contained in:
elky
2026-07-27 22:06:28 +08:00
parent 550cc36760
commit 4148ab1931
62 changed files with 5510 additions and 850 deletions
@@ -7,9 +7,10 @@ use std::time::Duration;
use aether_cache::ExpiringMap;
use aether_data::DataLayerError;
use aether_data_contracts::repository::candidate_selection::{
MinimalCandidateSelectionReadRepository, StoredMinimalCandidateSelectionRow,
StoredPoolKeyCandidateOrder, StoredPoolKeyCandidateRowsByKeyIdsQuery,
StoredPoolKeyCandidateRowsQuery, StoredRequestedModelCandidateRowsQuery,
MinimalCandidateSelectionReadRepository, StoredApiFormatCandidateRowsQuery,
StoredMinimalCandidateSelectionRow, StoredPoolKeyCandidateOrder,
StoredPoolKeyCandidateRowsByKeyIdsQuery, StoredPoolKeyCandidateRowsQuery,
StoredRequestedModelCandidateRowsQuery,
};
use async_trait::async_trait;
use tokio::sync::{Notify, OwnedSemaphorePermit, Semaphore};
@@ -389,6 +390,19 @@ impl MinimalCandidateSelectionReadRepository for CachedMinimalCandidateSelection
.await
}
async fn list_for_exact_api_format_page(
&self,
query: &StoredApiFormatCandidateRowsQuery,
) -> Result<Vec<StoredMinimalCandidateSelectionRow>, DataLayerError> {
let key = CandidateSelectionCacheKey::ApiFormatPage {
api_format: normalize_api_format_key(&query.api_format),
offset: query.offset,
limit: query.limit,
};
self.get_or_load(key, || self.inner.list_for_exact_api_format_page(query))
.await
}
async fn list_for_exact_api_format_and_global_model(
&self,
api_format: &str,
@@ -480,6 +494,11 @@ enum CandidateSelectionCacheKey {
ApiFormat {
api_format: String,
},
ApiFormatPage {
api_format: String,
offset: u32,
limit: u32,
},
ApiFormatAndGlobalModel {
api_format: String,
global_model_name: String,
@@ -1044,6 +1063,36 @@ mod tests {
assert!(cache.inflight.lock().unwrap().is_empty());
}
#[tokio::test]
async fn candidate_selection_cache_keys_api_format_pages_by_offset() {
let inner = Arc::new(StubCandidateSelectionRepository::new(Duration::ZERO));
let cache = CachedMinimalCandidateSelectionReadRepository::new(inner.clone());
let first = StoredApiFormatCandidateRowsQuery {
api_format: "openai:chat".to_string(),
offset: 0,
limit: 256,
};
let second = StoredApiFormatCandidateRowsQuery {
offset: 256,
..first.clone()
};
cache
.list_for_exact_api_format_page(&first)
.await
.expect("first page should load");
cache
.list_for_exact_api_format_page(&first)
.await
.expect("first page should be cached");
cache
.list_for_exact_api_format_page(&second)
.await
.expect("second page should load independently");
assert_eq!(inner.calls(), 2);
}
#[tokio::test]
async fn candidate_selection_cache_releases_inflight_when_leader_is_cancelled() {
let inner = Arc::new(FirstLoadPendingThenFastRepository::new());