1use chrono::{DateTime, Utc};
2
3use crate::error::{FlowError, Result};
4use crate::model::{project_run, FlowEvent, ScheduledWakeup, ScheduledWakeupKind, WaitStatus};
5use crate::store::scheduled_wakeups_for_snapshot;
6
7use super::{
8 validation::is_event_conflict, FlowEngine, ScheduledRunOutcome, WaitResolutionOutcome,
9};
10
11impl FlowEngine {
12 pub async fn resume_wait(&self, run_id: &str, wait_id: &str) -> Result<()> {
19 self.resume_wait_if_open(run_id, wait_id).await?;
20 Ok(())
21 }
22
23 pub(crate) async fn resume_wait_if_open(
25 &self,
26 run_id: &str,
27 wait_id: &str,
28 ) -> Result<WaitResolutionOutcome> {
29 self.resume_wait_if_open_at(run_id, wait_id, Utc::now())
30 .await
31 }
32
33 async fn resume_wait_if_open_at(
34 &self,
35 run_id: &str,
36 wait_id: &str,
37 now: DateTime<Utc>,
38 ) -> Result<WaitResolutionOutcome> {
39 let mut resumed = false;
40 for _ in 0..self.max_replay_iterations {
41 let snapshot = self.snapshot(run_id).await?;
42 let Some(wait) = snapshot.waits.get(wait_id) else {
43 if snapshot.status.is_terminal() {
44 return Err(FlowError::RunTerminal(run_id.to_string()));
45 }
46 return Err(FlowError::InvalidTransition(format!(
47 "wait {wait_id} does not exist for run {run_id}"
48 )));
49 };
50 if snapshot.status.is_terminal() {
51 match self
52 .recover_and_drive_continuation_leaf_at(run_id, now)
53 .await
54 {
55 Ok(snapshot) => return Ok(wait_resolution(run_id, wait_id, snapshot, resumed)),
56 Err(error) if is_event_conflict(&error) => continue,
57 Err(error) => return Err(error),
58 }
59 }
60
61 match wait.status {
62 WaitStatus::Waiting => {
63 self.ensure_runtime_build_available(run_id, &snapshot.spec)?;
64 match self
65 .record_event_at(
66 run_id,
67 snapshot.last_sequence,
68 FlowEvent::WaitCompleted {
69 wait_id: wait_id.to_string(),
70 },
71 )
72 .await
73 {
74 Ok(_) => resumed = true,
75 Err(error) if is_event_conflict(&error) => continue,
76 Err(error) => return Err(error),
77 }
78 }
79 WaitStatus::Completed | WaitStatus::Cancelled => {
80 self.ensure_runtime_build_available(run_id, &snapshot.spec)?;
81 }
82 }
83
84 match self
85 .recover_and_drive_continuation_leaf_at(run_id, now)
86 .await
87 {
88 Ok(snapshot) => return Ok(wait_resolution(run_id, wait_id, snapshot, resumed)),
89 Err(error) if is_event_conflict(&error) => continue,
90 Err(error) => return Err(error),
91 }
92 }
93
94 Err(FlowError::ReplayLimitExceeded(self.max_replay_iterations))
95 }
96
97 pub async fn list_due_waits(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
102 let mut due = self
103 .list_due_wakeups(now)
104 .await?
105 .into_iter()
106 .filter(|wakeup| wakeup.kind == ScheduledWakeupKind::Wait)
107 .map(|wakeup| (wakeup.run_id, wakeup.subject_id))
108 .collect::<Vec<_>>();
109 due.sort();
110 Ok(due)
111 }
112
113 pub async fn resume_due_waits(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
119 let due = self.list_due_waits(now).await?;
120 let mut resumed = Vec::with_capacity(due.len());
121 for (run_id, wait_id) in due {
122 let resolution = self.resume_wait_if_open_at(&run_id, &wait_id, now).await?;
123 if resolution.committed {
124 resumed.push((resolution.wait_run_id, resolution.wait_id));
125 }
126 }
127 Ok(resumed)
128 }
129
130 pub async fn list_due_retries(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
132 let mut due = self
133 .list_due_wakeups(now)
134 .await?
135 .into_iter()
136 .filter(|wakeup| wakeup.kind == ScheduledWakeupKind::Retry)
137 .map(|wakeup| (wakeup.run_id, wakeup.subject_id))
138 .collect::<Vec<_>>();
139 due.sort();
140 Ok(due)
141 }
142
143 pub async fn list_due_wakeups(&self, now: DateTime<Utc>) -> Result<Vec<ScheduledWakeup>> {
145 let mut wakeups = self.store.list_due_wakeups(now).await?;
146 wakeups.sort_by(|left, right| {
147 (left.kind, left.run_id.as_str(), left.subject_id.as_str()).cmp(&(
148 right.kind,
149 right.run_id.as_str(),
150 right.subject_id.as_str(),
151 ))
152 });
153 Ok(wakeups)
154 }
155
156 pub async fn resume_due_retries(&self, now: DateTime<Utc>) -> Result<Vec<(String, String)>> {
158 let due = self.list_due_retries(now).await?;
159 let mut run_ids = Vec::new();
160 for (run_id, _) in &due {
161 if !run_ids.contains(run_id) {
162 run_ids.push(run_id.clone());
163 }
164 }
165 for run_id in run_ids {
166 self.drive_at(&run_id, now).await?;
167 }
168 Ok(due)
169 }
170
171 pub async fn resume_scheduled_run(
178 &self,
179 run_id: &str,
180 now: DateTime<Utc>,
181 ) -> Result<Vec<ScheduledWakeup>> {
182 let outcome = self
183 .resume_scheduled_run_with_committed_waits(run_id, now)
184 .await?;
185 Ok(outcome.due)
186 }
187
188 pub(crate) async fn resume_scheduled_run_with_committed_waits(
190 &self,
191 run_id: &str,
192 now: DateTime<Utc>,
193 ) -> Result<ScheduledRunOutcome> {
194 let history = self.store.list(run_id).await?;
195 let snapshot = project_run(run_id, &history)?;
196 if snapshot.status.is_terminal() {
197 let snapshot = self
198 .recover_and_drive_continuation_leaf_at(run_id, now)
199 .await?;
200 return Ok(ScheduledRunOutcome {
201 snapshot,
202 due: Vec::new(),
203 resumed_waits: Vec::new(),
204 });
205 }
206 self.ensure_runtime_build_available(run_id, &snapshot.spec)?;
207 let due = scheduled_wakeups_for_snapshot(&snapshot)
208 .into_iter()
209 .filter(|wakeup| wakeup.scheduled_at <= now)
210 .collect::<Vec<_>>();
211
212 let due_wait_ids = due
213 .iter()
214 .filter(|wakeup| wakeup.kind == ScheduledWakeupKind::Wait)
215 .map(|wakeup| wakeup.subject_id.clone())
216 .collect::<Vec<_>>();
217 let has_due_retries = due
218 .iter()
219 .any(|wakeup| wakeup.kind == ScheduledWakeupKind::Retry);
220
221 let mut resumed_waits = Vec::with_capacity(due_wait_ids.len());
222 let mut driven_snapshot = snapshot;
223 for wait_id in due_wait_ids {
224 let resolution = self.resume_wait_if_open_at(run_id, &wait_id, now).await?;
225 driven_snapshot = resolution.snapshot;
226 if resolution.committed {
227 resumed_waits.push((resolution.wait_run_id, resolution.wait_id));
228 }
229 }
230 if has_due_retries {
231 driven_snapshot = self
232 .recover_and_drive_continuation_leaf_at(run_id, now)
233 .await?;
234 }
235
236 Ok(ScheduledRunOutcome {
237 snapshot: driven_snapshot,
238 due,
239 resumed_waits,
240 })
241 }
242}
243
244fn wait_resolution(
245 run_id: &str,
246 wait_id: &str,
247 snapshot: crate::WorkflowRunSnapshot,
248 committed: bool,
249) -> WaitResolutionOutcome {
250 WaitResolutionOutcome {
251 wait_run_id: run_id.to_string(),
252 wait_id: wait_id.to_string(),
253 snapshot,
254 committed,
255 }
256}