1use anyhow::Result;
4
5use crate::ctx::{WorkflowCtx, timestamp_now};
6use crate::events::WorkflowBusEvent;
7use crate::store::WorkflowStore;
8use crate::types::*;
9
10impl<S: WorkflowStore> WorkflowCtx<S> {
11 pub async fn schedule_activity(
25 &self,
26 workflow_id: &str,
27 seq: i32,
28 name: &str,
29 input: Option<&str>,
30 task_queue: &str,
31 opts: ScheduleActivityOpts,
32 ) -> Result<WorkflowActivity> {
33 if let Some(existing) = self
35 .store
36 .get_activity_by_workflow_seq(workflow_id, seq)
37 .await?
38 {
39 return Ok(existing);
40 }
41
42 let now = timestamp_now();
43 let mut act = WorkflowActivity {
44 id: None,
45 workflow_id: workflow_id.to_string(),
46 seq,
47 name: name.to_string(),
48 task_queue: task_queue.to_string(),
49 input: input.map(String::from),
50 status: "PENDING".to_string(),
51 result: None,
52 error: None,
53 attempt: 1,
54 max_attempts: opts.max_attempts.unwrap_or(3),
55 initial_interval_secs: opts.initial_interval_secs.unwrap_or(1.0),
56 backoff_coefficient: opts.backoff_coefficient.unwrap_or(2.0),
57 start_to_close_secs: opts.start_to_close_secs.unwrap_or(300.0),
58 heartbeat_timeout_secs: opts.heartbeat_timeout_secs,
59 claimed_by: None,
60 scheduled_at: now,
61 started_at: None,
62 completed_at: None,
63 last_heartbeat: None,
64 };
65
66 let id = self.store.create_activity(&act).await?;
67 act.id = Some(id);
68
69 let event_seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
71 self.store
72 .append_event(&WorkflowEvent {
73 id: None,
74 workflow_id: workflow_id.to_string(),
75 seq: event_seq,
76 event_type: "ActivityScheduled".to_string(),
77 payload: Some(
78 serde_json::json!({
79 "activity_id": id,
80 "activity_seq": seq,
81 "name": name,
82 "task_queue": task_queue,
83 "input": input,
84 })
85 .to_string(),
86 ),
87 timestamp: now,
88 })
89 .await?;
90
91 let ns = self
96 .store
97 .get_workflow(workflow_id)
98 .await?
99 .map(|w| w.namespace)
100 .unwrap_or_else(|| "main".to_string());
101 self.emit(
102 &ns,
103 WorkflowBusEvent::ActivityInserted {
104 activity_id: id,
105 workflow_id: workflow_id.to_string(),
106 task_queue: task_queue.to_string(),
107 name: name.to_string(),
108 },
109 )
110 .await;
111
112 if let Some(wf) = self.store.get_workflow(workflow_id).await?
114 && wf.status == "PENDING"
115 {
116 self.store
117 .update_workflow_status(workflow_id, WorkflowStatus::Running, None, None)
118 .await?;
119 }
120
121 Ok(act)
122 }
123
124 pub async fn claim_activity(
125 &self,
126 task_queue: &str,
127 worker_id: &str,
128 ) -> Result<Option<WorkflowActivity>> {
129 self.store.claim_activity(task_queue, worker_id).await
130 }
131
132 pub async fn get_activity(&self, id: i64) -> Result<Option<WorkflowActivity>> {
133 self.store.get_activity(id).await
134 }
135
136 pub async fn retry_failed_activity(
137 &self,
138 workflow_id: &str,
139 requested_by: &str,
140 reason: &str,
141 ) -> Result<RetryFailedActivityResult> {
142 let result = self
143 .store
144 .retry_failed_activity(workflow_id, requested_by, reason, timestamp_now())
145 .await?;
146 if let RetryFailedActivityResult::Retried(retried) = &result {
147 let activity = &retried.activity;
148 let namespace = self
149 .store
150 .get_workflow(workflow_id)
151 .await?
152 .map(|workflow| workflow.namespace)
153 .unwrap_or_else(|| "main".to_string());
154 self.emit_retry_requested(
155 &namespace,
156 workflow_id,
157 activity.id.unwrap_or_default(),
158 activity.seq,
159 )
160 .await;
161 }
162 Ok(result)
163 }
164
165 pub async fn complete_activity(
173 &self,
174 id: i64,
175 result: Option<&str>,
176 error: Option<&str>,
177 failed: bool,
178 ) -> Result<()> {
179 self.store
180 .complete_activity(id, result, error, failed)
181 .await?;
182
183 let act = match self.store.get_activity(id).await? {
185 Some(a) => a,
186 None => return Ok(()),
187 };
188
189 let event_type = if failed {
190 "ActivityFailed"
191 } else {
192 "ActivityCompleted"
193 };
194 let payload = serde_json::json!({
195 "activity_id": id,
196 "activity_seq": act.seq,
197 "name": act.name,
198 "result": result.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok()),
199 "error": error,
200 });
201 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
202 let workflow_id = act.workflow_id.clone();
203 self.store
204 .append_event(&WorkflowEvent {
205 id: None,
206 workflow_id: act.workflow_id,
207 seq: event_seq,
208 event_type: event_type.to_string(),
209 payload: Some(payload.to_string()),
210 timestamp: timestamp_now(),
211 })
212 .await?;
213 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
215 Ok(())
216 }
217
218 pub async fn fail_activity(&self, id: i64, error: &str) -> Result<()> {
228 let act = match self.store.get_activity(id).await? {
229 Some(a) => a,
230 None => return Ok(()),
231 };
232
233 if act.attempt < act.max_attempts {
234 let backoff = act.initial_interval_secs * act.backoff_coefficient.powi(act.attempt - 1);
236 let next_scheduled_at = timestamp_now() + backoff;
237 self.store
238 .requeue_activity_for_retry(id, act.attempt + 1, next_scheduled_at)
239 .await?;
240 return Ok(());
241 }
242
243 self.store
245 .complete_activity(id, None, Some(error), true)
246 .await?;
247
248 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
249 let workflow_id = act.workflow_id.clone();
250 self.store
251 .append_event(&WorkflowEvent {
252 id: None,
253 workflow_id: act.workflow_id,
254 seq: event_seq,
255 event_type: "ActivityFailed".to_string(),
256 payload: Some(
257 serde_json::json!({
258 "activity_id": id,
259 "activity_seq": act.seq,
260 "name": act.name,
261 "error": error,
262 "final_attempt": act.attempt,
263 })
264 .to_string(),
265 ),
266 timestamp: timestamp_now(),
267 })
268 .await?;
269 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
271 Ok(())
272 }
273
274 pub async fn heartbeat_activity(&self, id: i64, details: Option<&str>) -> Result<()> {
275 self.store.heartbeat_activity(id, details).await
276 }
277
278 pub async fn record_side_effect(&self, workflow_id: &str, value: &str) -> Result<()> {
279 let now = timestamp_now();
280 let seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
281 self.store
282 .append_event(&WorkflowEvent {
283 id: None,
284 workflow_id: workflow_id.to_string(),
285 seq,
286 event_type: "SideEffectRecorded".to_string(),
287 payload: Some(value.to_string()),
288 timestamp: now,
289 })
290 .await?;
291 Ok(())
292 }
293}