Skip to main content

cja/cron/
mod.rs

1//! Scheduled task execution with interval-based and cron expression scheduling.
2//!
3//! The cron system runs jobs on a schedule, either at fixed intervals or using
4//! standard cron expressions with timezone support.
5//!
6//! # Interval Scheduling
7//!
8//! ```rust,ignore
9//! use cja::cron::CronRegistry;
10//! use std::time::Duration;
11//!
12//! let mut registry = CronRegistry::new();
13//!
14//! // Run a Job every 60 seconds
15//! registry.register_job(MyJob, Some("Description"), Duration::from_secs(60));
16//! ```
17//!
18//! # Cron Expression Scheduling
19//!
20//! ```rust,ignore
21//! // Run at 9 AM every day (cron syntax: sec min hour day month weekday year)
22//! registry.register_job_with_cron(
23//!     DailyReportJob,
24//!     Some("Daily report"),
25//!     "0 0 9 * * * *",
26//! )?;
27//! ```
28//!
29//! # Running the Worker
30//!
31//! ```rust,ignore
32//! use cja::cron::Worker;
33//! use chrono_tz::US::Eastern;
34//! use std::time::Duration;
35//!
36//! // With timezone and custom poll interval
37//! Worker::new_with_timezone(app_state, registry, Eastern, Duration::from_secs(30))
38//!     .run(shutdown_token)
39//!     .await?;
40//!
41//! // Or with defaults (UTC, 60s poll interval)
42//! Worker::new(app_state, registry)
43//!     .run(shutdown_token)
44//!     .await?;
45//! ```
46//!
47//! The default poll interval is 60 seconds. To change it, use
48//! [`Worker::new_with_timezone`] — there is no `new_with_interval` method.
49//!
50//! # Database-backed cron claims
51//!
52//! `register_job` and `register_job_with_cron` enqueue exactly once per due
53//! interval or cron slot across schedulers sharing a database. The row lock,
54//! queue insert, and `last_run_at` update commit in one transaction. A crash
55//! before commit rolls them all back, so the next tick retries. Job execution
56//! remains at-least-once because worker leases and retries are unchanged.
57//!
58//! `register` and `register_with_cron` callbacks are at-most-once per interval:
59//! the claim commits before the callback runs. If it errors or the process dies,
60//! that interval is skipped and the next interval fires normally.
61//! During a rollout, an older scheduler overlapping an upgraded one can still
62//! double-fire.
63//!
64//! # Queue Pileup Warning
65//!
66//! If a cron job takes longer to run than its scheduling interval, the queue
67//! grows unbounded (CJA does not deduplicate). For example, a 60-second interval
68//! with a 20-minute job runtime queues ~20 copies before the first finishes.
69//!
70//! Mitigations: use longer intervals, add job-level deduplication, or set timeouts.
71
72pub(crate) mod registry;
73pub use registry::{CronRegistry, CronSchedule, IntervalSchedule, Schedule};
74pub use tokio_util::sync::CancellationToken;
75
76mod worker;
77pub use worker::Worker;