Skip to main content

modo/job/
config.rs

1use serde::Deserialize;
2
3/// Top-level configuration for the job worker.
4///
5/// Deserializes from YAML under the `job` key. All fields have defaults so an
6/// empty config block is valid.
7///
8/// # Defaults
9///
10/// | Field | Default |
11/// |---|---|
12/// | `poll_interval_secs` | `1` |
13/// | `stale_threshold_secs` | `600` (10 min) |
14/// | `stale_reaper_interval_secs` | `60` (1 min) |
15/// | `drain_timeout_secs` | `30` |
16/// | `queues` | one `"default"` queue with concurrency 4 |
17/// | `cleanup` | enabled, 1 h interval, 72 h retention |
18#[non_exhaustive]
19#[derive(Debug, Clone, Deserialize)]
20#[serde(default)]
21pub struct JobConfig {
22    /// How often the worker polls the database for new jobs, in seconds.
23    pub poll_interval_secs: u64,
24    /// Jobs stuck in `running` for longer than this many seconds are considered
25    /// stale and reset to `pending` by the reaper.
26    pub stale_threshold_secs: u64,
27    /// How often the stale reaper runs, in seconds.
28    pub stale_reaper_interval_secs: u64,
29    /// Maximum time in seconds to wait for in-flight jobs to finish during
30    /// graceful shutdown.
31    pub drain_timeout_secs: u64,
32    /// Queue definitions. Defaults to a single `"default"` queue.
33    pub queues: Vec<QueueConfig>,
34    /// Optional periodic cleanup of terminal jobs. Set to `None` to disable.
35    pub cleanup: Option<CleanupConfig>,
36    /// Separate SQLite database for the job queue. When set, the job worker
37    /// uses this pool instead of the main application database, keeping
38    /// job-queue writes from contending with app queries.
39    pub database: Option<crate::db::Config>,
40}
41
42impl Default for JobConfig {
43    fn default() -> Self {
44        Self {
45            poll_interval_secs: 1,
46            stale_threshold_secs: 600,
47            stale_reaper_interval_secs: 60,
48            drain_timeout_secs: 30,
49            queues: vec![QueueConfig::default()],
50            cleanup: Some(CleanupConfig::default()),
51            database: None,
52        }
53    }
54}
55
56/// Configuration for a single named queue.
57#[non_exhaustive]
58#[derive(Debug, Clone, Deserialize)]
59pub struct QueueConfig {
60    /// Queue name. Must match the `queue` field used when enqueuing jobs.
61    pub name: String,
62    /// Maximum number of jobs from this queue that run concurrently.
63    /// Defaults to `4`.
64    #[serde(default = "default_concurrency")]
65    pub concurrency: u32,
66}
67
68fn default_concurrency() -> u32 {
69    4
70}
71
72impl Default for QueueConfig {
73    fn default() -> Self {
74        Self {
75            name: "default".to_string(),
76            concurrency: 4,
77        }
78    }
79}
80
81/// Configuration for the periodic cleanup of terminal jobs.
82///
83/// Terminal jobs (status `completed`, `dead`, or `cancelled`) whose
84/// `updated_at` is older than `retention_secs` are deleted from the database.
85#[non_exhaustive]
86#[derive(Debug, Clone, Deserialize)]
87#[serde(default)]
88pub struct CleanupConfig {
89    /// How often the cleanup task runs, in seconds. Defaults to `3600` (1 h).
90    pub interval_secs: u64,
91    /// Jobs whose `updated_at` is older than this many seconds are deleted.
92    /// Defaults to `259200` (72 h).
93    pub retention_secs: u64,
94}
95
96impl Default for CleanupConfig {
97    fn default() -> Self {
98        Self {
99            interval_secs: 3600,
100            retention_secs: 259_200, // 72h
101        }
102    }
103}