@temporalio/core-bridge 0.16.4 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/Cargo.lock +339 -226
  2. package/Cargo.toml +7 -3
  3. package/common.js +50 -0
  4. package/index.d.ts +7 -0
  5. package/index.js +12 -0
  6. package/package.json +7 -4
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/{index.node → releases/index.node} +0 -0
  10. package/releases/x86_64-apple-darwin/index.node +0 -0
  11. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  12. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  13. package/scripts/build.js +10 -50
  14. package/sdk-core/.buildkite/docker/Dockerfile +1 -1
  15. package/sdk-core/.buildkite/docker/docker-compose.yaml +2 -2
  16. package/sdk-core/.buildkite/pipeline.yml +2 -0
  17. package/sdk-core/Cargo.toml +1 -88
  18. package/sdk-core/README.md +30 -6
  19. package/sdk-core/bridge-ffi/Cargo.toml +24 -0
  20. package/sdk-core/bridge-ffi/LICENSE.txt +23 -0
  21. package/sdk-core/bridge-ffi/build.rs +25 -0
  22. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +216 -0
  23. package/sdk-core/bridge-ffi/src/lib.rs +829 -0
  24. package/sdk-core/bridge-ffi/src/wrappers.rs +193 -0
  25. package/sdk-core/client/Cargo.toml +32 -0
  26. package/sdk-core/{src/pollers/gateway.rs → client/src/lib.rs} +101 -195
  27. package/sdk-core/client/src/metrics.rs +89 -0
  28. package/sdk-core/client/src/mocks.rs +167 -0
  29. package/sdk-core/{src/pollers → client/src}/retry.rs +172 -14
  30. package/sdk-core/core/Cargo.toml +96 -0
  31. package/sdk-core/{src → core/src}/core_tests/activity_tasks.rs +193 -37
  32. package/sdk-core/{src → core/src}/core_tests/child_workflows.rs +14 -14
  33. package/sdk-core/{src → core/src}/core_tests/determinism.rs +8 -8
  34. package/sdk-core/core/src/core_tests/local_activities.rs +328 -0
  35. package/sdk-core/{src → core/src}/core_tests/mod.rs +6 -9
  36. package/sdk-core/{src → core/src}/core_tests/queries.rs +54 -54
  37. package/sdk-core/{src → core/src}/core_tests/replay_flag.rs +8 -12
  38. package/sdk-core/{src → core/src}/core_tests/workers.rs +120 -33
  39. package/sdk-core/{src → core/src}/core_tests/workflow_cancels.rs +16 -26
  40. package/sdk-core/{src → core/src}/core_tests/workflow_tasks.rs +280 -292
  41. package/sdk-core/core/src/lib.rs +374 -0
  42. package/sdk-core/{src → core/src}/log_export.rs +3 -27
  43. package/sdk-core/core/src/pending_activations.rs +162 -0
  44. package/sdk-core/{src → core/src}/pollers/mod.rs +4 -22
  45. package/sdk-core/{src → core/src}/pollers/poll_buffer.rs +1 -1
  46. package/sdk-core/core/src/protosext/mod.rs +396 -0
  47. package/sdk-core/core/src/replay/mod.rs +210 -0
  48. package/sdk-core/core/src/retry_logic.rs +144 -0
  49. package/sdk-core/{src → core/src}/telemetry/metrics.rs +3 -58
  50. package/sdk-core/{src → core/src}/telemetry/mod.rs +8 -8
  51. package/sdk-core/{src → core/src}/telemetry/prometheus_server.rs +0 -0
  52. package/sdk-core/{src → core/src}/test_help/mod.rs +35 -83
  53. package/sdk-core/{src → core/src}/worker/activities/activity_heartbeat_manager.rs +95 -42
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +973 -0
  55. package/sdk-core/{src → core/src}/worker/activities.rs +52 -33
  56. package/sdk-core/{src → core/src}/worker/dispatcher.rs +8 -6
  57. package/sdk-core/{src → core/src}/worker/mod.rs +347 -221
  58. package/sdk-core/core/src/worker/wft_delivery.rs +81 -0
  59. package/sdk-core/{src → core/src}/workflow/bridge.rs +5 -2
  60. package/sdk-core/{src → core/src}/workflow/driven_workflow.rs +17 -7
  61. package/sdk-core/{src → core/src}/workflow/history_update.rs +33 -7
  62. package/sdk-core/{src → core/src/workflow}/machines/activity_state_machine.rs +26 -26
  63. package/sdk-core/{src → core/src/workflow}/machines/cancel_external_state_machine.rs +8 -11
  64. package/sdk-core/{src → core/src/workflow}/machines/cancel_workflow_state_machine.rs +19 -21
  65. package/sdk-core/{src → core/src/workflow}/machines/child_workflow_state_machine.rs +20 -31
  66. package/sdk-core/{src → core/src/workflow}/machines/complete_workflow_state_machine.rs +3 -5
  67. package/sdk-core/{src → core/src/workflow}/machines/continue_as_new_workflow_state_machine.rs +18 -18
  68. package/sdk-core/{src → core/src/workflow}/machines/fail_workflow_state_machine.rs +5 -6
  69. package/sdk-core/core/src/workflow/machines/local_activity_state_machine.rs +1451 -0
  70. package/sdk-core/{src → core/src/workflow}/machines/mod.rs +54 -107
  71. package/sdk-core/{src → core/src/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
  72. package/sdk-core/{src → core/src/workflow}/machines/patch_state_machine.rs +29 -30
  73. package/sdk-core/{src → core/src/workflow}/machines/side_effect_state_machine.rs +0 -0
  74. package/sdk-core/{src → core/src/workflow}/machines/signal_external_state_machine.rs +17 -19
  75. package/sdk-core/{src → core/src/workflow}/machines/timer_state_machine.rs +20 -21
  76. package/sdk-core/{src → core/src/workflow}/machines/transition_coverage.rs +5 -2
  77. package/sdk-core/{src → core/src/workflow}/machines/upsert_search_attributes_state_machine.rs +0 -0
  78. package/sdk-core/core/src/workflow/machines/workflow_machines/local_acts.rs +96 -0
  79. package/sdk-core/{src → core/src/workflow}/machines/workflow_machines.rs +357 -171
  80. package/sdk-core/{src → core/src/workflow}/machines/workflow_task_state_machine.rs +1 -1
  81. package/sdk-core/{src → core/src}/workflow/mod.rs +200 -39
  82. package/sdk-core/{src → core/src}/workflow/workflow_tasks/cache_manager.rs +0 -0
  83. package/sdk-core/{src → core/src}/workflow/workflow_tasks/concurrency_manager.rs +38 -5
  84. package/sdk-core/{src → core/src}/workflow/workflow_tasks/mod.rs +317 -103
  85. package/sdk-core/{test_utils → core-api}/Cargo.toml +10 -7
  86. package/sdk-core/{src → core-api/src}/errors.rs +42 -92
  87. package/sdk-core/core-api/src/lib.rs +158 -0
  88. package/sdk-core/{src/worker/config.rs → core-api/src/worker.rs} +18 -23
  89. package/sdk-core/etc/deps.svg +156 -0
  90. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +5 -5
  91. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +3 -5
  92. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +7 -1
  93. package/sdk-core/histories/fail_wf_task.bin +0 -0
  94. package/sdk-core/histories/timer_workflow_history.bin +0 -0
  95. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +44 -13
  96. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +19 -1
  97. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +1 -1
  98. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +9 -0
  99. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +1 -0
  100. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +1 -0
  101. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +13 -0
  102. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +14 -7
  103. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +176 -18
  104. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -0
  105. package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +11 -0
  106. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +3 -0
  107. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +156 -7
  108. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +135 -104
  109. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +78 -0
  110. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +78 -0
  111. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +205 -0
  112. package/sdk-core/protos/local/temporal/sdk/core/bridge/service.proto +61 -0
  113. package/sdk-core/protos/local/{child_workflow.proto → temporal/sdk/core/child_workflow/child_workflow.proto} +1 -1
  114. package/sdk-core/protos/local/{common.proto → temporal/sdk/core/common/common.proto} +5 -3
  115. package/sdk-core/protos/local/{core_interface.proto → temporal/sdk/core/core_interface.proto} +10 -10
  116. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +30 -0
  117. package/sdk-core/protos/local/{workflow_activation.proto → temporal/sdk/core/workflow_activation/workflow_activation.proto} +35 -11
  118. package/sdk-core/protos/local/{workflow_commands.proto → temporal/sdk/core/workflow_commands/workflow_commands.proto} +55 -4
  119. package/sdk-core/protos/local/{workflow_completion.proto → temporal/sdk/core/workflow_completion/workflow_completion.proto} +3 -3
  120. package/sdk-core/sdk/Cargo.toml +32 -0
  121. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/conversions.rs +0 -0
  122. package/sdk-core/sdk/src/lib.rs +699 -0
  123. package/sdk-core/sdk/src/payload_converter.rs +11 -0
  124. package/sdk-core/sdk/src/workflow_context/options.rs +180 -0
  125. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/workflow_context.rs +201 -124
  126. package/sdk-core/{src/prototype_rust_sdk → sdk/src}/workflow_future.rs +63 -30
  127. package/sdk-core/sdk-core-protos/Cargo.toml +10 -0
  128. package/sdk-core/sdk-core-protos/build.rs +28 -6
  129. package/sdk-core/sdk-core-protos/src/constants.rs +7 -0
  130. package/sdk-core/{src/test_help → sdk-core-protos/src}/history_builder.rs +134 -49
  131. package/sdk-core/sdk-core-protos/src/history_info.rs +216 -0
  132. package/sdk-core/sdk-core-protos/src/lib.rs +601 -168
  133. package/sdk-core/sdk-core-protos/src/task_token.rs +38 -0
  134. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -0
  135. package/sdk-core/test-utils/Cargo.toml +32 -0
  136. package/sdk-core/{src/test_help → test-utils/src}/canned_histories.rs +59 -78
  137. package/sdk-core/test-utils/src/histfetch.rs +28 -0
  138. package/sdk-core/{test_utils → test-utils}/src/lib.rs +131 -68
  139. package/sdk-core/tests/integ_tests/client_tests.rs +1 -1
  140. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -7
  141. package/sdk-core/tests/integ_tests/polling_tests.rs +12 -11
  142. package/sdk-core/tests/integ_tests/queries_tests.rs +82 -78
  143. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +91 -71
  144. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +3 -4
  145. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +2 -4
  146. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +4 -6
  147. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +4 -6
  148. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -4
  149. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +496 -0
  150. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +5 -8
  151. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +125 -0
  152. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +7 -13
  153. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +33 -5
  154. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +12 -16
  155. package/sdk-core/tests/integ_tests/workflow_tests.rs +85 -82
  156. package/sdk-core/tests/load_tests.rs +6 -6
  157. package/sdk-core/tests/main.rs +2 -2
  158. package/src/conversions.rs +24 -21
  159. package/src/errors.rs +8 -0
  160. package/src/lib.rs +323 -211
  161. package/sdk-core/protos/local/activity_result.proto +0 -46
  162. package/sdk-core/protos/local/activity_task.proto +0 -66
  163. package/sdk-core/src/core_tests/retry.rs +0 -147
  164. package/sdk-core/src/lib.rs +0 -403
  165. package/sdk-core/src/machines/local_activity_state_machine.rs +0 -117
  166. package/sdk-core/src/pending_activations.rs +0 -249
  167. package/sdk-core/src/protosext/mod.rs +0 -160
  168. package/sdk-core/src/prototype_rust_sdk.rs +0 -412
  169. package/sdk-core/src/task_token.rs +0 -20
  170. package/sdk-core/src/test_help/history_info.rs +0 -157
@@ -0,0 +1,699 @@
1
+ #![warn(missing_docs)] // error if there are missing docs
2
+
3
+ //! This crate is a rough prototype Rust SDK. It can be used to create closures that look sort of
4
+ //! like normal workflow code. It should only depend on things in the core crate that are already
5
+ //! publicly exposed.
6
+ //!
7
+ //! Needs lots of love to be production ready but the basis is there
8
+
9
+ #[macro_use]
10
+ extern crate tracing;
11
+
12
+ mod conversions;
13
+ mod payload_converter;
14
+ mod workflow_context;
15
+ mod workflow_future;
16
+
17
+ pub use workflow_context::{
18
+ ActivityOptions, CancellableFuture, ChildWorkflow, ChildWorkflowOptions, LocalActivityOptions,
19
+ WfContext,
20
+ };
21
+
22
+ use crate::workflow_context::{ChildWfCommon, PendingChildWorkflow};
23
+ use anyhow::{anyhow, bail};
24
+ use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt};
25
+ use std::{
26
+ collections::HashMap,
27
+ fmt::{Debug, Display, Formatter},
28
+ future::Future,
29
+ ops::{Deref, DerefMut},
30
+ sync::{
31
+ atomic::{AtomicUsize, Ordering},
32
+ Arc,
33
+ },
34
+ time::Duration,
35
+ };
36
+ use temporal_sdk_core_api::{
37
+ errors::{PollActivityError, PollWfError},
38
+ Core,
39
+ };
40
+ use temporal_sdk_core_protos::{
41
+ coresdk::{
42
+ activity_result::{ActivityExecutionResult, ActivityResolution},
43
+ activity_task::{activity_task, ActivityTask},
44
+ child_workflow::ChildWorkflowResult,
45
+ common::{NamespacedWorkflowExecution, Payload},
46
+ workflow_activation::{
47
+ resolve_child_workflow_execution_start::Status as ChildWorkflowStartStatus,
48
+ workflow_activation_job::Variant, WorkflowActivation, WorkflowActivationJob,
49
+ },
50
+ workflow_commands::{workflow_command, ContinueAsNewWorkflowExecution},
51
+ workflow_completion::WorkflowActivationCompletion,
52
+ ActivityTaskCompletion, AsJsonPayloadExt, FromJsonPayloadExt,
53
+ },
54
+ temporal::api::failure::v1::Failure,
55
+ TaskToken,
56
+ };
57
+ use tokio::{
58
+ sync::{
59
+ mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
60
+ oneshot, watch,
61
+ watch::Receiver,
62
+ },
63
+ task::JoinError,
64
+ };
65
+ use tokio_util::sync::CancellationToken;
66
+
67
+ /// A worker that can poll for and respond to workflow tasks by using [WorkflowFunction]s
68
+ pub struct TestRustWorker {
69
+ core: Arc<dyn Core>,
70
+ task_queue: String,
71
+ task_timeout: Option<Duration>,
72
+ workflow_half: WorkflowHalf,
73
+ activity_half: ActivityHalf,
74
+ }
75
+
76
+ struct WorkflowHalf {
77
+ /// Maps run id to the driver
78
+ workflows: HashMap<String, UnboundedSender<WorkflowActivation>>,
79
+ /// Maps workflow type to the function for executing workflow runs with that ID
80
+ workflow_fns: HashMap<String, WorkflowFunction>,
81
+ /// Number of live workflows
82
+ incomplete_workflows: Arc<AtomicUsize>,
83
+ /// Handles for each spawned workflow run are inserted here to be cleaned up when all runs
84
+ /// are finished
85
+ join_handles: FuturesUnordered<BoxFuture<'static, Result<WorkflowResult<()>, JoinError>>>,
86
+ }
87
+
88
+ struct ActivityHalf {
89
+ /// Maps activity type to the function for executing activities of that type
90
+ activity_fns: HashMap<String, ActivityFunction>,
91
+ task_tokens_to_cancels: HashMap<TaskToken, CancellationToken>,
92
+ }
93
+
94
+ impl TestRustWorker {
95
+ /// Create a new rust worker
96
+ pub fn new(core: Arc<dyn Core>, task_queue: String, task_timeout: Option<Duration>) -> Self {
97
+ Self {
98
+ core,
99
+ task_queue,
100
+ task_timeout,
101
+ workflow_half: WorkflowHalf {
102
+ workflows: Default::default(),
103
+ workflow_fns: Default::default(),
104
+ incomplete_workflows: Arc::new(AtomicUsize::new(0)),
105
+ join_handles: FuturesUnordered::new(),
106
+ },
107
+ activity_half: ActivityHalf {
108
+ activity_fns: Default::default(),
109
+ task_tokens_to_cancels: Default::default(),
110
+ },
111
+ }
112
+ }
113
+
114
+ /// Returns the task queue name this worker polls on
115
+ pub fn task_queue(&self) -> &str {
116
+ &self.task_queue
117
+ }
118
+
119
+ /// Create a workflow, asking the server to start it with the provided workflow ID and using the
120
+ /// provided workflow function.
121
+ ///
122
+ /// Increments the expected Workflow run count.
123
+ ///
124
+ /// Returns the run id of the started workflow
125
+ pub async fn submit_wf(
126
+ &self,
127
+ workflow_id: impl Into<String>,
128
+ workflow_type: impl Into<String>,
129
+ input: Vec<Payload>,
130
+ ) -> Result<String, tonic::Status> {
131
+ let res = self
132
+ .core
133
+ .server_gateway()
134
+ .start_workflow(
135
+ input,
136
+ self.task_queue.clone(),
137
+ workflow_id.into(),
138
+ workflow_type.into(),
139
+ self.task_timeout,
140
+ )
141
+ .await?;
142
+
143
+ self.incr_expected_run_count(1);
144
+ Ok(res.run_id)
145
+ }
146
+
147
+ /// Register a Workflow function to invoke when the Worker is asked to run a workflow of
148
+ /// `workflow_type`
149
+ pub fn register_wf<F: Into<WorkflowFunction>>(
150
+ &mut self,
151
+ workflow_type: impl Into<String>,
152
+ wf_function: F,
153
+ ) {
154
+ self.workflow_half
155
+ .workflow_fns
156
+ .insert(workflow_type.into(), wf_function.into());
157
+ }
158
+
159
+ /// Register an Activity function to invoke when the Worker is asked to run an activity of
160
+ /// `activity_type`
161
+ pub fn register_activity<A, R>(
162
+ &mut self,
163
+ activity_type: impl Into<String>,
164
+ act_function: impl IntoActivityFunc<A, R>,
165
+ ) {
166
+ self.activity_half.activity_fns.insert(
167
+ activity_type.into(),
168
+ ActivityFunction {
169
+ act_func: act_function.into_activity_fn(),
170
+ },
171
+ );
172
+ }
173
+
174
+ // TODO: Should be removed before making this worker prod ready. There can be a test worker
175
+ // which wraps this one and implements the workflow counting / run_until_done concepts.
176
+ // This worker can expose an interceptor for completions that could be used to assist with
177
+ // workflow tracking
178
+ /// Increment the expected Workflow run count on this Worker. The Worker tracks the run count
179
+ /// and will resolve `run_until_done` when it goes down to 0.
180
+ /// You do not have to increment if scheduled a Workflow with `submit_wf`.
181
+ pub fn incr_expected_run_count(&self, count: usize) {
182
+ self.workflow_half
183
+ .incomplete_workflows
184
+ .fetch_add(count, Ordering::SeqCst);
185
+ }
186
+
187
+ /// See [Self::run_until_done], except calls the provided callback just before performing core
188
+ /// shutdown.
189
+ pub async fn run_until_done_shutdown_hook(
190
+ &mut self,
191
+ before_shutdown: impl FnOnce(),
192
+ ) -> Result<(), anyhow::Error> {
193
+ let (shutdown_tx, shutdown_rx) = watch::channel(false);
194
+ let pollers = async move {
195
+ let (core, task_q, wf_half, act_half) = self.split_apart();
196
+ let (completions_tx, mut completions_rx) = unbounded_channel();
197
+ let (wf_poll_res, act_poll_res) = tokio::join!(
198
+ // Workflow polling loop
199
+ async {
200
+ loop {
201
+ let activation = match core.poll_workflow_activation(task_q).await {
202
+ Err(PollWfError::ShutDown) => {
203
+ break Result::<_, anyhow::Error>::Ok(());
204
+ }
205
+ o => o?,
206
+ };
207
+ wf_half
208
+ .workflow_activation_handler(
209
+ core.as_ref(),
210
+ task_q,
211
+ &shutdown_rx,
212
+ &completions_tx,
213
+ &mut completions_rx,
214
+ activation,
215
+ )
216
+ .await?;
217
+ if wf_half.incomplete_workflows.load(Ordering::SeqCst) == 0 {
218
+ // Die rebel scum - evict all workflows (which are complete now),
219
+ // and turn off activity polling.
220
+ let _ = shutdown_tx.send(true);
221
+ break Result::<_, anyhow::Error>::Ok(());
222
+ }
223
+ }
224
+ },
225
+ // Only poll on the activity queue if activity functions have been registered. This
226
+ // makes tests which use mocks dramatically more manageable.
227
+ async {
228
+ let mut shutdown_rx = shutdown_rx.clone();
229
+ if !act_half.activity_fns.is_empty() {
230
+ loop {
231
+ tokio::select! {
232
+ activity = core.poll_activity_task(task_q) => {
233
+ if matches!(activity, Err(PollActivityError::ShutDown)) {
234
+ break;
235
+ }
236
+ act_half.activity_task_handler(core.clone(), task_q,
237
+ activity?)?;
238
+ },
239
+ _ = shutdown_rx.changed() => { break }
240
+ }
241
+ }
242
+ };
243
+ Result::<_, anyhow::Error>::Ok(())
244
+ }
245
+ );
246
+ wf_poll_res?;
247
+ // TODO: Activity loop errors don't show up until wf loop exits/errors
248
+ act_poll_res?;
249
+ Result::<_, anyhow::Error>::Ok(self)
250
+ };
251
+
252
+ let myself = pollers.await?;
253
+ while let Some(h) = myself.workflow_half.join_handles.next().await {
254
+ h??;
255
+ }
256
+ before_shutdown();
257
+ myself.core.shutdown().await;
258
+ myself.workflow_half.workflows.clear();
259
+ Ok(())
260
+ }
261
+
262
+ /// Drives all workflows & activities until they have all finished, repeatedly polls server to
263
+ /// fetch work for them.
264
+ pub async fn run_until_done(&mut self) -> Result<(), anyhow::Error> {
265
+ self.run_until_done_shutdown_hook(|| {}).await
266
+ }
267
+
268
+ /// Temporarily swap the core implementation used by this worker. When the returned value is
269
+ /// dropped, the old core implementation will be restored. This can be used to run against
270
+ /// mocked-out core instances.
271
+ pub fn swap_core(&mut self, other_core: Arc<dyn Core>) -> impl DerefMut<Target = Self> + '_ {
272
+ let old_core = std::mem::replace(&mut self.core, other_core);
273
+ RustWorkerSwappedCore {
274
+ old_core: Some(old_core),
275
+ worker: self,
276
+ }
277
+ }
278
+
279
+ fn split_apart(&mut self) -> (Arc<dyn Core>, &str, &mut WorkflowHalf, &mut ActivityHalf) {
280
+ (
281
+ self.core.clone(),
282
+ &self.task_queue,
283
+ &mut self.workflow_half,
284
+ &mut self.activity_half,
285
+ )
286
+ }
287
+ }
288
+
289
+ impl WorkflowHalf {
290
+ async fn workflow_activation_handler(
291
+ &mut self,
292
+ core: &dyn Core,
293
+ task_queue: &str,
294
+ shutdown_rx: &Receiver<bool>,
295
+ completions_tx: &UnboundedSender<WorkflowActivationCompletion>,
296
+ completions_rx: &mut UnboundedReceiver<WorkflowActivationCompletion>,
297
+ activation: WorkflowActivation,
298
+ ) -> Result<(), anyhow::Error> {
299
+ // If the activation is to start a workflow, create a new workflow driver for it,
300
+ // using the function associated with that workflow id
301
+ if let Some(WorkflowActivationJob {
302
+ variant: Some(Variant::StartWorkflow(sw)),
303
+ }) = activation.jobs.get(0)
304
+ {
305
+ let wf_function = self
306
+ .workflow_fns
307
+ .get(&sw.workflow_type)
308
+ .ok_or_else(|| anyhow!("Workflow type not found"))?;
309
+
310
+ let (wff, activations) = wf_function.start_workflow(
311
+ core.server_gateway().get_options().namespace.clone(),
312
+ task_queue.to_string(),
313
+ // NOTE: Don't clone args if this gets ported to be a non-test rust worker
314
+ sw.arguments.clone(),
315
+ completions_tx.clone(),
316
+ );
317
+ let mut shutdown_rx = shutdown_rx.clone();
318
+ let jh = tokio::spawn(async move {
319
+ tokio::select! {
320
+ r = wff => r,
321
+ _ = shutdown_rx.changed() => Ok(WfExitValue::Evicted)
322
+ }
323
+ });
324
+ self.workflows
325
+ .insert(activation.run_id.clone(), activations);
326
+ self.join_handles.push(jh.boxed());
327
+ }
328
+
329
+ // The activation is expected to apply to some workflow we know about. Use it to
330
+ // unblock things and advance the workflow.
331
+ if let Some(tx) = self.workflows.get_mut(&activation.run_id) {
332
+ tx.send(activation)
333
+ .expect("Workflow should exist if we're sending it an activation");
334
+ } else {
335
+ bail!("Got activation for unknown workflow");
336
+ };
337
+
338
+ let completion = completions_rx.recv().await.expect("No workflows left?");
339
+ if completion.has_execution_ending() {
340
+ debug!("Workflow {} says it's finishing", &completion.run_id);
341
+ self.incomplete_workflows.fetch_sub(1, Ordering::SeqCst);
342
+ }
343
+ core.complete_workflow_activation(completion).await?;
344
+ Ok(())
345
+ }
346
+ }
347
+
348
+ tokio::task_local! {
349
+ // This works, but maybe just passing a context object for activities like WFs is better
350
+ static ACT_CANCEL_TOK: CancellationToken
351
+ }
352
+
353
+ /// Returns a future the completes if and when the activity this was called inside has been
354
+ /// cancelled
355
+ pub async fn act_cancelled() {
356
+ ACT_CANCEL_TOK.with(|ct| ct.clone()).cancelled().await
357
+ }
358
+
359
+ /// Returns true if this activity has already been cancelled
360
+ pub fn act_is_cancelled() -> bool {
361
+ ACT_CANCEL_TOK.with(|ct| ct.is_cancelled())
362
+ }
363
+
364
+ impl ActivityHalf {
365
+ /// Spawns off a task to handle the provided activity task
366
+ fn activity_task_handler(
367
+ &mut self,
368
+ core: Arc<dyn Core>,
369
+ task_queue: &str,
370
+ activity: ActivityTask,
371
+ ) -> Result<(), anyhow::Error> {
372
+ match activity.variant {
373
+ Some(activity_task::Variant::Start(start)) => {
374
+ let task_queue = task_queue.to_string();
375
+ let act_fn = self
376
+ .activity_fns
377
+ .get(&start.activity_type)
378
+ .ok_or_else(|| {
379
+ anyhow!(
380
+ "No function registered for activity type {}",
381
+ start.activity_type
382
+ )
383
+ })?
384
+ .clone();
385
+ let ct = CancellationToken::new();
386
+ self.task_tokens_to_cancels
387
+ .insert(activity.task_token.clone().into(), ct.clone());
388
+
389
+ tokio::spawn(ACT_CANCEL_TOK.scope(ct, async move {
390
+ let mut inputs = start.input;
391
+ let arg = inputs.pop().unwrap_or_default();
392
+ let output = (&act_fn.act_func)(arg).await;
393
+ let result = match output {
394
+ Ok(res) => ActivityExecutionResult::ok(res),
395
+ Err(err) => match err.downcast::<ActivityCancelledError>() {
396
+ Ok(ce) => ActivityExecutionResult::cancel_from_details(ce.details),
397
+ Err(other_err) => ActivityExecutionResult::fail(other_err.into()),
398
+ },
399
+ };
400
+ core.complete_activity_task(ActivityTaskCompletion {
401
+ task_token: activity.task_token,
402
+ task_queue,
403
+ result: Some(result),
404
+ })
405
+ .await?;
406
+ Result::<_, anyhow::Error>::Ok(())
407
+ }));
408
+ }
409
+ Some(activity_task::Variant::Cancel(_)) => {
410
+ if let Some(ct) = self.task_tokens_to_cancels.get(&activity.task_token.into()) {
411
+ ct.cancel();
412
+ }
413
+ }
414
+ None => bail!("Undefined activity task variant"),
415
+ }
416
+ Ok(())
417
+ }
418
+ }
419
+
420
+ #[derive(Debug)]
421
+ enum UnblockEvent {
422
+ Timer(u32, TimerResult),
423
+ Activity(u32, Box<ActivityResolution>),
424
+ WorkflowStart(u32, Box<ChildWorkflowStartStatus>),
425
+ WorkflowComplete(u32, Box<ChildWorkflowResult>),
426
+ SignalExternal(u32, Option<Failure>),
427
+ CancelExternal(u32, Option<Failure>),
428
+ }
429
+
430
+ /// Result of awaiting on a timer
431
+ #[derive(Debug, Copy, Clone)]
432
+ pub enum TimerResult {
433
+ /// The timer was cancelled
434
+ Cancelled,
435
+ /// The timer elapsed and fired
436
+ Fired,
437
+ }
438
+
439
+ /// Successful result of sending a signal to an external workflow
440
+ pub struct SignalExternalOk;
441
+ /// Result of awaiting on sending a signal to an external workflow
442
+ pub type SignalExternalWfResult = Result<SignalExternalOk, Failure>;
443
+
444
+ /// Successful result of sending a cancel request to an external workflow
445
+ pub struct CancelExternalOk;
446
+ /// Result of awaiting on sending a cancel request to an external workflow
447
+ pub type CancelExternalWfResult = Result<CancelExternalOk, Failure>;
448
+
449
+ trait Unblockable {
450
+ type OtherDat;
451
+
452
+ fn unblock(ue: UnblockEvent, od: Self::OtherDat) -> Self;
453
+ }
454
+
455
+ impl Unblockable for TimerResult {
456
+ type OtherDat = ();
457
+ fn unblock(ue: UnblockEvent, _: Self::OtherDat) -> Self {
458
+ match ue {
459
+ UnblockEvent::Timer(_, result) => result,
460
+ _ => panic!("Invalid unblock event for timer"),
461
+ }
462
+ }
463
+ }
464
+
465
+ impl Unblockable for ActivityResolution {
466
+ type OtherDat = ();
467
+ fn unblock(ue: UnblockEvent, _: Self::OtherDat) -> Self {
468
+ match ue {
469
+ UnblockEvent::Activity(_, result) => *result,
470
+ _ => panic!("Invalid unblock event for activity"),
471
+ }
472
+ }
473
+ }
474
+
475
+ impl Unblockable for PendingChildWorkflow {
476
+ // Other data here is workflow id
477
+ type OtherDat = ChildWfCommon;
478
+ fn unblock(ue: UnblockEvent, od: Self::OtherDat) -> Self {
479
+ match ue {
480
+ UnblockEvent::WorkflowStart(_, result) => Self {
481
+ status: *result,
482
+ common: od,
483
+ },
484
+ _ => panic!("Invalid unblock event for child workflow start"),
485
+ }
486
+ }
487
+ }
488
+
489
+ impl Unblockable for ChildWorkflowResult {
490
+ type OtherDat = ();
491
+ fn unblock(ue: UnblockEvent, _: Self::OtherDat) -> Self {
492
+ match ue {
493
+ UnblockEvent::WorkflowComplete(_, result) => *result,
494
+ _ => panic!("Invalid unblock event for child workflow complete"),
495
+ }
496
+ }
497
+ }
498
+
499
+ impl Unblockable for SignalExternalWfResult {
500
+ type OtherDat = ();
501
+ fn unblock(ue: UnblockEvent, _: Self::OtherDat) -> Self {
502
+ match ue {
503
+ UnblockEvent::SignalExternal(_, maybefail) => {
504
+ maybefail.map_or(Ok(SignalExternalOk), Err)
505
+ }
506
+ _ => panic!("Invalid unblock event for signal external workflow result"),
507
+ }
508
+ }
509
+ }
510
+
511
+ impl Unblockable for CancelExternalWfResult {
512
+ type OtherDat = ();
513
+ fn unblock(ue: UnblockEvent, _: Self::OtherDat) -> Self {
514
+ match ue {
515
+ UnblockEvent::CancelExternal(_, maybefail) => {
516
+ maybefail.map_or(Ok(CancelExternalOk), Err)
517
+ }
518
+ _ => panic!("Invalid unblock event for signal external workflow result"),
519
+ }
520
+ }
521
+ }
522
+
523
+ /// Identifier for cancellable operations
524
+ #[derive(Debug, Clone)]
525
+ pub enum CancellableID {
526
+ /// Timer sequence number
527
+ Timer(u32),
528
+ /// Activity sequence number
529
+ Activity(u32),
530
+ /// Activity sequence number
531
+ LocalActivity(u32),
532
+ /// Start child sequence number
533
+ ChildWorkflow(u32),
534
+ /// Signal workflow
535
+ SignalExternalWorkflow(u32),
536
+ /// An external workflow identifier as may have been created by a started child workflow
537
+ ExternalWorkflow {
538
+ /// Sequence number which will be used for the cancel command
539
+ seqnum: u32,
540
+ /// Identifying information about the workflow to be cancelled
541
+ execution: NamespacedWorkflowExecution,
542
+ /// Set to true if this workflow is a child of the issuing workflow
543
+ only_child: bool,
544
+ },
545
+ }
546
+
547
+ #[derive(derive_more::From)]
548
+ #[allow(clippy::large_enum_variant)]
549
+ enum RustWfCmd {
550
+ #[from(ignore)]
551
+ Cancel(CancellableID),
552
+ ForceWFTFailure(anyhow::Error),
553
+ NewCmd(CommandCreateRequest),
554
+ NewNonblockingCmd(workflow_command::Variant),
555
+ SubscribeChildWorkflowCompletion(CommandSubscribeChildWorkflowCompletion),
556
+ SubscribeSignal(String, UnboundedSender<Vec<Payload>>),
557
+ }
558
+
559
+ struct CommandCreateRequest {
560
+ cmd: workflow_command::Variant,
561
+ unblocker: oneshot::Sender<UnblockEvent>,
562
+ }
563
+
564
+ struct CommandSubscribeChildWorkflowCompletion {
565
+ seq: u32,
566
+ unblocker: oneshot::Sender<UnblockEvent>,
567
+ }
568
+
569
+ type WfFunc = dyn Fn(WfContext) -> BoxFuture<'static, WorkflowResult<()>> + Send + Sync + 'static;
570
+
571
+ /// The user's async function / workflow code
572
+ pub struct WorkflowFunction {
573
+ wf_func: Box<WfFunc>,
574
+ }
575
+
576
+ impl<F, Fut> From<F> for WorkflowFunction
577
+ where
578
+ F: Fn(WfContext) -> Fut + Send + Sync + 'static,
579
+ Fut: Future<Output = WorkflowResult<()>> + Send + 'static,
580
+ {
581
+ fn from(wf_func: F) -> Self {
582
+ Self::new(wf_func)
583
+ }
584
+ }
585
+
586
+ impl WorkflowFunction {
587
+ /// Build a workflow function from a closure or function pointer which accepts a [WfContext]
588
+ pub fn new<F, Fut>(wf_func: F) -> Self
589
+ where
590
+ F: Fn(WfContext) -> Fut + Send + Sync + 'static,
591
+ Fut: Future<Output = WorkflowResult<()>> + Send + 'static,
592
+ {
593
+ Self {
594
+ wf_func: Box::new(move |ctx: WfContext| wf_func(ctx).boxed()),
595
+ }
596
+ }
597
+ }
598
+
599
+ /// The result of running a workflow
600
+ pub type WorkflowResult<T> = Result<WfExitValue<T>, anyhow::Error>;
601
+
602
+ /// Workflow functions may return these values when exiting
603
+ #[derive(Debug, derive_more::From)]
604
+ pub enum WfExitValue<T: Debug> {
605
+ /// Continue the workflow as a new execution
606
+ #[from(ignore)]
607
+ ContinueAsNew(Box<ContinueAsNewWorkflowExecution>),
608
+ /// Confirm the workflow was cancelled (can be automatic in a more advanced iteration)
609
+ #[from(ignore)]
610
+ Cancelled,
611
+ /// The run was evicted
612
+ #[from(ignore)]
613
+ Evicted,
614
+ /// Finish with a result
615
+ Normal(T),
616
+ }
617
+
618
+ impl<T: Debug> WfExitValue<T> {
619
+ /// Construct a [WfExitValue::ContinueAsNew] variant (handles boxing)
620
+ pub fn continue_as_new(can: ContinueAsNewWorkflowExecution) -> Self {
621
+ Self::ContinueAsNew(Box::new(can))
622
+ }
623
+ }
624
+
625
+ type BoxActFn =
626
+ Arc<dyn Fn(Payload) -> BoxFuture<'static, Result<Payload, anyhow::Error>> + Send + Sync>;
627
+ /// Container for user-defined activity functions
628
+ #[derive(Clone)]
629
+ pub struct ActivityFunction {
630
+ act_func: BoxActFn,
631
+ }
632
+
633
+ /// Return this error to indicate your activity is cancelling
634
+ #[derive(Debug, Default)]
635
+ pub struct ActivityCancelledError {
636
+ details: Option<Payload>,
637
+ }
638
+ impl std::error::Error for ActivityCancelledError {}
639
+ impl Display for ActivityCancelledError {
640
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
641
+ write!(f, "Activity cancelled")
642
+ }
643
+ }
644
+
645
+ /// Closures / functions which can be turned into activity functions implement this trait
646
+ pub trait IntoActivityFunc<Args, Res> {
647
+ /// Consume the closure or fn pointer and turned it into a boxed activity function
648
+ fn into_activity_fn(self) -> BoxActFn;
649
+ }
650
+
651
+ impl<A, Rf, R, F> IntoActivityFunc<A, Rf> for F
652
+ where
653
+ F: (Fn(A) -> Rf) + Sync + Send + 'static,
654
+ A: FromJsonPayloadExt + Send,
655
+ Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
656
+ R: AsJsonPayloadExt,
657
+ {
658
+ fn into_activity_fn(self) -> BoxActFn {
659
+ let wrapper = move |input: Payload| {
660
+ // Some minor gymnastics are required to avoid needing to clone the function
661
+ match A::from_json_payload(&input) {
662
+ Ok(deser) => (self)(deser)
663
+ .map(|r| r.map(|r| r.as_json_payload())?)
664
+ .boxed(),
665
+ Err(e) => async move { Err(e.into()) }.boxed(),
666
+ }
667
+ };
668
+ Arc::new(wrapper)
669
+ }
670
+ }
671
+
672
+ /// Allows the worker to swap back to an original core instance upon Drop
673
+ struct RustWorkerSwappedCore<'a> {
674
+ old_core: Option<Arc<dyn Core>>,
675
+ worker: &'a mut TestRustWorker,
676
+ }
677
+
678
+ impl<'a> Deref for RustWorkerSwappedCore<'a> {
679
+ type Target = TestRustWorker;
680
+
681
+ fn deref(&self) -> &Self::Target {
682
+ self.worker
683
+ }
684
+ }
685
+
686
+ impl<'a> DerefMut for RustWorkerSwappedCore<'a> {
687
+ fn deref_mut(&mut self) -> &mut Self::Target {
688
+ self.worker
689
+ }
690
+ }
691
+
692
+ impl<'a> Drop for RustWorkerSwappedCore<'a> {
693
+ fn drop(&mut self) {
694
+ let _ = std::mem::replace(
695
+ &mut self.worker.core,
696
+ self.old_core.take().expect("Old core always exists"),
697
+ );
698
+ }
699
+ }