1use anyhow::Result;
4
5use crate::ctx::{timestamp_now, WorkflowCtx};
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 complete_activity(
144 &self,
145 id: i64,
146 result: Option<&str>,
147 error: Option<&str>,
148 failed: bool,
149 ) -> Result<()> {
150 self.store.complete_activity(id, result, error, failed).await?;
151
152 let act = match self.store.get_activity(id).await? {
154 Some(a) => a,
155 None => return Ok(()),
156 };
157
158 let event_type = if failed {
159 "ActivityFailed"
160 } else {
161 "ActivityCompleted"
162 };
163 let payload = serde_json::json!({
164 "activity_id": id,
165 "activity_seq": act.seq,
166 "name": act.name,
167 "result": result.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok()),
168 "error": error,
169 });
170 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
171 let workflow_id = act.workflow_id.clone();
172 self.store
173 .append_event(&WorkflowEvent {
174 id: None,
175 workflow_id: act.workflow_id,
176 seq: event_seq,
177 event_type: event_type.to_string(),
178 payload: Some(payload.to_string()),
179 timestamp: timestamp_now(),
180 })
181 .await?;
182 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
184 Ok(())
185 }
186
187 pub async fn fail_activity(&self, id: i64, error: &str) -> Result<()> {
197 let act = match self.store.get_activity(id).await? {
198 Some(a) => a,
199 None => return Ok(()),
200 };
201
202 if act.attempt < act.max_attempts {
203 let backoff = act.initial_interval_secs
205 * act.backoff_coefficient.powi(act.attempt - 1);
206 let next_scheduled_at = timestamp_now() + backoff;
207 self.store
208 .requeue_activity_for_retry(id, act.attempt + 1, next_scheduled_at)
209 .await?;
210 return Ok(());
211 }
212
213 self.store
215 .complete_activity(id, None, Some(error), true)
216 .await?;
217
218 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
219 let workflow_id = act.workflow_id.clone();
220 self.store
221 .append_event(&WorkflowEvent {
222 id: None,
223 workflow_id: act.workflow_id,
224 seq: event_seq,
225 event_type: "ActivityFailed".to_string(),
226 payload: Some(
227 serde_json::json!({
228 "activity_id": id,
229 "activity_seq": act.seq,
230 "name": act.name,
231 "error": error,
232 "final_attempt": act.attempt,
233 })
234 .to_string(),
235 ),
236 timestamp: timestamp_now(),
237 })
238 .await?;
239 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
241 Ok(())
242 }
243
244 pub async fn heartbeat_activity(&self, id: i64, details: Option<&str>) -> Result<()> {
245 self.store.heartbeat_activity(id, details).await
246 }
247
248 pub async fn record_side_effect(
249 &self,
250 workflow_id: &str,
251 value: &str,
252 ) -> Result<()> {
253 let now = timestamp_now();
254 let seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
255 self.store
256 .append_event(&WorkflowEvent {
257 id: None,
258 workflow_id: workflow_id.to_string(),
259 seq,
260 event_type: "SideEffectRecorded".to_string(),
261 payload: Some(value.to_string()),
262 timestamp: now,
263 })
264 .await?;
265 Ok(())
266 }
267}