1pub mod memory;
2pub mod mock;
3pub mod stream;
4
5pub use memory::{MemoryAttempt, MemoryBackend};
6pub use mock::{CallRecord, MockBackend};
7pub use stream::StreamBackend;
8
9use crate::model::{Job, JobListItem, NewJob};
10use async_trait::async_trait;
11use chrono::{DateTime, Utc};
12use std::pin::Pin;
13use uuid::Uuid;
14
15pub type NotificationStream = Pin<Box<dyn futures_core::Stream<Item = ()> + Send>>;
17
18#[async_trait]
23pub trait StorageBackend: Send + Sync {
24 fn as_stream(&self) -> Option<&dyn StreamBackend> {
26 None
27 }
28
29 async fn run_migrations(&self) -> anyhow::Result<()>;
31
32 async fn health_check(&self) -> anyhow::Result<()>;
34
35 async fn enqueue(&self, job: NewJob) -> anyhow::Result<Uuid>;
37
38 async fn subscribe(&self, queue: &str) -> anyhow::Result<NotificationStream>;
40
41 async fn lease_jobs_batch(
43 &self,
44 queue: &str,
45 worker_id: &str,
46 lease_seconds: i64,
47 batch_size: i64,
48 ) -> anyhow::Result<Vec<Job>>;
49
50 async fn lease_jobs_batch_with_ordering(
52 &self,
53 queue: &str,
54 worker_id: &str,
55 lease_seconds: i64,
56 batch_size: i64,
57 ordering: crate::model::QueueOrdering,
58 ) -> anyhow::Result<Vec<Job>> {
59 let _ = ordering;
60 self.lease_jobs_batch(queue, worker_id, lease_seconds, batch_size)
61 .await
62 }
63
64 async fn reap_expired_locks(&self) -> anyhow::Result<u64>;
66
67 async fn start_attempts_batch(
69 &self,
70 dataset_ids: &[String],
71 job_ids: &[Uuid],
72 worker_id: &str,
73 ) -> anyhow::Result<Vec<(Uuid, Uuid, i32)>>;
74
75 async fn mark_succeeded(
77 &self,
78 job_id: Uuid,
79 attempt_id: Uuid,
80 worker_id: &str,
81 latency_ms: i32,
82 ) -> anyhow::Result<()>;
83
84 async fn mark_succeeded_batch(
86 &self,
87 dataset_id: &str,
88 updates: &[(Uuid, Uuid, i32)],
89 worker_id: &str,
90 ) -> anyhow::Result<()>;
91
92 #[allow(clippy::too_many_arguments)]
94 async fn reschedule_for_retry(
95 &self,
96 job_id: Uuid,
97 attempt_id: Uuid,
98 worker_id: &str,
99 latency_ms: i32,
100 next_run_at: DateTime<Utc>,
101 error_code: &str,
102 error_message: &str,
103 attempt_no: i32,
104 ) -> anyhow::Result<()>;
105
106 #[allow(clippy::too_many_arguments)]
108 async fn mark_dlq(
109 &self,
110 job_id: Uuid,
111 attempt_id: Uuid,
112 worker_id: &str,
113 latency_ms: i32,
114 reason_code: &str,
115 error_code: &str,
116 error_message: &str,
117 attempt_no: i32,
118 ) -> anyhow::Result<()>;
119
120 async fn archive_succeeded_older_than(
122 &self,
123 cutoff: DateTime<Utc>,
124 limit: i64,
125 ) -> anyhow::Result<u64>;
126
127 async fn delete_history_for_succeeded_older_than(
129 &self,
130 cutoff: DateTime<Utc>,
131 limit: i64,
132 ) -> anyhow::Result<(u64, u64)>;
133
134 async fn perform_maintenance(&self) -> anyhow::Result<()> {
136 Ok(())
137 }
138
139 async fn extend_lease(
142 &self,
143 _job_id: Uuid,
144 _worker_id: &str,
145 _lease_seconds: i64,
146 ) -> anyhow::Result<bool> {
147 Ok(true)
148 }
149
150 async fn get_job(&self, job_id: Uuid) -> anyhow::Result<Option<Job>>;
152
153 async fn list_jobs(
155 &self,
156 queue: Option<&str>,
157 status: Option<&str>,
158 limit: i64,
159 cursor_created_at: Option<DateTime<Utc>>,
160 cursor_id: Option<Uuid>,
161 ) -> anyhow::Result<Vec<JobListItem>>;
162
163 async fn replay_job(
165 &self,
166 job_id: Uuid,
167 override_queue: Option<&str>,
168 override_run_at: Option<DateTime<Utc>>,
169 ) -> anyhow::Result<Uuid>;
170
171 async fn dequeue_and_lease(
173 &self,
174 queue: &str,
175 worker_id: &str,
176 lease_seconds: i64,
177 batch_size: i64,
178 ) -> anyhow::Result<Vec<Job>> {
179 self.lease_jobs_batch(queue, worker_id, lease_seconds, batch_size)
180 .await
181 }
182
183 async fn complete_job(
185 &self,
186 job_id: Uuid,
187 attempt_id: Uuid,
188 worker_id: &str,
189 latency_ms: i32,
190 ) -> anyhow::Result<()> {
191 self.mark_succeeded(job_id, attempt_id, worker_id, latency_ms)
192 .await
193 }
194
195 #[allow(clippy::too_many_arguments)]
197 async fn retry_job(
198 &self,
199 job_id: Uuid,
200 attempt_id: Uuid,
201 worker_id: &str,
202 latency_ms: i32,
203 next_run_at: DateTime<Utc>,
204 error_code: &str,
205 error_message: &str,
206 attempt_no: i32,
207 ) -> anyhow::Result<()> {
208 self.reschedule_for_retry(
209 job_id,
210 attempt_id,
211 worker_id,
212 latency_ms,
213 next_run_at,
214 error_code,
215 error_message,
216 attempt_no,
217 )
218 .await
219 }
220
221 #[allow(clippy::too_many_arguments)]
223 async fn fail_job(
224 &self,
225 job_id: Uuid,
226 attempt_id: Uuid,
227 worker_id: &str,
228 latency_ms: i32,
229 reason_code: &str,
230 error_code: &str,
231 error_message: &str,
232 attempt_no: i32,
233 ) -> anyhow::Result<()> {
234 self.mark_dlq(
235 job_id,
236 attempt_id,
237 worker_id,
238 latency_ms,
239 reason_code,
240 error_code,
241 error_message,
242 attempt_no,
243 )
244 .await
245 }
246}