1use std::{future::Future, pin::Pin, sync::Arc};
2
3use time::{DateTime, TimeZone};
4use tokio::sync::{Mutex, RwLock};
5#[cfg(feature = "tracing")]
6use tracing::*;
7
8use crate::{error::SchedulerError, scheduler::Scheduler};
9
10pub struct JobScheduler {
11 pub job: Job,
12 schedule: Mutex<Scheduler>,
13 next_run_at: Mutex<Option<DateTime>>,
14 last_run_at: Mutex<Option<DateTime>>,
15}
16
17impl JobScheduler {
18 pub fn new(mut schedule: Scheduler, job: Job) -> Self {
19 let next_run_at = schedule.next(&DateTime::now_in(TimeZone::UTC));
21 JobScheduler {
22 job,
23 schedule: Mutex::new(schedule),
24 next_run_at: Mutex::new(next_run_at),
25 last_run_at: Mutex::new(None),
26 }
27 }
28
29 pub async fn is_pending(&self) -> bool {
31 if !self.job.is_active {
33 return false;
34 }
35
36 if let Some(next_run_at) = self.next_run_at.lock().await.as_ref() {
38 *next_run_at < DateTime::now_in(TimeZone::UTC)
39 } else {
40 false
41 }
42 }
43
44 pub async fn run(&self) -> Result<(), SchedulerError> {
46 let run_result = self.job.run().await;
48
49 let now = DateTime::now_in(TimeZone::UTC);
50
51 let mut schedule = self.schedule.lock().await;
52
53 let mut next_run_at = self.next_run_at.lock().await;
55 *next_run_at = schedule.next(&now);
56
57 let mut last_run_at = self.last_run_at.lock().await;
59 *last_run_at = Some(now);
60
61 run_result
62 }
63}
64
65pub type JobFn = dyn 'static
66 + Send
67 + Sync
68 + Fn() -> Pin<Box<dyn Future<Output = Result<(), Box<dyn core::error::Error + Send + Sync>>> + Send>>;
69
70pub struct Job {
71 function: Arc<JobFn>,
72 group: String,
73 name: String,
74 is_active: bool,
75 is_running: RwLock<bool>,
76 retries_after_failure: Option<u64>,
77}
78
79impl Job {
80 pub fn new<
81 G: Into<String>,
82 N: Into<String>,
83 F: 'static
84 + Send
85 + Sync
86 + Fn() -> Pin<Box<dyn Future<Output = Result<(), Box<dyn core::error::Error + Send + Sync>>> + Send>>,
87 >(
88 group: G,
89 name: N,
90 retries_after_failure: Option<u64>,
91 function: F,
92 ) -> Self {
93 Job {
94 function: Arc::new(function),
95 name: name.into(),
96 group: group.into(),
97 retries_after_failure,
98 is_running: RwLock::new(false),
99 is_active: true,
100 }
101 }
102
103 pub async fn is_running(&self) -> bool {
105 let read = self.is_running.read().await;
106 *read
107 }
108
109 pub fn name(&self) -> &str {
110 &self.name
111 }
112
113 pub fn group(&self) -> &str {
114 &self.group
115 }
116
117 #[cfg_attr(not(feature = "tracing"), allow(unused_variables))]
120 pub async fn run(&self) -> Result<(), SchedulerError> {
121 self.set_running(true).await?;
122
123 let mut run_result = self.exec().await;
125
126 if let Some(retries) = self.retries_after_failure {
127 for attempt in 1..=retries {
128 if let Err(e) = run_result {
129 #[cfg(feature = "tracing")]
130 warn!(
131 "Execution failed for job [{}/{}] - Retry execution, attempt {}/{}. Previous err: {}",
132 self.group, self.name, attempt, retries, e
133 );
134 run_result = self.exec().await;
135 } else {
136 break;
137 }
138 }
139 }
140
141 self.set_running(false).await?;
142
143 run_result.map_err(|err| SchedulerError::JobExecutionError {
144 source: err,
145 })
146 }
147
148 async fn exec(&self) -> Result<(), Box<dyn core::error::Error + Send + Sync>> {
149 let function = self.function.clone();
150 (function)().await
151 }
152
153 async fn set_running(&self, is_running: bool) -> Result<(), SchedulerError> {
154 let mut write = self.is_running.write().await;
155
156 if is_running.eq(&*write) {
157 return Err(SchedulerError::JobLockError {
158 message: format!(
159 "Wrong Job status found for job [{}/{}]. Expected: {}",
160 self.group, self.name, !is_running
161 ),
162 });
163 }
164
165 *write = is_running;
166 Ok(())
167 }
168}
169
170#[cfg(test)]
171pub mod test {
172
173 use std::{sync::Arc, time::Duration};
174
175 use tokio::sync::mpsc::channel;
176
177 use super::*;
178
179 #[tokio::test]
180 async fn should_be_running() {
181 let lock = Arc::new(Mutex::new(true));
182 let lock_clone = lock.clone();
183 let (tx, mut rx) = channel(10000);
184 let tx_clone = tx.clone();
185
186 let job_scheduler = Arc::new(JobScheduler::new(
187 Scheduler::Interval {
188 interval_duration: Duration::new(1, 0),
189 execute_at_startup: false,
190 },
191 Job::new("g", "n", None, move || {
192 let lock_clone = lock_clone.clone();
193 let tx_clone = tx_clone.clone();
194 Box::pin(async move {
195 println!("job - started");
196 tx_clone.send("").await.unwrap();
197 println!("job - Trying to get the lock");
198 let _lock = lock_clone.lock().await;
199 println!("job - lock acquired");
200 Ok(())
201 })
202 }),
203 ));
204
205 assert!(!job_scheduler.job.is_running().await);
206
207 {
208 let _lock = lock.lock().await;
209 let job_clone = job_scheduler.clone();
210 tokio::spawn(async move {
211 println!("starting job");
212 job_clone.run().await.unwrap();
213 println!("end job execution");
214 tx.send("").await.unwrap();
215 });
216 rx.recv().await.unwrap();
217 assert!(job_scheduler.job.is_running().await);
218 }
219
220 rx.recv().await.unwrap();
221 assert!(!job_scheduler.job.is_running().await);
222 }
223
224 #[tokio::test]
225 async fn job_should_not_retry_run_if_ok() {
226 let lock = Arc::new(Mutex::new(0));
227 let lock_clone = lock.clone();
228
229 let max_retries = 12;
230
231 let job = Job::new("g", "n", Some(max_retries), move || {
232 let lock_clone = lock_clone.clone();
233 Box::pin(async move {
234 println!("job - started");
235 println!("job - Trying to get the lock");
236 let mut lock = lock_clone.lock().await;
237 let count = *lock;
238 *lock = count + 1;
239 println!("job - count {count}");
240 Ok(())
241 })
242 });
243
244 let result = job.run().await;
245
246 assert!(result.is_ok());
247
248 let lock = lock.lock().await;
249 let count = *lock;
250 assert_eq!(1, count);
251 }
252
253 #[tokio::test]
254 async fn job_should_retry_run_if_error() {
255 let lock = Arc::new(Mutex::new(0));
256 let lock_clone = lock.clone();
257
258 let max_retries = 12;
259
260 let job = Job::new("g", "n", Some(max_retries), move || {
261 let lock_clone = lock_clone.clone();
262 Box::pin(async move {
263 println!("job - started");
264 println!("job - Trying to get the lock");
265 let mut lock = lock_clone.lock().await;
266 let count = *lock;
267 *lock = count + 1;
268 println!("job - count {count}");
269 Err(SchedulerError::JobLockError {
270 message: "".to_owned(),
271 })?
272 })
273 });
274
275 let result = job.run().await;
276
277 assert!(result.is_err());
278
279 let lock = lock.lock().await;
280 let count = *lock;
281 assert_eq!(max_retries + 1, count);
282 }
283
284 #[tokio::test]
285 async fn job_should_stop_retrying_run_if_attempt_succeed() {
286 let lock = Arc::new(Mutex::new(0));
287 let lock_clone = lock.clone();
288
289 let succeed_at = 7;
290 let max_retries = 12;
291
292 let job = Job::new("g", "n", Some(max_retries), move || {
293 let lock_clone = lock_clone.clone();
294 Box::pin(async move {
295 println!("job - started");
296 println!("job - Trying to get the lock");
297 let mut lock = lock_clone.lock().await;
298 let count = *lock;
299 *lock = count + 1;
300 println!("job - count {count}");
301
302 if count == succeed_at {
303 Ok(())
304 } else {
305 Err(SchedulerError::JobLockError {
306 message: "".to_owned(),
307 })?
308 }
309 })
310 });
311
312 let result = job.run().await;
313
314 assert!(result.is_ok());
315
316 let lock = lock.lock().await;
317 let count = *lock;
318 assert_eq!(succeed_at + 1, count);
319 }
320}