fix(job-registry): fix error handling*

This commit is contained in:
Edouard Vanbelle
2026-07-27 23:57:43 +02:00
parent f66f7fa31f
commit 24c514fe59
6 changed files with 26 additions and 13 deletions
@@ -608,11 +608,11 @@ impl JobHandler for StorageUsageService {
async fn run(&self, _args: &JobRunArgs) -> JobOutcome { async fn run(&self, _args: &JobRunArgs) -> JobOutcome {
let drives = match self.update_all_drives_storage_usage().await { let drives = match self.update_all_drives_storage_usage().await {
Ok(n) => n, Ok(n) => n,
Err(e) => return JobOutcome::Err(format!("drive reconciliation failed: {e}")), Err(e) => return JobOutcome::err(format!("drive reconciliation failed: {e}")),
}; };
let users = match self.update_all_users_storage_usage().await { let users = match self.update_all_users_storage_usage().await {
Ok(n) => n, Ok(n) => n,
Err(e) => return JobOutcome::Err(format!("user reconciliation failed: {e}")), Err(e) => return JobOutcome::err(format!("user reconciliation failed: {e}")),
}; };
JobOutcome::ok_with( JobOutcome::ok_with(
drives + users, drives + users,
+5 -5
View File
@@ -178,7 +178,7 @@ pub(super) async fn dispatch(name: &str, entry: Arc<JobEntry>, args: &JobRunArgs
// yield points may run to completion in the background. // yield points may run to completion in the background.
// We still record timeout and release the permit. // We still record timeout and release the permit.
( (
JobOutcome::Err(format!("wall-clock timeout of {:?} exceeded", dur)), JobOutcome::err(format!("wall-clock timeout of {:?} exceeded", dur)),
Some(ErrCause::Timeout), Some(ErrCause::Timeout),
) )
} }
@@ -249,12 +249,12 @@ fn translate_join(
"unknown panic payload".to_string() "unknown panic payload".to_string()
}; };
( (
JobOutcome::Err(format!("handler panicked: {msg}")), JobOutcome::err(format!("handler panicked: {msg}")),
Some(ErrCause::Panicked), Some(ErrCause::Panicked),
) )
} }
Err(join_err) => ( Err(join_err) => (
JobOutcome::Err(format!("task cancelled: {join_err}")), JobOutcome::err(format!("task cancelled: {join_err}")),
Some(ErrCause::Handler), Some(ErrCause::Handler),
), ),
} }
@@ -278,7 +278,7 @@ fn log_outcome(name: &str, outcome: &JobOutcome, cause: Option<ErrCause>, elapse
name, name,
); );
} }
JobOutcome::Err(msg) => { JobOutcome::Err { message: msg } => {
tracing::warn!( tracing::warn!(
target: "oxicloud::scheduler", target: "oxicloud::scheduler",
event = "job.run", event = "job.run",
@@ -342,7 +342,7 @@ mod tests {
let (outcome, cause) = translate_join(join.await); let (outcome, cause) = translate_join(join.await);
assert!(!outcome.is_ok()); assert!(!outcome.is_ok());
assert_eq!(cause, Some(ErrCause::Panicked)); assert_eq!(cause, Some(ErrCause::Panicked));
if let JobOutcome::Err(msg) = outcome { if let JobOutcome::Err { message: msg } = outcome {
assert!(msg.contains("panicked"), "expected panic marker in: {msg}"); assert!(msg.contains("panicked"), "expected panic marker in: {msg}");
} }
} }
+16 -3
View File
@@ -51,7 +51,12 @@ pub enum JobOutcome {
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")] #[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
extra: serde_json::Value, extra: serde_json::Value,
}, },
Err(String), /// `Err` is a struct variant (not tuple-newtype) so it composes
/// with `#[serde(tag = "outcome")]`. Serde's internal tagging
/// refuses to serialise a tuple variant wrapping a bare String
/// — the tag has nowhere to live. The struct form `{ message }`
/// lets serde emit `{"outcome":"err","message":"..."}` cleanly.
Err { message: String },
} }
impl JobOutcome { impl JobOutcome {
@@ -69,11 +74,19 @@ impl JobOutcome {
JobOutcome::Ok { count, extra } JobOutcome::Ok { count, extra }
} }
/// Convenience constructor for `Err` — call-site ergonomics
/// match the retired tuple form.
pub fn err(message: impl Into<String>) -> Self {
JobOutcome::Err {
message: message.into(),
}
}
/// Terse discriminant for logs / metrics: `"ok"` | `"err"`. /// Terse discriminant for logs / metrics: `"ok"` | `"err"`.
pub fn kind(&self) -> &'static str { pub fn kind(&self) -> &'static str {
match self { match self {
JobOutcome::Ok { .. } => "ok", JobOutcome::Ok { .. } => "ok",
JobOutcome::Err(_) => "err", JobOutcome::Err { .. } => "err",
} }
} }
@@ -125,7 +138,7 @@ mod tests {
#[test] #[test]
fn joboutcome_kind_label() { fn joboutcome_kind_label() {
assert_eq!(JobOutcome::ok(0).kind(), "ok"); assert_eq!(JobOutcome::ok(0).kind(), "ok");
assert_eq!(JobOutcome::Err("boom".into()).kind(), "err"); assert_eq!(JobOutcome::err("boom").kind(), "err");
} }
#[test] #[test]
+1 -1
View File
@@ -3170,7 +3170,7 @@ impl crate::infrastructure::scheduler::JobHandler for DedupService {
items, items,
serde_json::json!({ "bytes_reclaimed": bytes, "forced": args.force }), serde_json::json!({ "bytes_reclaimed": bytes, "forced": args.force }),
), ),
Err(e) => JobOutcome::Err(format!("dedup GC failed: {e}")), Err(e) => JobOutcome::err(format!("dedup GC failed: {e}")),
} }
} }
} }
@@ -132,7 +132,7 @@ impl JobHandler for GrantCleanupService {
"forced": args.force, "forced": args.force,
}), }),
), ),
Err(e) => JobOutcome::Err(format!("grant cleanup failed: {e}")), Err(e) => JobOutcome::err(format!("grant cleanup failed: {e}")),
} }
} }
} }
@@ -183,7 +183,7 @@ impl JobHandler for TrashCleanupService {
}), }),
) )
} }
Err(e) => JobOutcome::Err(format!("trash cleanup failed: {e}")), Err(e) => JobOutcome::err(format!("trash cleanup failed: {e}")),
} }
} }
} }