use std::sync::Arc;
use std::time::Duration;
use tokio::task::JoinHandle;
use tracing::{debug, info, warn};
use crate::control::state::SharedState;
const TICK_INTERVAL: Duration = Duration::from_secs(30);
#[derive(Debug)]
pub struct PendingReclaimWorker {
pub handle: JoinHandle<()>,
}
pub fn spawn_pending_reclaim(shared: Arc<SharedState>) -> PendingReclaimWorker {
let handle = tokio::spawn(async move { run_loop(shared).await });
PendingReclaimWorker { handle }
}
async fn run_loop(shared: Arc<SharedState>) {
info!(
tick_secs = TICK_INTERVAL.as_secs(),
"pending-reclaim worker started"
);
loop {
tokio::time::sleep(TICK_INTERVAL).await;
if let Err(error) = drain_once(&shared).await {
warn!(error = %error, "pending-reclaim worker pass incomplete");
}
}
}
pub async fn drain_once(shared: &SharedState) -> crate::Result<()> {
let catalog = shared.credentials.catalog();
let queue = catalog.load_pending_reclaim_queue()?;
let mut last_error = None;
for entry in queue {
if !shared
.quiesce
.is_draining(entry.database_id, entry.tenant_id, &entry.name)
{
shared
.quiesce
.begin_drain(entry.database_id, entry.tenant_id, &entry.name);
}
match crate::control::server::shared::ddl::neutral::collection::purge::dispatch_unregister_collection(
shared,
entry.database_id,
entry.tenant_id,
&entry.name,
entry.purge_lsn,
)
.await
{
Ok(()) => {
if let Err(error) =
crate::control::catalog_entry::apply::collection::finalize_purge(
entry.database_id,
entry.tenant_id,
&entry.name,
catalog,
)
{
warn!(
tenant = entry.tenant_id,
collection = %entry.name,
error = %error,
"pending-reclaim: engine rows purged but catalog finalization failed"
);
last_error = Some(error.to_string());
continue;
}
if let Err(error) = catalog.remove_pending_reclaim(
entry.database_id,
entry.tenant_id,
&entry.name,
) {
warn!(
tenant = entry.tenant_id,
collection = %entry.name,
error = %error,
"pending-reclaim: purged engine rows but failed to reap queue entry"
);
last_error = Some(error.to_string());
continue;
}
shared
.quiesce
.forget(entry.database_id, entry.tenant_id, &entry.name);
debug!(
tenant = entry.tenant_id,
collection = %entry.name,
purge_lsn = entry.purge_lsn,
"pending-reclaim: drained queue entry — engine storage purged"
);
}
Err(e) => {
let msg = e.to_string();
if let Err(update_err) =
catalog.record_pending_reclaim_attempt(
entry.database_id,
entry.tenant_id,
&entry.name,
&msg,
)
{
warn!(
tenant = entry.tenant_id,
collection = %entry.name,
error = %update_err,
"pending-reclaim: failed to record attempt"
);
}
warn!(
tenant = entry.tenant_id,
collection = %entry.name,
attempts = entry.attempts + 1,
error = %msg,
"pending-reclaim: engine purge failed; will retry next tick"
);
last_error = Some(msg);
}
}
}
if let Some(detail) = last_error {
return Err(crate::Error::Storage {
engine: "pending-reclaim".into(),
detail,
});
}
Ok(())
}