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(
179 &self,
180 id: i64,
181 result: Option<&str>,
182 error: Option<&str>,
183 failed: bool,
184 ) -> Result<()> {
185 let act = match self.store.get_activity(id).await? {
188 Some(a) => a,
189 None => return Ok(()),
190 };
191
192 let event_type = if failed {
193 "ActivityFailed"
194 } else {
195 "ActivityCompleted"
196 };
197 let payload = settled_event_payload(id, act.seq, &act.name, result, error).to_string();
198 let outcome = self
199 .store
200 .settle_activity(&ActivitySettlement {
201 activity_id: id,
202 workflow_id: &act.workflow_id,
203 result,
204 error,
205 failed,
206 event_type,
207 payload: &payload,
208 now: timestamp_now(),
209 })
210 .await?;
211 if outcome == SettleOutcome::Repaired {
212 tracing::warn!(
213 activity_id = id,
214 workflow_id = %act.workflow_id,
215 "settled activity was missing its history event — repaired"
216 );
217 }
218 self.emit_needs_dispatch(&act.workflow_id).await;
220 Ok(())
221 }
222
223 pub async fn fail_activity(&self, id: i64, error: &str) -> Result<()> {
233 let act = match self.store.get_activity(id).await? {
234 Some(a) => a,
235 None => return Ok(()),
236 };
237
238 if act.attempt < act.max_attempts {
239 let backoff = act.initial_interval_secs * act.backoff_coefficient.powi(act.attempt - 1);
241 let next_scheduled_at = timestamp_now() + backoff;
242 self.store
243 .requeue_activity_for_retry(id, act.attempt + 1, next_scheduled_at)
244 .await?;
245 return Ok(());
246 }
247
248 let payload = failed_event_payload(id, act.seq, &act.name, error, act.attempt).to_string();
251 self.store
252 .settle_activity(&ActivitySettlement {
253 activity_id: id,
254 workflow_id: &act.workflow_id,
255 result: None,
256 error: Some(error),
257 failed: true,
258 event_type: "ActivityFailed",
259 payload: &payload,
260 now: timestamp_now(),
261 })
262 .await?;
263 self.emit_needs_dispatch(&act.workflow_id).await;
265 Ok(())
266 }
267
268 pub async fn heartbeat_activity(&self, id: i64, details: Option<&str>) -> Result<()> {
269 self.store.heartbeat_activity(id, details).await
270 }
271
272 pub async fn record_side_effect(&self, workflow_id: &str, value: &str) -> Result<()> {
273 let now = timestamp_now();
274 let seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
275 self.store
276 .append_event(&WorkflowEvent {
277 id: None,
278 workflow_id: workflow_id.to_string(),
279 seq,
280 event_type: "SideEffectRecorded".to_string(),
281 payload: Some(value.to_string()),
282 timestamp: now,
283 })
284 .await?;
285 Ok(())
286 }
287}
288
289pub(crate) fn settled_event_payload(
293 activity_id: i64,
294 activity_seq: i32,
295 name: &str,
296 result: Option<&str>,
297 error: Option<&str>,
298) -> serde_json::Value {
299 serde_json::json!({
300 "activity_id": activity_id,
301 "activity_seq": activity_seq,
302 "name": name,
303 "result": result.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok()),
304 "error": error,
305 })
306}
307
308pub(crate) fn failed_event_payload(
312 activity_id: i64,
313 activity_seq: i32,
314 name: &str,
315 error: &str,
316 final_attempt: i32,
317) -> serde_json::Value {
318 serde_json::json!({
319 "activity_id": activity_id,
320 "activity_seq": activity_seq,
321 "name": name,
322 "error": error,
323 "final_attempt": final_attempt,
324 })
325}