1use aion_core::{ActivityError, ActivityId, Payload, TimerId, WorkflowError, WorkflowId};
4use async_trait::async_trait;
5use chrono::{DateTime, Utc};
6
7use crate::durability::{
8 Command, CorrelationKey, DurabilityError, Recorder, Resolution, ResolveOutcome, Resolver,
9};
10
11#[derive(Clone, Debug, PartialEq)]
13pub enum LiveActivityOutcome {
14 Completed(Payload),
16 Failed(ActivityError),
18}
19
20#[derive(Clone, Debug, PartialEq)]
22pub enum LiveChildOutcome {
23 Completed {
25 child_workflow_id: WorkflowId,
27 package_version: aion_core::PackageVersion,
29 result: Payload,
31 },
32 Failed {
34 child_workflow_id: WorkflowId,
36 package_version: aion_core::PackageVersion,
38 error: WorkflowError,
40 },
41}
42
43impl LiveChildOutcome {
44 fn package_version(&self) -> aion_core::PackageVersion {
45 match self {
46 Self::Completed {
47 package_version, ..
48 }
49 | Self::Failed {
50 package_version, ..
51 } => package_version.clone(),
52 }
53 }
54
55 fn child_workflow_id(&self) -> WorkflowId {
56 match self {
57 Self::Completed {
58 child_workflow_id, ..
59 }
60 | Self::Failed {
61 child_workflow_id, ..
62 } => child_workflow_id.clone(),
63 }
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
69pub enum HandoffOutcome {
70 Resolved(Resolution),
72 WorkflowCompleted,
74}
75
76#[async_trait]
92pub trait LiveExecutor: Send + Sync {
93 async fn run_activity(
99 &self,
100 activity_type: String,
101 input: Payload,
102 ) -> Result<LiveActivityOutcome, DurabilityError>;
103
104 async fn start_timer(
113 &self,
114 timer_id: TimerId,
115 fire_at: DateTime<Utc>,
116 ) -> Result<(), DurabilityError>;
117
118 async fn await_signal(&self, name: String, index: usize) -> Result<Payload, DurabilityError>;
124
125 async fn spawn_child(
131 &self,
132 workflow_type: String,
133 input: Payload,
134 ) -> Result<LiveChildOutcome, DurabilityError>;
135}
136
137pub async fn resolve_or_execute_live(
151 resolver: &mut Resolver,
152 recorder: &mut Recorder,
153 executor: &dyn LiveExecutor,
154 command: Command,
155 recorded_at: DateTime<Utc>,
156) -> Result<HandoffOutcome, DurabilityError> {
157 match resolver.resolve(command.clone())? {
158 ResolveOutcome::Recorded(resolution) => Ok(HandoffOutcome::Resolved(resolution)),
159 ResolveOutcome::ResumeLive => {
160 execute_live_and_record(recorder, executor, command, recorded_at).await
161 }
162 }
163}
164
165async fn execute_live_and_record(
166 recorder: &mut Recorder,
167 executor: &dyn LiveExecutor,
168 command: Command,
169 recorded_at: DateTime<Utc>,
170) -> Result<HandoffOutcome, DurabilityError> {
171 match command {
172 Command::RunActivity {
173 key,
174 activity_type,
175 input,
176 } => {
177 let activity_id = activity_id_from_key(&key)?;
178 recorder
179 .record_activity_scheduled(
180 recorded_at,
181 activity_id.clone(),
182 activity_type.clone(),
183 input.clone(),
184 String::from(aion_core::DEFAULT_TASK_QUEUE),
187 None,
190 )
191 .await?;
192 recorder
193 .record_activity_started(recorded_at, activity_id.clone(), 1)
197 .await?;
198 let outcome = executor.run_activity(activity_type, input).await?;
199 match outcome {
200 LiveActivityOutcome::Completed(result) => {
201 recorder
202 .record_activity_completed(recorded_at, activity_id, result.clone(), 1)
203 .await?;
204 Ok(HandoffOutcome::Resolved(Resolution::ActivityCompleted(
205 result,
206 )))
207 }
208 LiveActivityOutcome::Failed(error) => {
209 ensure_terminal_activity_error(&error)?;
210 recorder
211 .record_activity_failed(recorded_at, activity_id, error.clone(), 1)
212 .await?;
213 Ok(HandoffOutcome::Resolved(
214 Resolution::ActivityFailedTerminal(error),
215 ))
216 }
217 }
218 }
219 Command::StartTimer { key, fire_at } => {
220 let timer_id = timer_id_from_key(&key)?;
221 recorder
222 .record_timer_started(recorded_at, timer_id.clone(), fire_at)
223 .await?;
224 executor.start_timer(timer_id, fire_at).await?;
225 Ok(HandoffOutcome::Resolved(Resolution::TimerFired))
226 }
227 Command::AwaitSignal { key } => {
228 let (name, index) = signal_from_key(&key)?;
229 let payload = executor.await_signal(name, index).await?;
230 Ok(HandoffOutcome::Resolved(Resolution::SignalDelivered(
231 payload,
232 )))
233 }
234 Command::SendSignal { .. } => Err(DurabilityError::HistoryShape {
235 reason: "send-signal live execution is owned by the NIF signal bridge".to_owned(),
236 }),
237 Command::AwaitChild { .. } => Err(DurabilityError::HistoryShape {
238 reason: "await-child live execution is owned by the NIF child bridge".to_owned(),
239 }),
240 Command::SpawnChild {
241 key,
242 workflow_type,
243 input,
244 } => {
245 child_from_key(&key)?;
246 let outcome = executor
247 .spawn_child(workflow_type.clone(), input.clone())
248 .await?;
249 recorder
250 .record_child_workflow_started(
251 recorded_at,
252 outcome.child_workflow_id(),
253 workflow_type,
254 input,
255 outcome.package_version(),
256 )
257 .await?;
258 match outcome {
259 LiveChildOutcome::Completed { result, .. } => {
260 Ok(HandoffOutcome::Resolved(Resolution::ChildCompleted(result)))
261 }
262 LiveChildOutcome::Failed { error, .. } => {
263 Ok(HandoffOutcome::Resolved(Resolution::ChildFailed(error)))
264 }
265 }
266 }
267 Command::CompleteWorkflow { result } => {
268 recorder
269 .record_workflow_completed(recorded_at, result)
270 .await?;
271 Ok(HandoffOutcome::WorkflowCompleted)
272 }
273 }
274}
275
276fn ensure_terminal_activity_error(error: &ActivityError) -> Result<(), DurabilityError> {
277 if error.is_retryable() {
278 return Err(DurabilityError::HistoryShape {
279 reason: "live activity failure must be terminal before AD can record a terminal \
280 resolution"
281 .to_owned(),
282 });
283 }
284 Ok(())
285}
286
287fn activity_id_from_key(key: &CorrelationKey) -> Result<ActivityId, DurabilityError> {
288 match key {
289 CorrelationKey::Activity(ordinal) => Ok(ActivityId::from_sequence_position(*ordinal)),
290 other => Err(DurabilityError::HistoryShape {
291 reason: format!("RunActivity requires an activity correlation key, got {other:?}"),
292 }),
293 }
294}
295
296fn timer_id_from_key(key: &CorrelationKey) -> Result<TimerId, DurabilityError> {
297 match key {
298 CorrelationKey::Timer(timer_id) => Ok(timer_id.clone()),
299 other => Err(DurabilityError::HistoryShape {
300 reason: format!("StartTimer requires a timer correlation key, got {other:?}"),
301 }),
302 }
303}
304
305fn signal_from_key(key: &CorrelationKey) -> Result<(String, usize), DurabilityError> {
306 match key {
307 CorrelationKey::Signal { name, index } => Ok((name.clone(), *index)),
308 other => Err(DurabilityError::HistoryShape {
309 reason: format!("AwaitSignal requires a signal correlation key, got {other:?}"),
310 }),
311 }
312}
313
314fn child_from_key(key: &CorrelationKey) -> Result<u64, DurabilityError> {
315 match key {
316 CorrelationKey::Child(ordinal) => Ok(*ordinal),
317 other => Err(DurabilityError::HistoryShape {
318 reason: format!("SpawnChild requires a child correlation key, got {other:?}"),
319 }),
320 }
321}
322
323#[cfg(test)]
324#[path = "executor_tests.rs"]
325mod executor_tests;