1use std::sync::Arc;
4use std::sync::atomic::AtomicU64;
5
6use aion_core::{Payload, RunId, WorkflowError, WorkflowId, WorkflowStatus};
7use aion_package::ContentHash;
8use tokio::sync::{Mutex, watch};
9
10use crate::durability::Recorder;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
14pub enum Residency {
15 Resident,
17 Suspended,
19}
20
21pub type HandleResidency = Residency;
23
24#[derive(Clone, Debug, PartialEq, Eq)]
26pub enum TerminalOutcome {
27 Completed(Payload),
29 Failed(WorkflowError),
31 Cancelled(String),
33 TimedOut(String),
35 ContinuedAsNew {
37 input: Payload,
39 workflow_type: Option<String>,
41 parent_run_id: RunId,
43 },
44}
45
46#[derive(Clone, Debug)]
48pub struct CompletionNotifier {
49 sender: watch::Sender<Option<TerminalOutcome>>,
50}
51
52impl CompletionNotifier {
53 #[must_use]
55 pub fn new() -> Self {
56 let (sender, _receiver) = watch::channel(None);
57 Self { sender }
58 }
59
60 #[must_use]
62 pub fn subscribe(&self) -> watch::Receiver<Option<TerminalOutcome>> {
63 self.sender.subscribe()
64 }
65
66 pub fn notify(&self, outcome: TerminalOutcome) {
72 drop(self.sender.send_replace(Some(outcome)));
73 }
74
75 #[must_use]
77 pub fn is_completed(&self) -> bool {
78 self.sender.borrow().is_some()
79 }
80}
81
82impl Default for CompletionNotifier {
83 fn default() -> Self {
84 Self::new()
85 }
86}
87
88impl PartialEq for CompletionNotifier {
89 fn eq(&self, other: &Self) -> bool {
90 self.sender.same_channel(&other.sender)
91 }
92}
93
94impl Eq for CompletionNotifier {}
95
96pub struct WorkflowHandleParts {
98 pub workflow_id: WorkflowId,
100 pub run_id: RunId,
102 pub pid: u64,
104 pub workflow_type: String,
106 pub namespace: String,
108 pub loaded_version: ContentHash,
110 pub cached_status: WorkflowStatus,
112 pub residency: Residency,
114 pub recorder: Recorder,
116 pub completion: CompletionNotifier,
118}
119
120#[derive(Clone)]
127pub struct WorkflowHandle {
128 workflow_id: WorkflowId,
129 run_id: RunId,
130 pid: u64,
131 workflow_type: String,
132 namespace: String,
133 loaded_version: ContentHash,
134 cached_status: WorkflowStatus,
135 residency: Residency,
136 recorder: Arc<Mutex<Recorder>>,
137 completion: CompletionNotifier,
138 deterministic_nif_sequence: Arc<AtomicU64>,
139 activity_ordinal_sequence: Arc<AtomicU64>,
140 timer_ordinal_sequence: Arc<AtomicU64>,
141 child_ordinal_sequence: Arc<AtomicU64>,
142 signal_receive_counts: Arc<dashmap::DashMap<String, u64>>,
143 signal_send_counts: Arc<dashmap::DashMap<String, u64>>,
144}
145
146impl WorkflowHandle {
147 #[must_use]
149 pub fn new(parts: WorkflowHandleParts) -> Self {
150 Self {
151 workflow_id: parts.workflow_id,
152 run_id: parts.run_id,
153 pid: parts.pid,
154 workflow_type: parts.workflow_type,
155 namespace: parts.namespace,
156 loaded_version: parts.loaded_version,
157 cached_status: parts.cached_status,
158 residency: parts.residency,
159 recorder: Arc::new(Mutex::new(parts.recorder)),
160 completion: parts.completion,
161 deterministic_nif_sequence: Arc::new(AtomicU64::new(0)),
162 activity_ordinal_sequence: Arc::new(AtomicU64::new(0)),
163 timer_ordinal_sequence: Arc::new(AtomicU64::new(0)),
164 child_ordinal_sequence: Arc::new(AtomicU64::new(0)),
165 signal_receive_counts: Arc::new(dashmap::DashMap::new()),
166 signal_send_counts: Arc::new(dashmap::DashMap::new()),
167 }
168 }
169
170 #[must_use]
178 pub fn allocate_activity_ordinals(&self, count: u64) -> u64 {
179 self.activity_ordinal_sequence
180 .fetch_add(count, std::sync::atomic::Ordering::SeqCst)
181 }
182
183 #[must_use]
193 pub fn allocate_child_ordinals(&self, count: u64) -> u64 {
194 self.child_ordinal_sequence
195 .fetch_add(count, std::sync::atomic::Ordering::SeqCst)
196 }
197
198 #[must_use]
206 pub fn allocate_timer_ordinals(&self, count: u64) -> u64 {
207 self.timer_ordinal_sequence
208 .fetch_add(count, std::sync::atomic::Ordering::SeqCst)
209 }
210
211 #[must_use]
217 pub fn activity_ordinals_allocated(&self) -> u64 {
218 self.activity_ordinal_sequence
219 .load(std::sync::atomic::Ordering::SeqCst)
220 }
221
222 #[must_use]
227 pub fn timer_ordinals_allocated(&self) -> u64 {
228 self.timer_ordinal_sequence
229 .load(std::sync::atomic::Ordering::SeqCst)
230 }
231
232 #[must_use]
237 pub fn child_ordinals_allocated(&self) -> u64 {
238 self.child_ordinal_sequence
239 .load(std::sync::atomic::Ordering::SeqCst)
240 }
241
242 #[must_use]
250 pub fn signal_receives_consumed(&self, name: &str) -> u64 {
251 self.signal_receive_counts
252 .get(name)
253 .map_or(0, |entry| *entry)
254 }
255
256 pub fn mark_signal_receive_consumed(&self, name: &str) {
258 *self
259 .signal_receive_counts
260 .entry(name.to_owned())
261 .or_insert(0) += 1;
262 }
263
264 #[must_use]
272 pub fn signal_sends_completed(&self, name: &str) -> u64 {
273 self.signal_send_counts.get(name).map_or(0, |entry| *entry)
274 }
275
276 pub fn mark_signal_send_completed(&self, name: &str) {
278 *self.signal_send_counts.entry(name.to_owned()).or_insert(0) += 1;
279 }
280
281 #[must_use]
283 pub const fn workflow_id(&self) -> &WorkflowId {
284 &self.workflow_id
285 }
286
287 #[must_use]
289 pub const fn run_id(&self) -> &RunId {
290 &self.run_id
291 }
292
293 #[must_use]
295 pub const fn pid(&self) -> u64 {
296 self.pid
297 }
298
299 #[must_use]
301 pub fn workflow_type(&self) -> &str {
302 &self.workflow_type
303 }
304
305 #[must_use]
307 pub fn namespace(&self) -> &str {
308 &self.namespace
309 }
310
311 #[must_use]
313 pub const fn loaded_version(&self) -> &ContentHash {
314 &self.loaded_version
315 }
316
317 #[must_use]
319 pub const fn cached_status(&self) -> WorkflowStatus {
320 self.cached_status
321 }
322
323 #[must_use]
325 pub const fn residency(&self) -> Residency {
326 self.residency
327 }
328
329 #[must_use]
331 pub fn recorder(&self) -> Arc<Mutex<Recorder>> {
332 Arc::clone(&self.recorder)
333 }
334
335 #[must_use]
337 pub const fn completion(&self) -> &CompletionNotifier {
338 &self.completion
339 }
340
341 #[must_use]
343 pub fn next_deterministic_nif_sequence(&self) -> u64 {
344 self.deterministic_nif_sequence
345 .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
346 }
347
348 pub(in crate::registry) const fn replace_projected_status(&mut self, status: WorkflowStatus) {
350 self.cached_status = status;
351 }
352
353 pub(in crate::registry) const fn replace_residency(&mut self, residency: Residency) {
355 self.residency = residency;
356 }
357}
358
359impl std::fmt::Debug for WorkflowHandle {
360 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361 formatter
362 .debug_struct("WorkflowHandle")
363 .field("workflow_id", &self.workflow_id)
364 .field("run_id", &self.run_id)
365 .field("pid", &self.pid)
366 .field("workflow_type", &self.workflow_type)
367 .field("namespace", &self.namespace)
368 .field("loaded_version", &self.loaded_version)
369 .field("cached_status", &self.cached_status)
370 .field("residency", &self.residency)
371 .field("completion", &self.completion)
372 .finish_non_exhaustive()
373 }
374}
375
376impl PartialEq for WorkflowHandle {
377 fn eq(&self, other: &Self) -> bool {
378 self.workflow_id == other.workflow_id
379 && self.run_id == other.run_id
380 && self.pid == other.pid
381 && self.workflow_type == other.workflow_type
382 && self.namespace == other.namespace
383 && self.loaded_version == other.loaded_version
384 && self.cached_status == other.cached_status
385 && self.residency == other.residency
386 && Arc::ptr_eq(&self.recorder, &other.recorder)
387 && self.completion == other.completion
388 && Arc::ptr_eq(
389 &self.deterministic_nif_sequence,
390 &other.deterministic_nif_sequence,
391 )
392 }
393}
394
395impl Eq for WorkflowHandle {}
396
397#[cfg(test)]
398mod tests {
399 use serde_json::json;
400
401 use super::{CompletionNotifier, TerminalOutcome};
402
403 fn payload(label: &str) -> Result<aion_core::Payload, aion_core::PayloadError> {
404 aion_core::Payload::from_json(&json!({ "label": label }))
405 }
406
407 #[test]
408 fn completion_notifier_stores_outcome_without_active_receiver()
409 -> Result<(), aion_core::PayloadError> {
410 let notifier = CompletionNotifier::new();
411 let receiver = notifier.subscribe();
412 drop(receiver);
413 let result = payload("completed")?;
414
415 notifier.notify(TerminalOutcome::Completed(result.clone()));
416 let late_receiver = notifier.subscribe();
417
418 assert_eq!(
419 late_receiver.borrow().clone(),
420 Some(TerminalOutcome::Completed(result))
421 );
422 assert!(notifier.is_completed());
423 Ok(())
424 }
425}