#![allow(
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic
)]
use super::*;
use crate::proxy::types::{CacheStatus, SearchResult};
use std::sync::Arc;
use tokio::sync::Barrier;
fn make_response(content: &str) -> QueryResponse {
QueryResponse {
results: vec![SearchResult {
id: "test".to_string(),
score: 1.0,
content: content.to_string(),
metadata: None,
upstream_id: None,
}],
cache_status: CacheStatus::Miss,
took_ms: 10,
generated_at: None,
miss_reason: None,
}
}
#[tokio::test]
async fn test_concurrent_insert_respects_max_entries() {
let max_entries = 500;
let num_tasks = 100;
let ops_per_task = 1000;
let store = Arc::new(CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
));
let barrier = Arc::new(Barrier::new(num_tasks));
let mut handles = Vec::with_capacity(num_tasks);
for task_id in 0..num_tasks {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for op in 0..ops_per_task {
let query = format!("task-{}-query-{}", task_id, op);
let response = make_response(&format!("result-{}-{}", task_id, op));
store.insert(&query, response, format!("upstream-{}", task_id));
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let final_len = store.len();
assert!(
final_len <= max_entries,
"Cache size {} exceeded max_entries {}",
final_len,
max_entries,
);
store.insert(
"post-stress-query",
make_response("post-stress"),
"upstream-post".to_string(),
);
assert!(store.get("post-stress-query").is_some());
}
#[tokio::test]
async fn test_concurrent_mixed_reads_and_writes() {
let max_entries = 1000;
let num_tasks = 100;
let ops_per_task = 500;
let store = Arc::new(CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
));
for i in 0..100 {
let query = format!("seed-query-{}", i);
store.insert(
&query,
make_response(&format!("seed-{}", i)),
"seed-upstream".to_string(),
);
}
let barrier = Arc::new(Barrier::new(num_tasks));
let mut handles = Vec::with_capacity(num_tasks);
for task_id in 0..num_tasks {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
if task_id % 2 == 0 {
for op in 0..ops_per_task {
let query = format!("write-{}-{}", task_id, op);
store.insert(
&query,
make_response(&format!("w-{}-{}", task_id, op)),
"writer".to_string(),
);
}
} else {
for op in 0..ops_per_task {
let seed_idx = op % 100;
let _entry = store.get(&format!("seed-query-{}", seed_idx));
let _entry = store.get(&format!("write-{}-{}", task_id - 1, op));
let _len = store.len();
}
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let final_len = store.len();
assert!(
final_len <= max_entries,
"Cache size {} exceeded max_entries {}",
final_len,
max_entries,
);
}
#[tokio::test]
async fn test_sustained_inserts_size_tracking_consistency() {
let max_entries = 200;
let total_inserts = 5000;
let num_tasks = 50;
let inserts_per_task = total_inserts / num_tasks;
let store = Arc::new(CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
));
let barrier = Arc::new(Barrier::new(num_tasks));
let mut handles = Vec::with_capacity(num_tasks);
for task_id in 0..num_tasks {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for op in 0..inserts_per_task {
let query = format!("sustained-{}-{}", task_id, op);
let response = make_response(&format!("data-{}-{}", task_id, op));
store.insert(&query, response, "sustained-upstream".to_string());
if op % 20 == 0 {
let len = store.len();
assert!(
len <= max_entries + num_tasks, "Cache size {} exceeded expected max {} during inserts (task {}, op {})",
len,
max_entries + num_tasks,
task_id,
op,
);
}
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let final_len = store.len();
assert!(
final_len <= max_entries,
"Final cache size {} exceeded max_entries {}",
final_len,
max_entries,
);
let stats = store.stats();
assert_eq!(
stats.total, final_len,
"stats().total ({}) should match len() ({})",
stats.total, final_len,
);
assert_eq!(stats.max_entries, max_entries);
assert!(
stats.utilization >= 0.0 && stats.utilization <= 1.0,
"Utilization {} should be between 0.0 and 1.0",
stats.utilization,
);
let total_inserts_attempted = num_tasks * inserts_per_task;
assert!(
final_len < total_inserts_attempted,
"Eviction must have occurred: final_len ({}) should be less than total inserts ({})",
final_len,
total_inserts_attempted,
);
let memory = store.memory_usage();
assert!(
memory > 0,
"Memory usage should be non-zero with {} entries",
final_len,
);
}
#[tokio::test]
async fn test_integrity_after_concurrent_stress() {
let max_entries = 300;
let num_tasks = 50;
let ops_per_task = 200;
let store = Arc::new(CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
));
let barrier = Arc::new(Barrier::new(num_tasks));
let mut handles = Vec::with_capacity(num_tasks);
for task_id in 0..num_tasks {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for op in 0..ops_per_task {
let query = format!("integrity-{}-{}", task_id, op);
let response = make_response(&format!("content-{}-{}", task_id, op));
store.insert(&query, response, format!("upstream-{}", task_id % 5));
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let report = store.verify_integrity();
assert_eq!(
report.invalid, 0,
"Expected 0 invalid entries after stress, found {}",
report.invalid,
);
assert_eq!(
report.total,
store.len(),
"Integrity report total ({}) should match len() ({})",
report.total,
store.len(),
);
assert_eq!(
report.missing_hash, 0,
"Expected 0 entries with missing hash, found {}",
report.missing_hash,
);
}
#[tokio::test]
async fn test_concurrent_clear_during_inserts() {
let max_entries = 500;
let num_writers = 20;
let ops_per_writer = 500;
let store = Arc::new(CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
));
let barrier = Arc::new(Barrier::new(num_writers + 1)); let mut handles = Vec::with_capacity(num_writers + 1);
for task_id in 0..num_writers {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for op in 0..ops_per_writer {
let query = format!("clear-test-{}-{}", task_id, op);
store.insert(
&query,
make_response(&format!("v-{}-{}", task_id, op)),
"upstream".to_string(),
);
}
}));
}
{
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for _ in 0..10 {
tokio::time::sleep(Duration::from_millis(5)).await;
store.clear();
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let final_len = store.len();
assert!(
final_len <= max_entries,
"Cache size {} exceeded max_entries {} after concurrent clears",
final_len,
max_entries,
);
let stats = store.stats();
assert_eq!(stats.total, final_len);
}
#[tokio::test]
async fn test_per_upstream_limit_under_stress() {
let max_entries = 1000;
let per_upstream_limit = 50;
let num_upstreams = 10;
let inserts_per_upstream = 200;
let mut store = CacheStore::new(
Duration::from_secs(300),
Duration::from_secs(3600),
max_entries,
);
store.set_per_upstream_limit(Some(per_upstream_limit));
let store = Arc::new(store);
let barrier = Arc::new(Barrier::new(num_upstreams));
let mut handles = Vec::with_capacity(num_upstreams);
for upstream_id in 0..num_upstreams {
let store = store.clone();
let barrier = barrier.clone();
handles.push(tokio::spawn(async move {
barrier.wait().await;
for op in 0..inserts_per_upstream {
let query = format!("upstream-{}-query-{}", upstream_id, op);
let response = make_response(&format!("data-{}-{}", upstream_id, op));
store.insert(&query, response, format!("upstream-{}", upstream_id));
}
}));
}
for handle in handles {
handle.await.unwrap();
}
let by_upstream = store.entries_by_upstream();
for (upstream, count) in &by_upstream {
assert!(
*count <= per_upstream_limit + num_upstreams,
"Upstream {} has {} entries, expected <= {} (limit {} + margin {})",
upstream,
count,
per_upstream_limit + num_upstreams,
per_upstream_limit,
num_upstreams,
);
}
assert!(
store.len() <= max_entries,
"Total cache size {} exceeded max_entries {}",
store.len(),
max_entries,
);
}