use std::collections::HashSet;
use std::sync::{Arc, RwLock};
use aion_core::{
Event, RunId, SearchAttributeSchema, WorkflowId, WorkflowStatus, run_segment,
status_from_events,
};
use aion_store::EventStore;
use aion_store::visibility::VisibilityStore;
use chrono::Utc;
use crate::EngineError;
use crate::durability::Recorder;
use crate::loader::WorkflowCatalog;
use crate::registry::{Registry, WorkflowHandle};
use crate::runtime::RuntimeHandle;
use crate::supervision::SupervisionTree;
use super::reopen::{self, ReopenWorkflowContext};
#[derive(Clone, Debug, Default)]
pub struct PausedRuns {
inner: Arc<RwLock<HashSet<WorkflowId>>>,
}
impl PausedRuns {
pub fn insert(&self, workflow_id: WorkflowId) {
if let Ok(mut set) = self.inner.write() {
set.insert(workflow_id);
}
}
pub fn remove(&self, workflow_id: &WorkflowId) {
if let Ok(mut set) = self.inner.write() {
set.remove(workflow_id);
}
}
#[must_use]
pub fn snapshot(&self) -> HashSet<WorkflowId> {
self.inner.read().map(|set| set.clone()).unwrap_or_default()
}
pub fn replace_all(&self, workflow_ids: impl IntoIterator<Item = WorkflowId>) {
if let Ok(mut set) = self.inner.write() {
*set = workflow_ids.into_iter().collect();
}
}
pub fn extend(&self, workflow_ids: impl IntoIterator<Item = WorkflowId>) {
if let Ok(mut set) = self.inner.write() {
set.extend(workflow_ids);
}
}
}
pub struct PauseWorkflowContext<'a> {
pub store: Arc<dyn EventStore>,
pub visibility_store: Arc<dyn VisibilityStore>,
pub catalog: Arc<WorkflowCatalog>,
pub runtime: &'a Arc<RuntimeHandle>,
pub supervision: Arc<SupervisionTree>,
pub registry: &'a Arc<Registry>,
pub search_attribute_schema: Arc<SearchAttributeSchema>,
pub paused_runs: PausedRuns,
}
impl<'a> PauseWorkflowContext<'a> {
fn reopen_context(&self) -> ReopenWorkflowContext<'a> {
ReopenWorkflowContext {
store: Arc::clone(&self.store),
visibility_store: Arc::clone(&self.visibility_store),
catalog: Arc::clone(&self.catalog),
runtime: self.runtime,
supervision: Arc::clone(&self.supervision),
registry: self.registry,
search_attribute_schema: Arc::clone(&self.search_attribute_schema),
}
}
}
fn status_name(status: WorkflowStatus) -> &'static str {
match status {
WorkflowStatus::Running => "Running",
WorkflowStatus::Completed => "Completed",
WorkflowStatus::Failed => "Failed",
WorkflowStatus::Cancelled => "Cancelled",
WorkflowStatus::TimedOut => "TimedOut",
WorkflowStatus::ContinuedAsNew => "ContinuedAsNew",
WorkflowStatus::Paused => "Paused",
}
}
pub async fn pause(
context: &PauseWorkflowContext<'_>,
id: &WorkflowId,
run: &RunId,
reason: Option<String>,
operator: Option<String>,
) -> Result<WorkflowHandle, EngineError> {
let history = context.store.read_history(id).await?;
if history.is_empty() {
return Err(crate::engine::api::workflow_not_found(id, run));
}
let segment = run_segment(&history, run);
if segment.is_empty() {
return Err(crate::engine::api::workflow_not_found(id, run));
}
let status = status_from_events(segment);
if status != WorkflowStatus::Running {
return Err(EngineError::InvalidState {
reason: format!(
"workflow {id} run {run} is {}, not Running; only a Running run can be paused",
status_name(status)
),
});
}
let handle = context
.registry
.get(id, run)?
.ok_or_else(|| crate::engine::api::workflow_not_found(id, run))?;
{
let recorder = handle.recorder();
let mut recorder = recorder.lock().await;
let history = context.store.read_history(id).await?;
let segment = run_segment(&history, run);
let status = status_from_events(segment);
if status != WorkflowStatus::Running {
return Err(EngineError::InvalidState {
reason: format!(
"workflow {id} run {run} is {}, not Running; only a Running run can be paused",
status_name(status)
),
});
}
recorder
.record_workflow_paused(Utc::now(), run.clone(), reason, operator)
.await?;
}
context.paused_runs.insert(id.clone());
Ok(handle)
}
pub async fn resume(
context: &PauseWorkflowContext<'_>,
id: &WorkflowId,
run: &RunId,
operator: Option<String>,
) -> Result<WorkflowHandle, EngineError> {
let history = context.store.read_history(id).await?;
if history.is_empty() {
return Err(crate::engine::api::workflow_not_found(id, run));
}
let segment = run_segment(&history, run);
if segment.is_empty() {
return Err(crate::engine::api::workflow_not_found(id, run));
}
let status = status_from_events(segment);
if status != WorkflowStatus::Paused {
return Err(EngineError::InvalidState {
reason: format!(
"workflow {id} run {run} is {}, not Paused; only a Paused run can be resumed",
status_name(status)
),
});
}
if let Some(handle) = context.registry.get(id, run)? {
{
let recorder = handle.recorder();
let mut recorder = recorder.lock().await;
let history = context.store.read_history(id).await?;
let segment = run_segment(&history, run);
let status = status_from_events(segment);
if status != WorkflowStatus::Paused {
return Err(EngineError::InvalidState {
reason: format!(
"workflow {id} run {run} is {}, not Paused; only a Paused run can be resumed",
status_name(status)
),
});
}
recorder
.record_workflow_resumed(Utc::now(), run.clone(), operator)
.await?;
}
context.paused_runs.remove(id);
return Ok(handle);
}
let rearm = reopen::rearmable_timers(segment);
let history_head = history.last().map(Event::seq).unwrap_or_default();
let mut recorder = Recorder::resume_at(id.clone(), Arc::clone(&context.store), history_head)
.with_visibility(run.clone(), Arc::clone(&context.visibility_store));
recorder
.record_workflow_resumed(Utc::now(), run.clone(), operator)
.await?;
for timer in rearm.iter().filter(|timer| timer.needs_restart_marker) {
recorder
.record_timer_started(Utc::now(), timer.timer_id.clone(), timer.fire_at)
.await?;
}
context.paused_runs.remove(id);
let reopen_context = context.reopen_context();
let history = context.store.read_history(id).await?;
let handle = reopen::respawn_and_register(&reopen_context, id, run, &history, recorder).await?;
reopen::rearm_reopened_timers(&reopen_context, id, &rearm).await?;
Ok(handle)
}