1use thiserror::Error;
2
3use crate::config::JobsConfigValidationError;
4
5pub type Result<T> = std::result::Result<T, Error>;
6
7#[derive(Debug, Error)]
8pub enum Error {
9 #[error(transparent)]
10 Scheduler(#[from] SchedulerError),
11 #[error(transparent)]
12 Worker(#[from] WorkerError),
13 #[error(transparent)]
14 Reaper(#[from] ReaperError),
15 #[error(transparent)]
16 Runtime(#[from] RuntimeError),
17}
18
19#[derive(Debug, Error)]
20pub enum SchedulerError {
21 #[error("failed to begin scheduler transaction")]
22 BeginTransaction { source: runledger_postgres::Error },
23 #[error("failed to commit scheduler transaction")]
24 CommitTransaction { source: runledger_postgres::Error },
25 #[error("failed to claim due schedules")]
26 ClaimDueSchedules { source: runledger_postgres::Error },
27 #[error("failed to create savepoint using `{statement}`")]
28 SavepointCreate {
29 statement: &'static str,
30 source: runledger_postgres::Error,
31 },
32 #[error("failed to rollback savepoint using `{statement}`")]
33 SavepointRollback {
34 statement: &'static str,
35 source: runledger_postgres::Error,
36 },
37 #[error("failed to release savepoint using `{statement}`")]
38 SavepointRelease {
39 statement: &'static str,
40 source: runledger_postgres::Error,
41 },
42 #[error("failed deferring failed schedule `{schedule_id}`")]
43 DeferFailedSchedule {
44 schedule_id: uuid::Uuid,
45 source: runledger_postgres::Error,
46 },
47 #[error(
48 "invalid cron expression for schedule `{schedule_name}` ({schedule_id}): `{cron_expr}`"
49 )]
50 InvalidCronExpression {
51 schedule_id: uuid::Uuid,
52 schedule_name: String,
53 cron_expr: String,
54 },
55 #[error("failed enqueueing scheduled job `{job_type}` from schedule `{schedule_id}`")]
56 EnqueueScheduledJob {
57 schedule_id: uuid::Uuid,
58 job_type: String,
59 source: runledger_postgres::Error,
60 },
61 #[error("failed marking schedule `{schedule_id}` as fired")]
62 MarkScheduleFired {
63 schedule_id: uuid::Uuid,
64 source: runledger_postgres::Error,
65 },
66 #[error("claimed schedule `{schedule_id}` was missing while {operation}")]
69 ClaimedScheduleMissing {
70 schedule_id: uuid::Uuid,
71 operation: &'static str,
72 },
73}
74
75#[derive(Debug, Error)]
76pub enum WorkerError {
77 #[error("failed claiming jobs for worker `{worker_id}`")]
78 ClaimJobs {
79 worker_id: String,
80 source: runledger_postgres::Error,
81 },
82 #[error("failed setting running progress for job `{job_id}` attempt `{attempt}`")]
83 SetRunningProgress {
84 job_id: uuid::Uuid,
85 attempt: i32,
86 source: runledger_postgres::Error,
87 },
88 #[error("failed releasing unstarted claim for job `{job_id}` attempt `{attempt}`")]
89 ReleaseUnstartedClaim {
90 job_id: uuid::Uuid,
91 attempt: i32,
92 source: runledger_postgres::Error,
93 },
94 #[error("failed completing job `{job_id}` attempt `{attempt}` as success")]
95 CompleteSuccess {
96 job_id: uuid::Uuid,
97 attempt: i32,
98 source: runledger_postgres::Error,
99 },
100 #[error("failed continuing job `{job_id}` attempt `{attempt}` after successful handler slice")]
101 CompleteContinuation {
102 job_id: uuid::Uuid,
103 attempt: i32,
104 source: runledger_postgres::Error,
105 },
106 #[error("failed completing job `{job_id}` attempt `{attempt}` as failure")]
107 CompleteFailure {
108 job_id: uuid::Uuid,
109 attempt: i32,
110 source: runledger_postgres::Error,
111 },
112 #[error("failed heartbeat for job `{job_id}` attempt `{attempt}`")]
113 Heartbeat {
114 job_id: uuid::Uuid,
115 attempt: i32,
116 source: runledger_postgres::Error,
117 },
118}
119
120#[derive(Debug, Error)]
121pub enum ReaperError {
122 #[error(
123 "failed reaping expired leases with batch_size `{batch_size}` and retry_delay_ms `{retry_delay_ms}`"
124 )]
125 ReapExpiredLeases {
126 batch_size: i64,
127 retry_delay_ms: i32,
128 source: runledger_postgres::Error,
129 },
130}
131
132#[derive(Debug, Error)]
133#[non_exhaustive]
134pub enum RuntimeError {
135 #[error("invalid jobs runtime configuration: {source}")]
143 InvalidJobsConfig {
144 #[source]
145 source: JobsConfigValidationError,
146 },
147 #[error(
154 "supervisor builder received both a job registry and a job catalog; choose one registration source"
155 )]
156 MixedRegistrySources,
157 #[error(
164 "supervisor builder requires a job registry when worker or reaper loops are enabled \
165 (worker_enabled={worker_enabled}, reaper_enabled={reaper_enabled})"
166 )]
167 MissingRegistry {
168 worker_enabled: bool,
169 reaper_enabled: bool,
170 },
171 #[error("supervisor builder requires an active Tokio runtime")]
175 MissingTokioRuntime {
176 #[source]
177 source: tokio::runtime::TryCurrentError,
178 },
179 #[error("jobs runtime task `{task}` exited unexpectedly before shutdown")]
184 TaskExitedUnexpectedly { task: &'static str },
185 #[error("failed joining jobs runtime task `{task}`")]
188 TaskJoin {
189 task: &'static str,
190 #[source]
191 source: tokio::task::JoinError,
192 },
193 #[error("jobs runtime shutdown exceeded timeout {timeout:?}")]
198 ShutdownTimeout { timeout: std::time::Duration },
199 #[error("jobs runtime shutdown timeout {timeout:?} is too large to represent")]
202 ShutdownTimeoutTooLarge { timeout: std::time::Duration },
203 #[error(
207 "jobs runtime shutdown exceeded timeout {timeout:?} while draining after earlier task failure"
208 )]
209 ShutdownTimeoutAfterTaskError {
210 timeout: std::time::Duration,
211 #[source]
212 source: Box<RuntimeError>,
213 },
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn scheduler_invalid_cron_variant_contains_schedule_metadata() {
222 let schedule_id = uuid::Uuid::nil();
223 let error = SchedulerError::InvalidCronExpression {
224 schedule_id,
225 schedule_name: "nightly sync".to_string(),
226 cron_expr: "not cron".to_string(),
227 };
228
229 match error {
230 SchedulerError::InvalidCronExpression {
231 schedule_id: actual_id,
232 schedule_name,
233 cron_expr,
234 } => {
235 assert_eq!(actual_id, schedule_id);
236 assert_eq!(schedule_name, "nightly sync");
237 assert_eq!(cron_expr, "not cron");
238 }
239 other => panic!("expected invalid cron variant, got: {other:?}"),
240 }
241 }
242}