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 by worker or watcher")]
34 DbUpdatedByWorkerOrWatcher,
35 #[display("{_0}")]
37 RunFinished(RunFinished),
38}
39
40#[derive(Debug, derive_more::Display)]
41#[display("{retval}")]
42pub struct RunFinished {
43 pub retval: SupportedFunctionReturnValue,
44 pub version: Version,
45 pub http_client_traces: Option<Vec<HttpClientTrace>>,
46}
47
48#[derive(Debug)]
49pub struct WorkerContext {
50 pub execution_id: ExecutionId,
51 pub metadata: ExecutionMetadata,
52 pub component_digest: ComponentDigest,
53 pub ffqn: FunctionFqn,
54 pub params: Params,
55 pub event_history: Vec<(HistoryEvent, Version)>,
56 pub responses: Vec<ResponseWithCursor>,
57 pub parent: Option<(ExecutionId, JoinSetId)>,
58 pub version: Version,
59 pub can_be_retried: bool,
60 pub worker_span: Span,
61 pub locked_event: Locked,
62 pub executor_close_watcher: tokio::sync::watch::Receiver<bool>,
63}
64
65#[derive(Debug, thiserror::Error)]
66pub enum WorkerError {
67 #[error("activity {trap_kind}: {reason}")]
70 ActivityTrap {
71 reason: String,
72 trap_kind: TrapKind,
73 detail: Option<String>,
74 version: Version,
75 http_client_traces: Option<Vec<HttpClientTrace>>,
76 },
77 #[error("limit reached: {reason}")]
81 LimitReached { reason: String, version: Version },
82 #[error("temporary timeout")]
84 TemporaryTimeout {
85 http_client_traces: Option<Vec<HttpClientTrace>>,
86 version: Version,
87 },
88 #[error("executor closing")]
89 ExecutorClosing(Version),
90 #[error(transparent)]
91 DbError(DbErrorWrite),
92 #[error("fatal error: {0}")]
94 FatalError(FatalError, Version),
95}
96
97#[derive(Debug, thiserror::Error)]
98pub enum FatalError {
99 #[error("nondeterminism detected")]
101 NondeterminismDetected { detail: String },
102 #[error(transparent)]
104 ParamsParsingError(ParamsParsingError),
105 #[error("{reason}")]
107 CannotInstantiate {
108 reason: String,
109 detail: Option<String>,
110 },
111 #[error(transparent)]
113 ResultParsingError(ResultParsingError),
114 #[error("error calling imported function {ffqn} : {reason}")]
116 ImportedFunctionCallError {
117 ffqn: FunctionFqn,
118 reason: StrVariant,
119 detail: Option<String>,
120 },
121 #[error("workflow {trap_kind}: {reason}")]
123 WorkflowTrap {
124 reason: String,
125 trap_kind: TrapKind,
126 detail: Option<String>,
127 },
128 #[error("out of fuel: {reason}")]
129 OutOfFuel { reason: String },
130 #[error("constraint violation: {reason}")]
131 ConstraintViolation { reason: StrVariant },
132 #[error("cancelled")]
134 Cancelled,
135}
136
137impl From<FatalError> for FinishedExecutionFailure {
138 fn from(err: FatalError) -> Self {
139 let reason_generic = err.to_string(); match err {
141 FatalError::NondeterminismDetected { detail } => FinishedExecutionFailure {
142 reason: None,
143 kind: ExecutionFailureKind::NondeterminismDetected,
144 detail: Some(detail),
145 },
146 FatalError::OutOfFuel { reason } => FinishedExecutionFailure {
147 reason: Some(reason),
148 kind: ExecutionFailureKind::OutOfFuel,
149 detail: None,
150 },
151 FatalError::ParamsParsingError(err) => FinishedExecutionFailure {
152 reason: Some(reason_generic),
153 kind: ExecutionFailureKind::Uncategorized,
154 detail: err.detail(),
155 },
156 FatalError::CannotInstantiate { reason, detail } => FinishedExecutionFailure {
157 reason: Some(reason),
158 kind: ExecutionFailureKind::Uncategorized,
159 detail,
160 },
161 FatalError::ResultParsingError(_) | FatalError::ConstraintViolation { reason: _ } => {
162 FinishedExecutionFailure {
163 reason: Some(reason_generic),
164 kind: ExecutionFailureKind::Uncategorized,
165 detail: None,
166 }
167 }
168 FatalError::ImportedFunctionCallError { detail, .. }
169 | FatalError::WorkflowTrap { detail, .. } => FinishedExecutionFailure {
170 reason: Some(reason_generic),
171 kind: ExecutionFailureKind::Uncategorized,
172 detail,
173 },
174 FatalError::Cancelled => FinishedExecutionFailure {
175 kind: ExecutionFailureKind::Cancelled,
176 reason: None,
177 detail: None,
178 },
179 }
180 }
181}
182
183impl From<&FatalError> for FinishedExecutionFailure {
184 fn from(err: &FatalError) -> Self {
185 let reason_generic = err.to_string(); match err {
187 FatalError::NondeterminismDetected { detail } => FinishedExecutionFailure {
188 reason: None,
189 kind: ExecutionFailureKind::NondeterminismDetected,
190 detail: Some(detail.clone()),
191 },
192 FatalError::OutOfFuel { reason } => FinishedExecutionFailure {
193 reason: Some(reason.clone()),
194 kind: ExecutionFailureKind::OutOfFuel,
195 detail: None,
196 },
197 FatalError::ParamsParsingError(err) => FinishedExecutionFailure {
198 reason: Some(reason_generic),
199 kind: ExecutionFailureKind::Uncategorized,
200 detail: err.detail(),
201 },
202 FatalError::CannotInstantiate { reason, detail } => FinishedExecutionFailure {
203 reason: Some(reason.clone()),
204 kind: ExecutionFailureKind::Uncategorized,
205 detail: detail.clone(),
206 },
207 FatalError::ResultParsingError(_) | FatalError::ConstraintViolation { reason: _ } => {
208 FinishedExecutionFailure {
209 reason: Some(reason_generic),
210 kind: ExecutionFailureKind::Uncategorized,
211 detail: None,
212 }
213 }
214 FatalError::ImportedFunctionCallError { detail, .. }
215 | FatalError::WorkflowTrap { detail, .. } => FinishedExecutionFailure {
216 reason: Some(reason_generic),
217 kind: ExecutionFailureKind::Uncategorized,
218 detail: detail.clone(),
219 },
220 FatalError::Cancelled => FinishedExecutionFailure {
221 kind: ExecutionFailureKind::Cancelled,
222 reason: None,
223 detail: None,
224 },
225 }
226 }
227}