1use std::collections::HashSet;
30use std::sync::{Arc, RwLock};
31
32use aion_core::{
33 Event, RunId, SearchAttributeSchema, WorkflowId, WorkflowStatus, run_segment,
34 status_from_events,
35};
36use aion_store::EventStore;
37use aion_store::visibility::VisibilityStore;
38use chrono::Utc;
39
40use crate::EngineError;
41use crate::durability::Recorder;
42use crate::loader::WorkflowCatalog;
43use crate::registry::{Registry, WorkflowHandle};
44use crate::runtime::RuntimeHandle;
45use crate::supervision::SupervisionTree;
46
47use super::reopen::{self, ReopenWorkflowContext};
48
49#[derive(Clone, Debug, Default)]
59pub struct PausedRuns {
60 inner: Arc<RwLock<HashSet<WorkflowId>>>,
61}
62
63impl PausedRuns {
64 pub fn insert(&self, workflow_id: WorkflowId) {
66 if let Ok(mut set) = self.inner.write() {
67 set.insert(workflow_id);
68 }
69 }
70
71 pub fn remove(&self, workflow_id: &WorkflowId) {
73 if let Ok(mut set) = self.inner.write() {
74 set.remove(workflow_id);
75 }
76 }
77
78 #[must_use]
80 pub fn snapshot(&self) -> HashSet<WorkflowId> {
81 self.inner.read().map(|set| set.clone()).unwrap_or_default()
82 }
83
84 pub fn replace_all(&self, workflow_ids: impl IntoIterator<Item = WorkflowId>) {
87 if let Ok(mut set) = self.inner.write() {
88 *set = workflow_ids.into_iter().collect();
89 }
90 }
91
92 pub fn extend(&self, workflow_ids: impl IntoIterator<Item = WorkflowId>) {
95 if let Ok(mut set) = self.inner.write() {
96 set.extend(workflow_ids);
97 }
98 }
99}
100
101pub struct PauseWorkflowContext<'a> {
103 pub store: Arc<dyn EventStore>,
105 pub visibility_store: Arc<dyn VisibilityStore>,
107 pub catalog: Arc<WorkflowCatalog>,
109 pub runtime: &'a Arc<RuntimeHandle>,
111 pub supervision: Arc<SupervisionTree>,
113 pub registry: &'a Arc<Registry>,
115 pub search_attribute_schema: Arc<SearchAttributeSchema>,
117 pub paused_runs: PausedRuns,
119}
120
121impl<'a> PauseWorkflowContext<'a> {
122 fn reopen_context(&self) -> ReopenWorkflowContext<'a> {
123 ReopenWorkflowContext {
124 store: Arc::clone(&self.store),
125 visibility_store: Arc::clone(&self.visibility_store),
126 catalog: Arc::clone(&self.catalog),
127 runtime: self.runtime,
128 supervision: Arc::clone(&self.supervision),
129 registry: self.registry,
130 search_attribute_schema: Arc::clone(&self.search_attribute_schema),
131 }
132 }
133}
134
135fn status_name(status: WorkflowStatus) -> &'static str {
136 match status {
137 WorkflowStatus::Running => "Running",
138 WorkflowStatus::Completed => "Completed",
139 WorkflowStatus::Failed => "Failed",
140 WorkflowStatus::Cancelled => "Cancelled",
141 WorkflowStatus::TimedOut => "TimedOut",
142 WorkflowStatus::ContinuedAsNew => "ContinuedAsNew",
143 WorkflowStatus::Paused => "Paused",
144 }
145}
146
147pub async fn pause(
158 context: &PauseWorkflowContext<'_>,
159 id: &WorkflowId,
160 run: &RunId,
161 reason: Option<String>,
162 operator: Option<String>,
163) -> Result<WorkflowHandle, EngineError> {
164 let history = context.store.read_history(id).await?;
167 if history.is_empty() {
168 return Err(crate::engine::api::workflow_not_found(id, run));
169 }
170 let segment = run_segment(&history, run);
171 if segment.is_empty() {
172 return Err(crate::engine::api::workflow_not_found(id, run));
173 }
174 let status = status_from_events(segment);
175 if status != WorkflowStatus::Running {
176 return Err(EngineError::InvalidState {
177 reason: format!(
178 "workflow {id} run {run} is {}, not Running; only a Running run can be paused",
179 status_name(status)
180 ),
181 });
182 }
183
184 let handle = context
186 .registry
187 .get(id, run)?
188 .ok_or_else(|| crate::engine::api::workflow_not_found(id, run))?;
189 {
190 let recorder = handle.recorder();
191 let mut recorder = recorder.lock().await;
192 let history = context.store.read_history(id).await?;
196 let segment = run_segment(&history, run);
197 let status = status_from_events(segment);
198 if status != WorkflowStatus::Running {
199 return Err(EngineError::InvalidState {
200 reason: format!(
201 "workflow {id} run {run} is {}, not Running; only a Running run can be paused",
202 status_name(status)
203 ),
204 });
205 }
206 recorder
207 .record_workflow_paused(Utc::now(), run.clone(), reason, operator)
208 .await?;
209 }
210 context.paused_runs.insert(id.clone());
211 Ok(handle)
212}
213
214pub async fn resume(
224 context: &PauseWorkflowContext<'_>,
225 id: &WorkflowId,
226 run: &RunId,
227 operator: Option<String>,
228) -> Result<WorkflowHandle, EngineError> {
229 let history = context.store.read_history(id).await?;
230 if history.is_empty() {
231 return Err(crate::engine::api::workflow_not_found(id, run));
232 }
233 let segment = run_segment(&history, run);
234 if segment.is_empty() {
235 return Err(crate::engine::api::workflow_not_found(id, run));
236 }
237 let status = status_from_events(segment);
238 if status != WorkflowStatus::Paused {
239 return Err(EngineError::InvalidState {
240 reason: format!(
241 "workflow {id} run {run} is {}, not Paused; only a Paused run can be resumed",
242 status_name(status)
243 ),
244 });
245 }
246
247 if let Some(handle) = context.registry.get(id, run)? {
250 {
251 let recorder = handle.recorder();
252 let mut recorder = recorder.lock().await;
253 let history = context.store.read_history(id).await?;
254 let segment = run_segment(&history, run);
255 let status = status_from_events(segment);
256 if status != WorkflowStatus::Paused {
257 return Err(EngineError::InvalidState {
258 reason: format!(
259 "workflow {id} run {run} is {}, not Paused; only a Paused run can be resumed",
260 status_name(status)
261 ),
262 });
263 }
264 recorder
265 .record_workflow_resumed(Utc::now(), run.clone(), operator)
266 .await?;
267 }
268 context.paused_runs.remove(id);
269 return Ok(handle);
270 }
271
272 let rearm = reopen::rearmable_timers(segment);
276 let history_head = history.last().map(Event::seq).unwrap_or_default();
277 let mut recorder = Recorder::resume_at(id.clone(), Arc::clone(&context.store), history_head)
278 .with_visibility(run.clone(), Arc::clone(&context.visibility_store));
279 recorder
280 .record_workflow_resumed(Utc::now(), run.clone(), operator)
281 .await?;
282 for timer in rearm.iter().filter(|timer| timer.needs_restart_marker) {
283 recorder
284 .record_timer_started(Utc::now(), timer.timer_id.clone(), timer.fire_at)
285 .await?;
286 }
287 context.paused_runs.remove(id);
290
291 let reopen_context = context.reopen_context();
292 let history = context.store.read_history(id).await?;
293 let handle = reopen::respawn_and_register(&reopen_context, id, run, &history, recorder).await?;
294 reopen::rearm_reopened_timers(&reopen_context, id, handle.pid(), &rearm).await?;
295 Ok(handle)
296}