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?;
185 Ok(())
186 }
187
188 pub async fn fail_activity(&self, id: i64, error: &str) -> Result<()> {
198 let act = match self.store.get_activity(id).await? {
199 Some(a) => a,
200 None => return Ok(()),
201 };
202
203 if act.attempt < act.max_attempts {
204 let backoff = act.initial_interval_secs
206 * act.backoff_coefficient.powi(act.attempt - 1);
207 let next_scheduled_at = timestamp_now() + backoff;
208 self.store
209 .requeue_activity_for_retry(id, act.attempt + 1, next_scheduled_at)
210 .await?;
211 return Ok(());
212 }
213
214 self.store
216 .complete_activity(id, None, Some(error), true)
217 .await?;
218
219 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
220 let workflow_id = act.workflow_id.clone();
221 self.store
222 .append_event(&WorkflowEvent {
223 id: None,
224 workflow_id: act.workflow_id,
225 seq: event_seq,
226 event_type: "ActivityFailed".to_string(),
227 payload: Some(
228 serde_json::json!({
229 "activity_id": id,
230 "activity_seq": act.seq,
231 "name": act.name,
232 "error": error,
233 "final_attempt": act.attempt,
234 })
235 .to_string(),
236 ),
237 timestamp: timestamp_now(),
238 })
239 .await?;
240 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
242 Ok(())
243 }
244
245 pub async fn heartbeat_activity(&self, id: i64, details: Option<&str>) -> Result<()> {
246 self.store.heartbeat_activity(id, details).await
247 }
248
249 pub async fn record_side_effect(
250 &self,
251 workflow_id: &str,
252 value: &str,
253 ) -> Result<()> {
254 let now = timestamp_now();
255 let seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
256 self.store
257 .append_event(&WorkflowEvent {
258 id: None,
259 workflow_id: workflow_id.to_string(),
260 seq,
261 event_type: "SideEffectRecorded".to_string(),
262 payload: Some(value.to_string()),
263 timestamp: now,
264 })
265 .await?;
266 Ok(())
267 }
268}