use dashmap::DashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::time::{sleep, Duration};
use crate::config::Config;
use crate::git::GitMaintenance;
use crate::state::RepoLock;
use crate::util::run_blocking;
pub struct MaintenanceScheduler {
config: Arc<Config>,
pending: Arc<DashMap<String, Option<tokio::task::JoinHandle<()>>>>,
}
impl MaintenanceScheduler {
pub fn new(config: Arc<Config>) -> Self {
Self {
config,
pending: Arc::new(DashMap::new()),
}
}
pub fn schedule(&self, tenant_key: &str, repo_path: PathBuf, repo_lock: RepoLock) {
if !self.config.maintenance.enabled {
return;
}
match self.pending.entry(tenant_key.to_string()) {
dashmap::mapref::entry::Entry::Occupied(_) => return,
dashmap::mapref::entry::Entry::Vacant(vacant) => {
vacant.insert(None);
}
}
let delay_secs = self.config.maintenance.delay_secs;
let destructive_prune = self.config.maintenance.destructive_prune;
let pending = self.pending.clone();
let task_tenant_key = tenant_key.to_string();
tracing::debug!(tenant = %task_tenant_key, delay_secs = delay_secs, "maintenance scheduled");
let task = tokio::spawn(async move {
sleep(Duration::from_secs(delay_secs)).await;
let _lock_guard = repo_lock.lock().await;
let repo_path_for_task = repo_path.clone();
match run_blocking(move || GitMaintenance::run(&repo_path_for_task, destructive_prune))
.await
{
Ok(report) => {
tracing::info!(
tenant = %task_tenant_key,
packed_objects = report.packed_objects,
loose_objects_removed = report.loose_objects_removed,
old_packs_removed = report.old_packs_removed,
"maintenance complete"
);
}
Err(err) => {
tracing::error!(tenant = %task_tenant_key, err = %err, "maintenance failed");
}
}
pending.remove(&task_tenant_key);
});
match self.pending.get_mut(tenant_key) {
Some(mut slot) => *slot = Some(task),
None => task.abort(),
}
}
pub fn cancel(&self, tenant_key: &str) {
if let Some((_, task)) = self.pending.remove(tenant_key) {
if let Some(task) = task {
task.abort();
}
tracing::debug!(tenant = %tenant_key, "maintenance canceled");
}
}
}