Skip to main content

scheduler/model/
schedule.rs

1use super::CronSchedule;
2use chrono::{DateTime, Utc};
3use chrono_tz::{Asia::Shanghai, Tz};
4use std::time::Duration;
5
6/// Interval schedule with a stable phase derived from a seed.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct StaggeredIntervalSchedule {
9    pub every: Duration,
10    pub seed: Option<String>,
11}
12
13impl StaggeredIntervalSchedule {
14    pub fn new(every: Duration) -> Self {
15        Self { every, seed: None }
16    }
17
18    pub fn with_seed(mut self, seed: impl Into<String>) -> Self {
19        self.seed = Some(seed.into());
20        self
21    }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum Schedule {
26    /// Trigger repeatedly after the given interval.
27    Interval(Duration),
28    /// Trigger repeatedly after the given interval with a stable phase.
29    ///
30    /// The first run is aligned to a deterministic phase derived from the
31    /// seed. If no seed is provided, the job id is used.
32    StaggeredInterval(StaggeredIntervalSchedule),
33    /// Trigger at the listed wall-clock times.
34    ///
35    /// The list is sorted before execution starts. An empty list is treated as
36    /// a no-op schedule and exits without running.
37    AtTimes(Vec<DateTime<Tz>>),
38    /// Trigger using a standard 5-field cron expression.
39    ///
40    /// The scheduler timezone is used to calculate the next matching wall-clock
41    /// time. The expression itself does not carry timezone information.
42    Cron(CronSchedule),
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
46pub enum MissedRunPolicy {
47    Skip,
48    #[default]
49    CatchUpOnce,
50    ReplayAll,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum OverlapPolicy {
55    #[default]
56    Forbid,
57    QueueOne,
58    AllowParallel,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
62pub enum TerminalStatePolicy {
63    #[default]
64    Retain,
65    Delete,
66}
67
68#[derive(Debug, Clone)]
69pub struct SchedulerConfig {
70    /// The timezone forwarded to each [`crate::RunContext`] and used to
71    /// evaluate [`Schedule::Cron`] expressions.
72    ///
73    /// This does not rewrite [`Schedule::AtTimes`] values, which already carry
74    /// their own timezone-aware timestamps.
75    pub timezone: Tz,
76    /// The maximum number of [`crate::RunRecord`] items kept in memory.
77    pub history_limit: usize,
78    /// Controls how persisted state is handled once a job reaches a terminal
79    /// state (`next_run_at == None` and no further work is pending).
80    pub terminal_state_policy: TerminalStatePolicy,
81}
82
83impl Default for SchedulerConfig {
84    fn default() -> Self {
85        Self {
86            timezone: Shanghai,
87            history_limit: 32,
88            terminal_state_policy: TerminalStatePolicy::Retain,
89        }
90    }
91}
92
93impl Schedule {
94    /// Create a staggered interval schedule that defaults to the job id as
95    /// the phase seed.
96    pub fn staggered_interval(every: Duration) -> Self {
97        Self::StaggeredInterval(StaggeredIntervalSchedule::new(every))
98    }
99
100    /// Create a staggered interval schedule with an explicit phase seed.
101    pub fn staggered_interval_with_seed(every: Duration, seed: impl Into<String>) -> Self {
102        Self::StaggeredInterval(StaggeredIntervalSchedule::new(every).with_seed(seed))
103    }
104}
105
106pub(crate) fn utc_time(value: DateTime<Tz>) -> DateTime<Utc> {
107    value.with_timezone(&Utc)
108}