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 complete_activity(
144 &self,
145 id: i64,
146 result: Option<&str>,
147 error: Option<&str>,
148 failed: bool,
149 ) -> Result<()> {
150 self.store
151 .complete_activity(id, result, error, failed)
152 .await?;
153
154 let act = match self.store.get_activity(id).await? {
156 Some(a) => a,
157 None => return Ok(()),
158 };
159
160 let event_type = if failed {
161 "ActivityFailed"
162 } else {
163 "ActivityCompleted"
164 };
165 let payload = serde_json::json!({
166 "activity_id": id,
167 "activity_seq": act.seq,
168 "name": act.name,
169 "result": result.and_then(|s| serde_json::from_str::<serde_json::Value>(s).ok()),
170 "error": error,
171 });
172 let event_seq = self.store.get_event_count(&act.workflow_id).await? as i32 + 1;
173 let workflow_id = act.workflow_id.clone();
174 self.store
175 .append_event(&WorkflowEvent {
176 id: None,
177 workflow_id: act.workflow_id,
178 seq: event_seq,
179 event_type: event_type.to_string(),
180 payload: Some(payload.to_string()),
181 timestamp: timestamp_now(),
182 })
183 .await?;
184 self.mark_and_emit_needs_dispatch(&workflow_id).await?;
186 Ok(())
187 }
188
189 pub async fn fail_activity(&self, id: i64, error: &str) -> Result<()> {
199 let act = match self.store.get_activity(id).await? {
200 Some(a) => a,
201 None => return Ok(()),
202 };
203
204 if act.attempt < act.max_attempts {
205 let backoff = act.initial_interval_secs * 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(&self, workflow_id: &str, value: &str) -> Result<()> {
250 let now = timestamp_now();
251 let seq = self.store.get_event_count(workflow_id).await? as i32 + 1;
252 self.store
253 .append_event(&WorkflowEvent {
254 id: None,
255 workflow_id: workflow_id.to_string(),
256 seq,
257 event_type: "SideEffectRecorded".to_string(),
258 payload: Some(value.to_string()),
259 timestamp: now,
260 })
261 .await?;
262 Ok(())
263 }
264}