use super::super::args::EnrichArgs;
use super::super::events::EnrichSummary;
use super::super::postprocess::take_enrich_backend;
use super::super::queue::reset_processing_for_op;
use crate::output::emit_json_line as emit_json;
use rusqlite::Connection;
use std::path::Path;
use std::time::Instant;
pub(super) struct FinalTally<'a> {
pub(super) counters: &'a super::super::drain_parallel::DrainCounters,
pub(super) items_total: usize,
pub(super) started: Instant,
pub(super) until_deadline: Instant,
pub(super) pair_scan_ops: bool,
pub(super) backlog_degree0_proxy: Option<i64>,
pub(super) yield_count: u64,
pub(super) preempted_for_gate: bool,
}
pub(super) fn release_on_shutdown(queue_conn: &Connection, op_label: &str, namespace: &str) {
if crate::shutdown_requested() {
let reset = reset_processing_for_op(queue_conn, op_label, namespace).unwrap_or(0);
let _ = queue_conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);");
tracing::info!(
target: "enrich",
reset,
"graceful shutdown: WAL checkpointed, processing claims reset"
);
}
}
pub(super) fn finish(
conn: &Connection,
queue_conn: &Connection,
queue_path: &Path,
args: &EnrichArgs,
op_label: &str,
namespace: &str,
tally: FinalTally<'_>,
) {
release_on_shutdown(queue_conn, op_label, namespace);
let _ = conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);");
let _ = queue_conn.execute_batch("PRAGMA wal_checkpoint(TRUNCATE);");
let waiting_final: i64 = queue_conn
.query_row(
"SELECT COUNT(*) FROM queue WHERE status='pending' \
AND (operation = ?1 OR operation IS NULL) \
AND next_retry_at IS NOT NULL AND next_retry_at > datetime('now')",
rusqlite::params![op_label],
|r| r.get(0),
)
.unwrap_or(0);
let dead_final: i64 = queue_conn
.query_row(
"SELECT COUNT(*) FROM queue WHERE status='dead' \
AND (operation = ?1 OR operation IS NULL)",
rusqlite::params![op_label],
|r| r.get(0),
)
.unwrap_or(0);
emit_json(&EnrichSummary {
summary: true,
operation: format!("{:?}", args.operation()),
items_total: tally.items_total,
completed: tally.counters.completed,
failed: tally.counters.failed,
skipped: tally.counters.skipped,
cost_usd: tally.counters.cost_total,
elapsed_ms: tally.started.elapsed().as_millis() as u64,
backend_invoked: take_enrich_backend(),
waiting: waiting_final,
dead: dead_final,
budget_exhausted: if tally.pair_scan_ops && Instant::now() >= tally.until_deadline {
Some(true)
} else {
None
},
pairs_remaining_estimate: tally.backlog_degree0_proxy,
yields: if tally.yield_count > 0 {
Some(tally.yield_count)
} else {
None
},
preempted_for_gate: if tally.preempted_for_gate {
Some(true)
} else {
None
},
});
if tally.counters.failed == 0 {
let dead: i64 = queue_conn
.query_row("SELECT COUNT(*) FROM queue WHERE status='dead'", [], |r| {
r.get(0)
})
.unwrap_or(0);
let skipped_remaining: i64 = queue_conn
.query_row(
"SELECT COUNT(*) FROM queue WHERE status='skipped'",
[],
|r| r.get(0),
)
.unwrap_or(0);
if dead == 0 && skipped_remaining == 0 {
let _ = std::fs::remove_file(queue_path);
}
}
}