#![cfg(all(test, feature = "future"))]
use moka::{future::Cache, policy::EvictionPolicy};
use std::sync::Arc;
#[tokio::test(flavor = "multi_thread", worker_threads = 8)]
async fn eviction_converges_under_concurrent_key_reuse() {
const MAX_CAPACITY: u64 = 10 * 1024 * 1024;
const VALUE_SIZE: usize = 64 * 1024;
const WRITERS_PER_GROUP: usize = 10;
const INSERTS_PER_WRITER: u64 = 1_000;
const REUSED_KEY_POOL: u64 = 500;
const DRAIN_ROUNDS: usize = 64;
let cache: Cache<u64, Vec<u8>> = Cache::builder()
.max_capacity(MAX_CAPACITY)
.weigher(|_k, v: &Vec<u8>| v.len() as u32)
.eviction_policy(EvictionPolicy::lru())
.build();
let barrier = Arc::new(tokio::sync::Barrier::new(WRITERS_PER_GROUP * 2));
let mut handles = Vec::with_capacity(WRITERS_PER_GROUP * 2);
for _ in 0..WRITERS_PER_GROUP {
let cache = cache.clone();
let barrier = Arc::clone(&barrier);
handles.push(tokio::spawn(async move {
barrier.wait().await;
for i in 0..INSERTS_PER_WRITER {
let key = i % REUSED_KEY_POOL;
cache.insert(key, vec![0u8; VALUE_SIZE]).await;
}
}));
}
for writer_id in 0..WRITERS_PER_GROUP {
let cache = cache.clone();
let barrier = Arc::clone(&barrier);
let base = (writer_id as u64 + 1) * 1_000_000_000;
handles.push(tokio::spawn(async move {
barrier.wait().await;
for i in 0..INSERTS_PER_WRITER {
cache.insert(base + i, vec![0u8; VALUE_SIZE]).await;
}
}));
}
for h in handles {
h.await.expect("writer task panicked");
}
for _ in 0..DRAIN_ROUNDS {
cache.run_pending_tasks().await;
}
let ws = cache.weighted_size();
let ec = cache.entry_count();
let max_entries = MAX_CAPACITY / VALUE_SIZE as u64;
assert!(
ws <= MAX_CAPACITY,
"eviction stalled: weighted_size {:.1} MiB > max_capacity {:.1} MiB \
after {} rounds of run_pending_tasks()",
ws as f64 / (1024.0 * 1024.0),
MAX_CAPACITY as f64 / (1024.0 * 1024.0),
DRAIN_ROUNDS,
);
assert!(
ec <= max_entries,
"eviction stalled: entry_count {ec} > {max_entries} (the number of \
{VALUE_SIZE}-byte entries that fit in {MAX_CAPACITY} bytes) after \
{DRAIN_ROUNDS} rounds of run_pending_tasks()",
);
}