1use async_trait::async_trait;
2use concepts::ExecutionFailureKind;
3use concepts::ExecutionId;
4use concepts::ExecutionMetadata;
5use concepts::FunctionMetadata;
6use concepts::JoinSetId;
7use concepts::TrapKind;
8use concepts::component_id::ComponentDigest;
9use concepts::storage::DbErrorWrite;
10use concepts::storage::HistoryEvent;
11use concepts::storage::Locked;
12use concepts::storage::ResponseWithCursor;
13use concepts::storage::Version;
14use concepts::storage::http_client_trace::HttpClientTrace;
15use concepts::{FinishedExecutionFailure, StrVariant};
16use concepts::{FunctionFqn, ParamsParsingError, ResultParsingError};
17use concepts::{Params, SupportedFunctionReturnValue};
18use tracing::Span;
19
20#[async_trait]
21pub trait Worker: Send + Sync + 'static {
22 async fn run(&self, ctx: WorkerContext) -> WorkerResult;
23
24 fn exported_functions_noext(&self) -> &[FunctionMetadata];
27}
28
29pub type WorkerResult = Result<WorkerResultOk, WorkerError>;
30
31#[derive(Debug, derive_more::Display)]
32pub enum WorkerResultOk {
33 #[display("db updated, not finished")]
34 DbUpdatedByWorkerOrWatcher,
35 #[display("{_0}")]
37 RunFinished(RunFinished),
38}
39
40#[derive(Debug, Clone, Copy, derive_more::Display)]
41pub enum ExecutionYieldReason {
42 #[display("executor closing")]
43 ExecutorClosing,
44 #[display("workflow event limit reached")]
45 WorkflowEventLimitReached,
46}
47
48#[derive(Debug, derive_more::Display)]
49#[display("finished at v{version}")]
50pub struct RunFinished {
51 pub retval: SupportedFunctionReturnValue,
52 pub version: Version,
53 pub http_client_traces: Option<Vec<HttpClientTrace>>,
54}
55
56#[derive(Debug)]
57pub struct WorkerContext {
58 pub execution_id: ExecutionId,
59 pub metadata: ExecutionMetadata,
60 pub component_digest: ComponentDigest,
61 pub ffqn: FunctionFqn,
62 pub params: Params,
63 pub event_history: Vec<(HistoryEvent, Version)>,
64 pub responses: Vec<ResponseWithCursor>,
65 pub parent: Option<(ExecutionId, JoinSetId)>,
66 pub version: Version,
67 pub can_be_retried: bool,
68 pub worker_span: Span,
69 pub locked_event: Locked,
70 pub execution_interrupt_watcher: tokio::sync::watch::Receiver<bool>,
71}
72
73#[derive(Debug, thiserror::Error)]
74pub enum WorkerError {
75 #[error("activity {trap_kind}: {reason}")]
78 ActivityTrap {
79 reason: String,
80 trap_kind: TrapKind,
81 detail: Option<String>,
82 version: Version,
83 http_client_traces: Option<Vec<HttpClientTrace>>,
84 },
85 #[error("limit reached: {reason}")]
89 LimitReached { reason: String, version: Version },
90 #[error("temporary timeout")]
92 TemporaryTimeout {
93 http_client_traces: Option<Vec<HttpClientTrace>>,
94 version: Version,
95 },
96 #[error("execution yielded: {reason}")]
97 ExecutionYielded {
98 version: Version,
99 reason: ExecutionYieldReason,
100 },
101 #[error(transparent)]
102 DbError(DbErrorWrite),
103 #[error("fatal error: {0}")]
105 FatalError(FatalError, Version),
106}
107
108#[derive(Debug, thiserror::Error)]
109pub enum FatalError {
110 #[error("nondeterminism detected")]
112 NondeterminismDetected { detail: String },
113 #[error(transparent)]
115 ParamsParsingError(ParamsParsingError),
116 #[error("{reason}")]
118 CannotInstantiate {
119 reason: String,
120 detail: Option<String>,
121 },
122 #[error(transparent)]
124 ResultParsingError(ResultParsingError),
125 #[error("error calling imported function {ffqn} : {reason}")]
127 ImportedFunctionCallError {
128 ffqn: FunctionFqn,
129 reason: StrVariant,
130 detail: Option<String>,
131 },
132 #[error("workflow {trap_kind}: {reason}")]
134 WorkflowTrap {
135 reason: String,
136 trap_kind: TrapKind,
137 detail: Option<String>,
138 },
139 #[error("out of fuel: {reason}")]
140 OutOfFuel { reason: String },
141 #[error("constraint violation: {reason}")]
142 ConstraintViolation { reason: StrVariant },
143 #[error("cancelled")]
145 Cancelled,
146}
147
148impl From<FatalError> for FinishedExecutionFailure {
149 fn from(err: FatalError) -> Self {
150 let reason_generic = err.to_string(); match err {
152 FatalError::NondeterminismDetected { detail } => FinishedExecutionFailure {
153 reason: None,
154 kind: ExecutionFailureKind::NondeterminismDetected,
155 detail: Some(detail),
156 },
157 FatalError::OutOfFuel { reason } => FinishedExecutionFailure {
158 reason: Some(reason),
159 kind: ExecutionFailureKind::OutOfFuel,
160 detail: None,
161 },
162 FatalError::ParamsParsingError(err) => FinishedExecutionFailure {
163 reason: Some(reason_generic),
164 kind: ExecutionFailureKind::Uncategorized,
165 detail: err.detail(),
166 },
167 FatalError::CannotInstantiate { reason, detail } => FinishedExecutionFailure {
168 reason: Some(reason),
169 kind: ExecutionFailureKind::Uncategorized,
170 detail,
171 },
172 FatalError::ResultParsingError(_) | FatalError::ConstraintViolation { reason: _ } => {
173 FinishedExecutionFailure {
174 reason: Some(reason_generic),
175 kind: ExecutionFailureKind::Uncategorized,
176 detail: None,
177 }
178 }
179 FatalError::ImportedFunctionCallError { detail, .. }
180 | FatalError::WorkflowTrap { detail, .. } => FinishedExecutionFailure {
181 reason: Some(reason_generic),
182 kind: ExecutionFailureKind::Uncategorized,
183 detail,
184 },
185 FatalError::Cancelled => FinishedExecutionFailure {
186 kind: ExecutionFailureKind::Cancelled,
187 reason: None,
188 detail: None,
189 },
190 }
191 }
192}
193
194impl From<&FatalError> for FinishedExecutionFailure {
195 fn from(err: &FatalError) -> Self {
196 let reason_generic = err.to_string(); match err {
198 FatalError::NondeterminismDetected { detail } => FinishedExecutionFailure {
199 reason: None,
200 kind: ExecutionFailureKind::NondeterminismDetected,
201 detail: Some(detail.clone()),
202 },
203 FatalError::OutOfFuel { reason } => FinishedExecutionFailure {
204 reason: Some(reason.clone()),
205 kind: ExecutionFailureKind::OutOfFuel,
206 detail: None,
207 },
208 FatalError::ParamsParsingError(err) => FinishedExecutionFailure {
209 reason: Some(reason_generic),
210 kind: ExecutionFailureKind::Uncategorized,
211 detail: err.detail(),
212 },
213 FatalError::CannotInstantiate { reason, detail } => FinishedExecutionFailure {
214 reason: Some(reason.clone()),
215 kind: ExecutionFailureKind::Uncategorized,
216 detail: detail.clone(),
217 },
218 FatalError::ResultParsingError(_) | FatalError::ConstraintViolation { reason: _ } => {
219 FinishedExecutionFailure {
220 reason: Some(reason_generic),
221 kind: ExecutionFailureKind::Uncategorized,
222 detail: None,
223 }
224 }
225 FatalError::ImportedFunctionCallError { detail, .. }
226 | FatalError::WorkflowTrap { detail, .. } => FinishedExecutionFailure {
227 reason: Some(reason_generic),
228 kind: ExecutionFailureKind::Uncategorized,
229 detail: detail.clone(),
230 },
231 FatalError::Cancelled => FinishedExecutionFailure {
232 kind: ExecutionFailureKind::Cancelled,
233 reason: None,
234 detail: None,
235 },
236 }
237 }
238}