use crate::provenance::{RunProvenance, STATUS_ABORTED};
use crate::run_store::{RunStatus, RunStore, StoreError};
use klieo_provenance::{ProvenanceError, ProvenanceRepository};
use std::sync::Arc;
const RESTART_REASON: &str = "aborted: executor lost to a process restart";
#[derive(Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct SweepSummary {
pub swept: usize,
pub failed: Vec<String>,
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum SweepError {
#[error("run store error during sweep: {0}")]
Store(#[from] StoreError),
#[error("provenance append error during sweep: {0}")]
Provenance(#[from] ProvenanceError),
}
pub async fn sweep_orphaned_runs(
run_store: &RunStore,
provenance: &Arc<dyn ProvenanceRepository>,
) -> Result<SweepSummary, SweepError> {
let mut summary = SweepSummary::default();
for run in run_store.list_active().await? {
if !is_orphaned(run.status) {
continue;
}
match abort_orphan(
run_store,
provenance,
&run.run_id,
&run.workflow_id,
&run.author,
)
.await
{
Ok(()) => summary.swept += 1,
Err(e) => {
tracing::error!(run_id = run.run_id, error = %e, "failed to reclaim orphaned run; continuing");
summary.failed.push(run.run_id);
}
}
}
Ok(summary)
}
fn is_orphaned(status: RunStatus) -> bool {
matches!(status, RunStatus::Running | RunStatus::Pending)
}
async fn abort_orphan(
run_store: &RunStore,
provenance: &Arc<dyn ProvenanceRepository>,
run_id: &str,
workflow_id: &str,
author: &str,
) -> Result<(), SweepError> {
let binding = RunProvenance {
repo: Arc::clone(provenance),
run_id: run_id.to_string(),
author: author.to_string(),
workflow_id: workflow_id.to_string(),
content_hash: String::new(),
};
binding
.record_terminal(STATUS_ABORTED, Some(RESTART_REASON.to_string()))
.await?;
run_store
.set_aborted(run_id, RESTART_REASON.to_string())
.await?;
Ok(())
}