Skip to main content

chronon_core/models/
run.rs

1//! Run model - represents an execution instance of a job.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// Status of a run.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum RunStatus {
11    /// Run is waiting to be executed (distributed: worker has not claimed yet).
12    #[default]
13    Queued,
14    /// Run is claimed by a worker (lease held); not yet marked as executing.
15    Claimed,
16    /// Run is currently executing.
17    Running,
18    /// Run completed successfully.
19    Success,
20    /// Run failed with an error.
21    Failed,
22    /// Run was manually canceled.
23    Canceled,
24    /// Run exceeded its timeout.
25    Timeout,
26}
27
28impl RunStatus {
29    /// Check if the run is in a terminal state.
30    pub const fn is_terminal(&self) -> bool {
31        matches!(
32            self,
33            Self::Success | Self::Failed | Self::Canceled | Self::Timeout
34        )
35    }
36
37    /// Check if the run is currently active.
38    pub const fn is_active(&self) -> bool {
39        matches!(self, Self::Queued | Self::Claimed | Self::Running)
40    }
41}
42
43impl std::fmt::Display for RunStatus {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Self::Queued => write!(f, "queued"),
47            Self::Claimed => write!(f, "claimed"),
48            Self::Running => write!(f, "running"),
49            Self::Success => write!(f, "success"),
50            Self::Failed => write!(f, "failed"),
51            Self::Canceled => write!(f, "canceled"),
52            Self::Timeout => write!(f, "timeout"),
53        }
54    }
55}
56
57/// An execution instance of a scheduled job.
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct Run {
60    /// Unique identifier (UUID).
61    pub run_id: String,
62
63    /// Job that spawned this run (None for ad-hoc child runs).
64    pub job_id: Option<String>,
65
66    /// Script being executed.
67    pub script_name: String,
68
69    // Parent/child lineage
70    /// Parent run that spawned this run (for child runs).
71    pub parent_run_id: Option<String>,
72
73    /// Root run in the lineage tree.
74    pub root_run_id: Option<String>,
75
76    /// Index among siblings (for child runs).
77    pub child_index: Option<i32>,
78
79    // Timing
80    /// When the run was scheduled to execute.
81    pub scheduled_for: DateTime<Utc>,
82
83    /// When execution actually started.
84    pub started_at: Option<DateTime<Utc>>,
85
86    /// When execution finished.
87    pub finished_at: Option<DateTime<Utc>>,
88
89    /// Execution duration in milliseconds.
90    pub duration_ms: Option<i64>,
91
92    // Status
93    /// Current status of the run.
94    pub status: RunStatus,
95
96    /// Retry attempt number (1 for first attempt).
97    pub attempt: i32,
98
99    // Distributed-mode fields
100    /// Instance ID that executed this run.
101    pub instance_id: Option<String>,
102
103    /// Placement information.
104    pub placement_json: Option<Value>,
105
106    /// Target execution pool (distributed mode). `None` in DB means the `"default"` pool.
107    pub pool_id: Option<String>,
108
109    // Identity for identity reconstruction
110    /// Serialized Actor for identity reconstruction.
111    pub actor_json: Value,
112
113    /// Parameters passed to the script.
114    pub params_json: Value,
115
116    // Output
117    /// Captured stdout from the script.
118    pub stdout_text: Option<String>,
119
120    /// Captured stderr from the script.
121    pub stderr_text: Option<String>,
122
123    /// Error details if the run failed.
124    pub error_json: Option<Value>,
125
126    /// Execution statistics.
127    pub stats_json: Option<Value>,
128
129    /// Worker instance id while `status` is `claimed` / renewed during execution.
130    pub claimed_by: Option<String>,
131
132    /// Worker lease expiry for reclaim / failover.
133    pub claim_lease_until: Option<DateTime<Utc>>,
134}
135
136impl Run {
137    /// Create a queued run with generated IDs and empty runtime fields.
138    ///
139    /// The run starts in `RunStatus::Queued`. Timestamps, duration, and error
140    /// details are populated later by `start`, `complete`, or `fail`.
141    pub fn new(script_name: impl Into<String>, scheduled_for: DateTime<Utc>) -> Self {
142        Self {
143            run_id: uuid::Uuid::new_v4().to_string(),
144            job_id: None,
145            script_name: script_name.into(),
146            parent_run_id: None,
147            root_run_id: None,
148            child_index: None,
149            scheduled_for,
150            started_at: None,
151            finished_at: None,
152            duration_ms: None,
153            status: RunStatus::Queued,
154            attempt: 1,
155            instance_id: None,
156            placement_json: None,
157            pool_id: None,
158            actor_json: Value::Null,
159            params_json: Value::Object(serde_json::Map::default()),
160            stdout_text: None,
161            stderr_text: None,
162            error_json: None,
163            stats_json: None,
164            claimed_by: None,
165            claim_lease_until: None,
166        }
167    }
168
169    /// Create a run linked to a specific job.
170    pub fn for_job(
171        job_id: impl Into<String>,
172        script_name: impl Into<String>,
173        scheduled_for: DateTime<Utc>,
174    ) -> Self {
175        let mut run = Self::new(script_name, scheduled_for);
176        run.job_id = Some(job_id.into());
177        run
178    }
179
180    /// Mark the run as started.
181    pub fn start(&mut self) {
182        self.started_at = Some(Utc::now());
183        self.status = RunStatus::Running;
184    }
185
186    /// Mark the run as successfully completed.
187    pub fn complete(&mut self) {
188        let finished = Utc::now();
189        self.finished_at = Some(finished);
190        self.status = RunStatus::Success;
191        if let Some(started) = self.started_at {
192            self.duration_ms = Some((finished - started).num_milliseconds());
193        }
194    }
195
196    /// Mark the run as failed.
197    pub fn fail(&mut self, error: impl Into<String>) {
198        let finished = Utc::now();
199        self.finished_at = Some(finished);
200        self.status = RunStatus::Failed;
201        self.error_json = Some(serde_json::json!({ "message": error.into() }));
202        if let Some(started) = self.started_at {
203            self.duration_ms = Some((finished - started).num_milliseconds());
204        }
205    }
206
207    /// Mark the run as timed out.
208    pub fn timeout(&mut self, error: impl Into<String>) {
209        let finished = Utc::now();
210        self.finished_at = Some(finished);
211        self.status = RunStatus::Timeout;
212        self.error_json = Some(serde_json::json!({ "message": error.into() }));
213        if let Some(started) = self.started_at {
214            self.duration_ms = Some((finished - started).num_milliseconds());
215        }
216    }
217}