1use crate::{
2 states::{UnnamespacedJobType, UnnamespacedWorkerSpec, WorkerId},
3 store::{BackfillableJob, SubmittableJob},
4 Actions, Error, JobId, JobType, JobbyBlob, Worker,
5};
6
7use std::{
8 collections::HashMap,
9 ops::Add,
10 sync::{Arc, Mutex},
11 time::{Duration, SystemTime},
12};
13
14use futures::StreamExt;
15
16type Result<T, E = crate::Error> = anyhow::Result<T, E>;
17
18#[derive(Debug)]
20pub struct JobBuilder {
21 job_type: UnnamespacedJobType,
23 id: JobId,
24 data: Option<JobbyBlob>,
26 ready_at: Option<SystemTime>,
28 is_submit: bool,
30 allowed_attempts: i64,
32 dependency: Option<JobId>,
33 dependency_type: Option<UnnamespacedJobType>,
34 expiry: Option<Duration>,
35}
36
37impl JobBuilder {
38 #[must_use]
39 pub fn new<T: JobType>(job_type: T, id: JobId) -> Self {
40 Self::new_unnamespaced(UnnamespacedJobType::from(job_type), id)
41 }
42
43 #[must_use]
44 pub(crate) fn new_unnamespaced(job_type: UnnamespacedJobType, id: JobId) -> Self {
45 Self {
46 job_type,
47 id,
48 data: None,
49 ready_at: None,
50 is_submit: false,
51 allowed_attempts: 1,
52 dependency: None,
53 dependency_type: None,
54 expiry: None,
55 }
56 }
57
58 #[must_use]
59 pub fn data(self, data: JobbyBlob) -> Self {
60 Self {
61 data: Some(data),
62 ..self
63 }
64 }
65
66 #[must_use]
67 pub fn backfill_at(self, ready_at: SystemTime) -> Self {
68 Self {
69 ready_at: Some(ready_at),
70 ..self
71 }
72 }
73
74 #[must_use]
75 pub fn backfill_in(self, ready_in: Duration) -> Self {
76 Self {
77 ready_at: Some(SystemTime::now().add(ready_in)),
78 ..self
79 }
80 }
81
82 #[must_use]
83 pub fn submit(self) -> Self {
84 Self {
85 is_submit: true,
86 ..self
87 }
88 }
89
90 #[must_use]
91 pub fn allowed_attempts(self, allowed_attempts: i64) -> Self {
92 Self {
93 allowed_attempts,
94 ..self
95 }
96 }
97
98 #[must_use]
99 pub fn dependency<T: JobType>(self, dependency_type: T, dependency: JobId) -> Self {
100 Self {
101 dependency: Some(dependency),
102 dependency_type: Some(UnnamespacedJobType::from(dependency_type)),
103 ..self
104 }
105 }
106
107 #[must_use]
108 pub(crate) fn unnamespaced_dependency(
109 self,
110 dependency_type: UnnamespacedJobType,
111 dependency: JobId,
112 ) -> Self {
113 Self {
114 dependency: Some(dependency),
115 dependency_type: Some(dependency_type),
116 ..self
117 }
118 }
119
120 #[must_use]
121 pub fn expiry(self, expiry: Option<Duration>) -> Self {
122 Self { expiry, ..self }
123 }
124
125 #[must_use]
126 pub fn without_expiry(self) -> Self {
127 Self {
128 expiry: None,
129 ..self
130 }
131 }
132
133 #[must_use]
134 pub fn expires_in(self, expires_in: Duration) -> Self {
135 Self {
136 expiry: Some(expires_in),
137 ..self
138 }
139 }
140
141 pub fn build(self, actions: &mut Actions) {
143 actions.jobs.push(self);
144 }
145
146 pub fn register(self, registry: &JobRegistry) {
149 registry.register(self);
150 }
151
152 pub(crate) async fn into_jobs(
153 self,
154 ) -> Result<(Option<SubmittableJob>, Option<BackfillableJob>)> {
155 let data = if let Some(blob) = self.data {
156 Some(blob.encode().await?.into_parts())
157 } else {
158 None
159 };
160 let backfill = if let Some(ready_at) = self.ready_at {
161 Some(BackfillableJob {
162 job_type: self.job_type,
163 id: self.id.clone(),
164 data: data.clone(),
165 ready_at,
166 })
167 } else {
168 None
169 };
170 let submit = self.is_submit.then(|| {
171 let data = data.unwrap_or_else(JobbyBlob::empty_parts);
172 SubmittableJob {
173 job_type: self.job_type,
174 id: self.id.clone(),
175 data,
176 allowed_attempts: self.allowed_attempts,
177 dependency: self.dependency,
178 dependency_type: self.dependency_type,
179 expiry: self.expiry,
180 }
181 });
182 Ok((submit, backfill))
183 }
184
185 pub(crate) async fn bulk_into_jobs(
186 builders: Vec<Self>,
187 ) -> Result<(Vec<BackfillableJob>, Vec<SubmittableJob>)> {
188 if builders.is_empty() {
189 return Ok((vec![], vec![]));
190 }
191 let mut backfills: Vec<BackfillableJob> = vec![];
192 let mut submits: Vec<SubmittableJob> = vec![];
193 let futures = builders.into_iter().map(Self::into_jobs);
194 let stream = futures::stream::iter(futures).buffered(crate::FUTURES_IN_PARALLEL);
195 let maybe_jobses = stream.collect::<Vec<_>>().await;
196 let jobses = maybe_jobses.into_iter().collect::<Result<Vec<_>>>()?;
197 for (maybe_submit, maybe_backfill) in jobses {
198 if let Some(submit) = maybe_submit {
199 submits.push(submit);
200 }
201 if let Some(backfill) = maybe_backfill {
202 backfills.push(backfill);
203 }
204 }
205 Ok((backfills, submits))
206 }
207}
208
209pub struct WorkerRegistryStorage {
210 pub(crate) workers: HashMap<WorkerId, Worker>,
211 pub(crate) specs: Vec<UnnamespacedWorkerSpec>,
212}
213
214impl WorkerRegistryStorage {
215 pub(crate) fn new() -> Self {
216 Self {
217 workers: HashMap::new(),
218 specs: vec![],
219 }
220 }
221
222 fn register<T: JobType>(
223 &mut self,
224 worker: Worker,
225 id: WorkerId,
226 job_type: T,
227 fetch_interval: Duration,
228 fetch_jitter: Duration,
229 last_fetch_time: Option<SystemTime>,
230 ) -> Result<()> {
231 if self.workers.contains_key(&id) {
232 return Err(Error::WorkerAlreadyRegistered(id));
233 }
234 self.workers.insert(id.clone(), worker);
235 self.specs.push(UnnamespacedWorkerSpec {
236 id,
237 job_type: UnnamespacedJobType::from(job_type),
238 fetch_interval,
239 fetch_jitter,
240 last_fetch_time,
241 });
242 Ok(())
243 }
244}
245
246pub struct WorkerRegistry {
247 client_name: &'static str,
248 storage: Arc<Mutex<WorkerRegistryStorage>>,
249}
250
251impl WorkerRegistry {
252 pub(crate) fn new(
253 client_name: &'static str,
254 storage: Arc<Mutex<WorkerRegistryStorage>>,
255 ) -> Self {
256 Self {
257 client_name,
258 storage,
259 }
260 }
261
262 #[allow(clippy::unwrap_in_result)]
266 pub fn register<T: JobType>(
267 &self,
268 worker: Worker,
269 id: &str,
270 job_type: T,
271 fetch_interval: Duration,
272 fetch_jitter: Duration,
273 last_fetch_time: Option<SystemTime>,
276 ) -> Result<()> {
277 let id = format!("{}_{id}", self.client_name);
278 self.storage.lock().expect("mutex not poisoned").register(
279 worker,
280 id,
281 job_type,
282 fetch_interval,
283 fetch_jitter,
284 last_fetch_time,
285 )
286 }
287}
288
289pub struct JobRegistry {
290 storage: Arc<Mutex<Vec<JobBuilder>>>,
291}
292
293impl JobRegistry {
294 pub(crate) fn new(storage: Arc<Mutex<Vec<JobBuilder>>>) -> Self {
295 Self { storage }
296 }
297
298 pub(crate) fn register(&self, builder: JobBuilder) {
299 self.storage
300 .lock()
301 .expect("mutex not poisoned")
302 .push(builder);
303 }
304
305 pub fn submit_job<T: JobType>(&self, job_type: T, id: JobId) -> JobBuilder {
306 JobBuilder::new(job_type, id).submit()
307 }
308
309 pub fn backfill_job_at<T: JobType>(
310 &self,
311 job_type: T,
312 id: JobId,
313 ready_at: SystemTime,
314 ) -> JobBuilder {
315 JobBuilder::new(job_type, id).backfill_at(ready_at)
316 }
317
318 pub fn backfill_job_in<T: JobType>(
319 &self,
320 job_type: T,
321 id: JobId,
322 ready_in: Duration,
323 ) -> JobBuilder {
324 JobBuilder::new(job_type, id).backfill_in(ready_in)
325 }
326}