use std::collections::HashSet;
use std::sync::Arc;
use std::time::Duration;
use tokio_util::sync::CancellationToken;
use crate::App;
const INTERVAL: Duration = Duration::from_secs(30);
pub async fn run(app: Arc<App>, shutdown: CancellationToken) {
loop {
tokio::select! {
() = shutdown.cancelled() => return,
() = tokio::time::sleep(INTERVAL) => {}
}
run_once(&app).await;
}
}
pub async fn run_once(app: &App) {
write_access_times(app).await;
evict_to_budget(app, app.config.cache_max_bytes.get()).await;
if let Err(err) = app.store().checkpoint().await {
tracing::debug!(error = %err, "the write-ahead log checkpoint did not run this pass");
}
}
async fn write_access_times(app: &App) {
let touched: HashSet<_> = app.content.drain_touched().into_iter().collect();
if touched.is_empty() {
return;
}
let now = app.clock.now_utc_micros();
app.store()
.touch_content(touched.into_iter().map(|key| (key, now)).collect());
}
pub async fn evict_to_budget(app: &App, budget: u64) {
let plan = match app.store().eviction_plan().await {
Ok(plan) => plan,
Err(err) => {
tracing::warn!(error = %err, "the eviction pass could not read the content index");
return;
}
};
let mut total = plan.total_bytes;
if total <= budget {
return;
}
let (mut evicted, mut freed, mut held) = (0usize, 0u64, 0usize);
for (key, size) in plan.oldest_first {
if total <= budget {
break;
}
if !app.content.evict(&key).await {
held += 1;
continue;
}
if let Err(err) = app.store().clear_content_key(key).await {
tracing::warn!(
content = %key,
error = %err,
"an evicted file's mapping could not be cleared; the next request for it will"
);
}
evicted += 1;
freed = freed.saturating_add(size);
total = total.saturating_sub(size);
}
tracing::info!(
evicted,
freed,
held,
budget,
"evicted cached artifact bytes down towards the cache budget"
);
}