sz_rust_core/runtime/
scheduler.rs1use std::sync::Arc;
24use std::time::Duration;
25
26use tokio_util::sync::CancellationToken;
27
28use sz_orm_scheduler::{CronScheduler, JobHandler, ScheduledTask, Scheduler, SchedulerError};
29
30#[derive(Debug, Clone)]
32pub struct SchedulerRuntimeConfig {
33 pub tick_ms: u64,
35}
36
37impl Default for SchedulerRuntimeConfig {
38 fn default() -> Self {
39 Self { tick_ms: 1000 }
40 }
41}
42
43impl SchedulerRuntimeConfig {
44 pub fn new(tick_ms: u64) -> Self {
46 Self {
47 tick_ms: tick_ms.max(1),
48 }
49 }
50}
51
52pub struct SchedulerRuntime {
82 config: SchedulerRuntimeConfig,
83 scheduler: Arc<CronScheduler>,
84}
85
86impl SchedulerRuntime {
87 pub fn new(config: SchedulerRuntimeConfig) -> Self {
89 Self {
90 config,
91 scheduler: Arc::new(CronScheduler::new()),
92 }
93 }
94
95 pub fn start(&self, token: CancellationToken) -> tokio::task::JoinHandle<()> {
103 let scheduler = self.scheduler.clone();
104 let tick_interval = Duration::from_millis(self.config.tick_ms);
105
106 tokio::spawn(async move {
107 let mut ticker = tokio::time::interval(tick_interval);
108 loop {
110 tokio::select! {
111 _ = token.cancelled() => break,
112 _ = ticker.tick() => {
113 let now = chrono::Utc::now();
114 let fired = scheduler.try_fire_due(now);
115 if fired > 0 {
116 tracing::debug!("scheduler fired {} task(s) at {}", fired, now);
117 }
118 }
119 }
120 }
121 })
122 }
123
124 pub fn schedule(&self, task: ScheduledTask) -> Result<(), SchedulerError> {
126 self.scheduler.schedule(task)
127 }
128
129 pub fn cancel(&self, task_id: &str) -> Result<(), SchedulerError> {
131 self.scheduler.cancel(task_id)
132 }
133
134 pub fn pause(&self, task_id: &str) -> Result<(), SchedulerError> {
136 self.scheduler.pause(task_id)
137 }
138
139 pub fn resume(&self, task_id: &str) -> Result<(), SchedulerError> {
141 self.scheduler.resume(task_id)
142 }
143
144 pub fn list_tasks(&self) -> Vec<ScheduledTask> {
146 self.scheduler.list_tasks()
147 }
148
149 pub fn register_handler(&self, task_id: impl Into<String>, handler: Arc<dyn JobHandler>) {
151 self.scheduler.register_handler(task_id, handler);
152 }
153
154 pub fn try_fire_due(&self) -> usize {
156 let now = chrono::Utc::now();
157 self.scheduler.try_fire_due(now)
158 }
159
160 pub fn task_count(&self) -> usize {
162 self.scheduler.list_tasks().len()
163 }
164
165 pub fn config(&self) -> &SchedulerRuntimeConfig {
167 &self.config
168 }
169
170 pub fn scheduler(&self) -> &CronScheduler {
172 &self.scheduler
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179 use std::sync::atomic::{AtomicUsize, Ordering};
180
181 struct CounterHandler {
183 counter: Arc<AtomicUsize>,
184 }
185
186 impl CounterHandler {
187 fn new() -> (Self, Arc<AtomicUsize>) {
188 let counter = Arc::new(AtomicUsize::new(0));
189 let handler = Self {
190 counter: counter.clone(),
191 };
192 (handler, counter)
193 }
194 }
195
196 impl JobHandler for CounterHandler {
197 fn handle(&self, _task: &ScheduledTask) -> Result<(), String> {
198 self.counter.fetch_add(1, Ordering::SeqCst);
199 Ok(())
200 }
201 }
202
203 #[test]
204 fn test_scheduler_runtime_config_default() {
205 let config = SchedulerRuntimeConfig::default();
206 assert_eq!(config.tick_ms, 1000);
207 }
208
209 #[test]
210 fn test_scheduler_runtime_config_custom() {
211 let config = SchedulerRuntimeConfig::new(500);
212 assert_eq!(config.tick_ms, 500);
213 }
214
215 #[test]
216 fn test_scheduler_runtime_config_zero_clamped() {
217 let config = SchedulerRuntimeConfig::new(0);
218 assert_eq!(config.tick_ms, 1);
219 }
220
221 #[test]
222 fn test_schedule_task() {
223 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
224 let task = ScheduledTask::new("task-1", "测试任务", "0 * * * *");
225 runtime.schedule(task).unwrap();
226 assert_eq!(runtime.task_count(), 1);
227 }
228
229 #[test]
230 fn test_schedule_multiple_tasks() {
231 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
232 runtime
233 .schedule(ScheduledTask::new("t1", "任务1", "0 * * * *"))
234 .unwrap();
235 runtime
236 .schedule(ScheduledTask::new("t2", "任务2", "0 0 * * *"))
237 .unwrap();
238 runtime
239 .schedule(ScheduledTask::new("t3", "任务3", "0 0 0 * *"))
240 .unwrap();
241 assert_eq!(runtime.task_count(), 3);
242 }
243
244 #[test]
245 fn test_cancel_task() {
246 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
247 runtime
248 .schedule(ScheduledTask::new("task-1", "测试", "0 * * * *"))
249 .unwrap();
250 assert_eq!(runtime.task_count(), 1);
251
252 runtime.cancel("task-1").unwrap();
253 assert_eq!(runtime.task_count(), 0);
254 }
255
256 #[test]
257 fn test_cancel_nonexistent_task() {
258 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
259 let result = runtime.cancel("nonexistent");
260 assert!(result.is_err());
261 }
262
263 #[test]
264 fn test_pause_resume_task() {
265 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
266 runtime
267 .schedule(ScheduledTask::new("task-1", "测试", "0 * * * *"))
268 .unwrap();
269
270 runtime.pause("task-1").unwrap();
271 let tasks = runtime.list_tasks();
272 assert!(!tasks[0].enabled);
273
274 runtime.resume("task-1").unwrap();
275 let tasks = runtime.list_tasks();
276 assert!(tasks[0].enabled);
277 }
278
279 #[test]
280 fn test_list_tasks() {
281 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
282 runtime
283 .schedule(ScheduledTask::new("t1", "任务1", "0 * * * *"))
284 .unwrap();
285 runtime
286 .schedule(ScheduledTask::new("t2", "任务2", "0 0 * * *"))
287 .unwrap();
288
289 let tasks = runtime.list_tasks();
290 assert_eq!(tasks.len(), 2);
291 }
292
293 #[test]
294 fn test_register_handler() {
295 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
296 runtime
297 .schedule(ScheduledTask::new("task-1", "测试", "0 * * * *"))
298 .unwrap();
299
300 let (handler, _counter) = CounterHandler::new();
301 runtime.register_handler("task-1", Arc::new(handler));
302 }
304
305 #[tokio::test]
306 async fn test_start_and_cancel() {
307 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::new(10));
308 let token = CancellationToken::new();
309 let handle = runtime.start(token.clone());
310
311 tokio::time::sleep(Duration::from_millis(50)).await;
313 token.cancel();
314
315 let result = tokio::time::timeout(Duration::from_secs(2), handle).await;
317 assert!(result.is_ok(), "scheduler task should stop on cancel");
318 }
319
320 #[tokio::test]
321 async fn test_scheduler_fires_due_task() {
322 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::new(10));
323 runtime
326 .schedule(ScheduledTask::new("every-second", "每秒任务", "* * * * *"))
327 .unwrap();
328
329 let (handler, counter) = CounterHandler::new();
330 runtime.register_handler("every-second", Arc::new(handler));
331
332 let token = CancellationToken::new();
333 let handle = runtime.start(token.clone());
334
335 tokio::time::sleep(Duration::from_millis(100)).await;
337 token.cancel();
338 let _ = handle.await;
339
340 assert!(
342 counter.load(Ordering::SeqCst) >= 1,
343 "task should have fired at least once"
344 );
345 }
346
347 #[test]
348 fn test_try_fire_due_manual() {
349 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
350 runtime
352 .schedule(ScheduledTask::new("every-second", "每秒任务", "* * * * *"))
353 .unwrap();
354
355 let (handler, counter) = CounterHandler::new();
356 runtime.register_handler("every-second", Arc::new(handler));
357
358 let fired = runtime.try_fire_due();
360 assert!(fired >= 1);
361 assert!(counter.load(Ordering::SeqCst) >= 1);
362 }
363
364 #[test]
365 fn test_try_fire_due_no_tasks() {
366 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
367 let fired = runtime.try_fire_due();
368 assert_eq!(fired, 0);
369 }
370
371 #[test]
372 fn test_config_accessor() {
373 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::new(250));
374 assert_eq!(runtime.config().tick_ms, 250);
375 }
376
377 #[test]
378 fn test_scheduler_accessor() {
379 let runtime = SchedulerRuntime::new(SchedulerRuntimeConfig::default());
380 let _scheduler = runtime.scheduler();
381 }
383}