@temporalio/core-bridge 0.17.2 → 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 +45 -52
  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 +264 -286
  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 +34 -73
  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 +305 -195
  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 +19 -21
  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 +344 -160
  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 +297 -81
  85. package/sdk-core/{test_utils → core-api}/Cargo.toml +10 -7
  86. package/sdk-core/{src → core-api/src}/errors.rs +42 -90
  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 +594 -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 -158
@@ -0,0 +1,396 @@
1
+ use crate::{
2
+ worker::LocalActivityExecutionResult, workflow::LEGACY_QUERY_ID, CompleteActivityError,
3
+ TaskToken,
4
+ };
5
+ use anyhow::anyhow;
6
+ use std::{
7
+ collections::HashMap,
8
+ convert::TryFrom,
9
+ fmt::{Display, Formatter},
10
+ time::{Duration, SystemTime},
11
+ };
12
+ use temporal_sdk_core_protos::{
13
+ constants::{LOCAL_ACTIVITY_MARKER_NAME, PATCH_MARKER_NAME},
14
+ coresdk::{
15
+ activity_result::{activity_execution_result, activity_execution_result::Status},
16
+ common::{
17
+ decode_change_marker_details, extract_local_activity_marker_data,
18
+ extract_local_activity_marker_details, Payload as SDKPayload, RetryPolicy,
19
+ },
20
+ external_data::LocalActivityMarkerData,
21
+ workflow_activation::{
22
+ workflow_activation_job, QueryWorkflow, WorkflowActivation, WorkflowActivationJob,
23
+ },
24
+ workflow_commands::{
25
+ query_result, ActivityCancellationType, QueryResult, ScheduleLocalActivity,
26
+ },
27
+ workflow_completion, FromPayloadsExt,
28
+ },
29
+ temporal::api::{
30
+ common::v1::{Payload, WorkflowExecution},
31
+ enums::v1::EventType,
32
+ failure::v1::Failure,
33
+ history::v1::{history_event, History, HistoryEvent, MarkerRecordedEventAttributes},
34
+ query::v1::WorkflowQuery,
35
+ workflowservice::v1::PollWorkflowTaskQueueResponse,
36
+ },
37
+ utilities::TryIntoOrNone,
38
+ };
39
+
40
+ /// A validated version of a [PollWorkflowTaskQueueResponse]
41
+ #[derive(Debug, Clone, PartialEq)]
42
+ #[allow(clippy::manual_non_exhaustive)] // Clippy doesn't understand it's only for *in* this crate
43
+ pub struct ValidPollWFTQResponse {
44
+ pub task_token: TaskToken,
45
+ pub task_queue: String,
46
+ pub workflow_execution: WorkflowExecution,
47
+ pub workflow_type: String,
48
+ pub history: History,
49
+ pub next_page_token: Vec<u8>,
50
+ pub attempt: u32,
51
+ pub previous_started_event_id: i64,
52
+ pub started_event_id: i64,
53
+ /// If this is present, `history` will be empty. This is not a very "tight" design, but it's
54
+ /// enforced at construction time. From the `query` field.
55
+ pub legacy_query: Option<WorkflowQuery>,
56
+ /// Query requests from the `queries` field
57
+ pub query_requests: Vec<QueryWorkflow>,
58
+
59
+ /// Zero-size field to prevent explicit construction
60
+ _cant_construct_me: (),
61
+ }
62
+
63
+ impl TryFrom<PollWorkflowTaskQueueResponse> for ValidPollWFTQResponse {
64
+ /// We return the poll response itself if it was invalid
65
+ type Error = PollWorkflowTaskQueueResponse;
66
+
67
+ fn try_from(value: PollWorkflowTaskQueueResponse) -> Result<Self, Self::Error> {
68
+ match value {
69
+ PollWorkflowTaskQueueResponse {
70
+ task_token,
71
+ workflow_execution_task_queue: Some(tq),
72
+ workflow_execution: Some(workflow_execution),
73
+ workflow_type: Some(workflow_type),
74
+ history: Some(history),
75
+ next_page_token,
76
+ attempt,
77
+ previous_started_event_id,
78
+ started_event_id,
79
+ query,
80
+ queries,
81
+ ..
82
+ } => {
83
+ let query_requests = queries
84
+ .into_iter()
85
+ .map(|(id, q)| QueryWorkflow {
86
+ query_id: id,
87
+ query_type: q.query_type,
88
+ arguments: Vec::from_payloads(q.query_args),
89
+ })
90
+ .collect();
91
+
92
+ Ok(Self {
93
+ task_token: TaskToken(task_token),
94
+ task_queue: tq.name,
95
+ workflow_execution,
96
+ workflow_type: workflow_type.name,
97
+ history,
98
+ next_page_token,
99
+ attempt: attempt as u32,
100
+ previous_started_event_id,
101
+ started_event_id,
102
+ legacy_query: query,
103
+ query_requests,
104
+ _cant_construct_me: (),
105
+ })
106
+ }
107
+ _ => Err(value),
108
+ }
109
+ }
110
+ }
111
+
112
+ pub(crate) trait WorkflowActivationExt {
113
+ /// Returns true if this activation has one and only one job to perform a legacy query
114
+ fn is_legacy_query(&self) -> bool;
115
+ }
116
+
117
+ impl WorkflowActivationExt for WorkflowActivation {
118
+ fn is_legacy_query(&self) -> bool {
119
+ matches!(&self.jobs.as_slice(), &[WorkflowActivationJob {
120
+ variant: Some(workflow_activation_job::Variant::QueryWorkflow(qr))
121
+ }] if qr.query_id == LEGACY_QUERY_ID)
122
+ }
123
+ }
124
+
125
+ /// Create a legacy query failure result
126
+ pub(crate) fn legacy_query_failure(fail: workflow_completion::Failure) -> QueryResult {
127
+ QueryResult {
128
+ query_id: LEGACY_QUERY_ID.to_string(),
129
+ variant: Some(query_result::Variant::Failed(
130
+ fail.failure.unwrap_or_default(),
131
+ )),
132
+ }
133
+ }
134
+
135
+ pub(crate) trait HistoryEventExt {
136
+ /// If this history event represents a `patched` marker, return the info about
137
+ /// it. Returns `None` if it is any other kind of event or marker.
138
+ fn get_patch_marker_details(&self) -> Option<(String, bool)>;
139
+ /// If this history event represents a local activity marker, return true.
140
+ fn is_local_activity_marker(&self) -> bool;
141
+ /// If this history event represents a local activity marker, return the marker id info.
142
+ /// Returns `None` if it is any other kind of event or marker or the data is invalid.
143
+ fn extract_local_activity_marker_data(&self) -> Option<LocalActivityMarkerData>;
144
+ /// If this history event represents a local activity marker, return all the contained data.
145
+ /// Returns `None` if it is any other kind of event or marker or the data is invalid.
146
+ fn into_local_activity_marker_details(self) -> Option<CompleteLocalActivityData>;
147
+ }
148
+
149
+ impl HistoryEventExt for HistoryEvent {
150
+ fn get_patch_marker_details(&self) -> Option<(String, bool)> {
151
+ if self.event_type() == EventType::MarkerRecorded {
152
+ match &self.attributes {
153
+ Some(history_event::Attributes::MarkerRecordedEventAttributes(
154
+ MarkerRecordedEventAttributes {
155
+ marker_name,
156
+ details,
157
+ ..
158
+ },
159
+ )) if marker_name == PATCH_MARKER_NAME => decode_change_marker_details(details),
160
+ _ => None,
161
+ }
162
+ } else {
163
+ None
164
+ }
165
+ }
166
+
167
+ fn is_local_activity_marker(&self) -> bool {
168
+ if self.event_type() == EventType::MarkerRecorded {
169
+ return matches!(&self.attributes,
170
+ Some(history_event::Attributes::MarkerRecordedEventAttributes(
171
+ MarkerRecordedEventAttributes { marker_name, .. },
172
+ )) if marker_name == LOCAL_ACTIVITY_MARKER_NAME
173
+ );
174
+ }
175
+ false
176
+ }
177
+
178
+ fn extract_local_activity_marker_data(&self) -> Option<LocalActivityMarkerData> {
179
+ if self.event_type() == EventType::MarkerRecorded {
180
+ match &self.attributes {
181
+ Some(history_event::Attributes::MarkerRecordedEventAttributes(
182
+ MarkerRecordedEventAttributes {
183
+ marker_name,
184
+ details,
185
+ ..
186
+ },
187
+ )) if marker_name == LOCAL_ACTIVITY_MARKER_NAME => {
188
+ extract_local_activity_marker_data(details)
189
+ }
190
+ _ => None,
191
+ }
192
+ } else {
193
+ None
194
+ }
195
+ }
196
+
197
+ fn into_local_activity_marker_details(self) -> Option<CompleteLocalActivityData> {
198
+ if self.event_type() == EventType::MarkerRecorded {
199
+ match self.attributes {
200
+ Some(history_event::Attributes::MarkerRecordedEventAttributes(
201
+ MarkerRecordedEventAttributes {
202
+ marker_name,
203
+ mut details,
204
+ failure,
205
+ ..
206
+ },
207
+ )) if marker_name == LOCAL_ACTIVITY_MARKER_NAME => {
208
+ let (data, ok_res) = extract_local_activity_marker_details(&mut details);
209
+ let data = data?;
210
+ let result = if let Some(r) = ok_res {
211
+ Ok(r)
212
+ } else {
213
+ let fail = failure?;
214
+ Err(fail)
215
+ };
216
+ Some(CompleteLocalActivityData {
217
+ marker_dat: data,
218
+ result,
219
+ })
220
+ }
221
+ _ => None,
222
+ }
223
+ } else {
224
+ None
225
+ }
226
+ }
227
+ }
228
+
229
+ pub(crate) struct CompleteLocalActivityData {
230
+ pub marker_dat: LocalActivityMarkerData,
231
+ pub result: Result<Payload, Failure>,
232
+ }
233
+
234
+ impl TryFrom<activity_execution_result::Status> for LocalActivityExecutionResult {
235
+ type Error = CompleteActivityError;
236
+
237
+ fn try_from(s: activity_execution_result::Status) -> Result<Self, Self::Error> {
238
+ match s {
239
+ Status::Completed(c) => Ok(LocalActivityExecutionResult::Completed(c)),
240
+ Status::Failed(f)
241
+ if f.failure
242
+ .as_ref()
243
+ .map(|fail| fail.is_timeout())
244
+ .unwrap_or_default() =>
245
+ {
246
+ Ok(LocalActivityExecutionResult::TimedOut(f))
247
+ }
248
+ Status::Failed(f) => Ok(LocalActivityExecutionResult::Failed(f)),
249
+ Status::Cancelled(cancel) => Ok(LocalActivityExecutionResult::Cancelled(cancel)),
250
+ Status::WillCompleteAsync(_) => {
251
+ Err(CompleteActivityError::MalformedActivityCompletion {
252
+ reason: "Local activities cannot be completed async".to_string(),
253
+ completion: None,
254
+ })
255
+ }
256
+ }
257
+ }
258
+ }
259
+
260
+ /// Validated version of [ScheduleLocalActivity]. See it for field docs.
261
+ /// One or both of `schedule_to_close_timeout` and `start_to_close_timeout` are guaranteed to exist.
262
+ #[derive(Debug, Clone)]
263
+ #[cfg_attr(test, derive(Default))]
264
+ pub struct ValidScheduleLA {
265
+ pub seq: u32,
266
+ pub activity_id: String,
267
+ pub activity_type: String,
268
+ pub attempt: u32,
269
+ pub original_schedule_time: Option<SystemTime>,
270
+ pub header_fields: HashMap<String, SDKPayload>,
271
+ pub arguments: Vec<SDKPayload>,
272
+ pub schedule_to_start_timeout: Option<Duration>,
273
+ pub close_timeouts: LACloseTimeouts,
274
+ pub retry_policy: RetryPolicy,
275
+ pub local_retry_threshold: Duration,
276
+ pub cancellation_type: ActivityCancellationType,
277
+ }
278
+
279
+ #[derive(Debug, Clone, Copy)]
280
+ pub enum LACloseTimeouts {
281
+ ScheduleOnly(Duration),
282
+ StartOnly(Duration),
283
+ Both { sched: Duration, start: Duration },
284
+ }
285
+
286
+ impl LACloseTimeouts {
287
+ /// Splits into (schedule_to_close, start_to_close) options, one or both of which is guaranteed
288
+ /// to be populated
289
+ pub fn into_sched_and_start(self) -> (Option<Duration>, Option<Duration>) {
290
+ match self {
291
+ LACloseTimeouts::ScheduleOnly(x) => (Some(x), None),
292
+ LACloseTimeouts::StartOnly(x) => (None, Some(x)),
293
+ LACloseTimeouts::Both { sched, start } => (Some(sched), Some(start)),
294
+ }
295
+ }
296
+ }
297
+
298
+ #[cfg(test)]
299
+ impl Default for LACloseTimeouts {
300
+ fn default() -> Self {
301
+ LACloseTimeouts::ScheduleOnly(Duration::from_secs(100))
302
+ }
303
+ }
304
+
305
+ impl ValidScheduleLA {
306
+ pub fn from_schedule_la(
307
+ v: ScheduleLocalActivity,
308
+ wf_exe_timeout: Option<Duration>,
309
+ ) -> Result<Self, anyhow::Error> {
310
+ let original_schedule_time = v
311
+ .original_schedule_time
312
+ .map(|x| {
313
+ x.try_into()
314
+ .map_err(|_| anyhow!("Could not convert original_schedule_time"))
315
+ })
316
+ .transpose()?;
317
+ let sched_to_close = v
318
+ .schedule_to_close_timeout
319
+ .map(|x| {
320
+ x.try_into()
321
+ .map_err(|_| anyhow!("Could not convert schedule_to_close_timeout"))
322
+ })
323
+ .transpose()?
324
+ // Default to execution timeout if unset
325
+ .or(wf_exe_timeout);
326
+ let mut schedule_to_start_timeout = v
327
+ .schedule_to_start_timeout
328
+ .map(|x| {
329
+ x.try_into()
330
+ .map_err(|_| anyhow!("Could not convert schedule_to_start_timeout"))
331
+ })
332
+ .transpose()?;
333
+ // Clamp schedule-to-start if larger than schedule-to-close
334
+ if let Some((sched_to_start, sched_to_close)) =
335
+ schedule_to_start_timeout.as_mut().zip(sched_to_close)
336
+ {
337
+ if *sched_to_start > sched_to_close {
338
+ *sched_to_start = sched_to_close;
339
+ }
340
+ }
341
+ let close_timeouts = match (
342
+ sched_to_close,
343
+ v.start_to_close_timeout
344
+ .map(|x| {
345
+ x.try_into()
346
+ .map_err(|_| anyhow!("Could not convert start_to_close_timeout"))
347
+ })
348
+ .transpose()?,
349
+ ) {
350
+ (Some(sch), None) => LACloseTimeouts::ScheduleOnly(sch),
351
+ (None, Some(start)) => LACloseTimeouts::StartOnly(start),
352
+ (Some(sched), Some(mut start)) => {
353
+ // Clamp start-to-close if larger than schedule-to-close
354
+ if start > sched {
355
+ start = sched;
356
+ }
357
+ LACloseTimeouts::Both { sched, start }
358
+ }
359
+ (None, None) => {
360
+ return Err(anyhow!(
361
+ "One of schedule_to_close or start_to_close timeouts must be set"
362
+ ))
363
+ }
364
+ };
365
+ let retry_policy = v
366
+ .retry_policy
367
+ .ok_or(anyhow!("Retry policy must be defined!"))?;
368
+ let local_retry_threshold = v
369
+ .local_retry_threshold
370
+ .clone()
371
+ .try_into_or_none()
372
+ .unwrap_or_else(|| Duration::from_secs(60));
373
+ let cancellation_type = ActivityCancellationType::from_i32(v.cancellation_type)
374
+ .unwrap_or(ActivityCancellationType::WaitCancellationCompleted);
375
+ Ok(ValidScheduleLA {
376
+ seq: v.seq,
377
+ activity_id: v.activity_id,
378
+ activity_type: v.activity_type,
379
+ attempt: v.attempt,
380
+ original_schedule_time,
381
+ header_fields: v.header_fields,
382
+ arguments: v.arguments,
383
+ schedule_to_start_timeout,
384
+ close_timeouts,
385
+ retry_policy,
386
+ local_retry_threshold,
387
+ cancellation_type,
388
+ })
389
+ }
390
+ }
391
+
392
+ impl Display for ValidScheduleLA {
393
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
394
+ write!(f, "ValidScheduleLA({}, {})", self.seq, self.activity_type)
395
+ }
396
+ }
@@ -0,0 +1,210 @@
1
+ //! This module implements support for creating special core instances and workers which can be used
2
+ //! to replay canned histories. It should be used by Lang SDKs to provide replay capabilities to
3
+ //! users during testing.
4
+
5
+ use crate::{init_mock_gateway, CoreInitOptionsBuilder, CoreSDK, TelemetryOptions};
6
+ use futures::FutureExt;
7
+ use std::{
8
+ sync::{
9
+ atomic::{AtomicBool, Ordering},
10
+ Arc,
11
+ },
12
+ time::Duration,
13
+ };
14
+ use temporal_client::{
15
+ mocks::{mock_gateway, mock_manual_gateway},
16
+ ServerGatewayApis,
17
+ };
18
+ use temporal_sdk_core_api::{
19
+ errors::{
20
+ CompleteActivityError, CompleteWfError, PollActivityError, PollWfError,
21
+ WorkerRegistrationError,
22
+ },
23
+ worker::WorkerConfig,
24
+ Core, CoreLog,
25
+ };
26
+ use temporal_sdk_core_protos::{
27
+ coresdk::{
28
+ activity_task::ActivityTask, workflow_activation::WorkflowActivation,
29
+ workflow_completion::WorkflowActivationCompletion, ActivityHeartbeat,
30
+ ActivityTaskCompletion,
31
+ },
32
+ temporal::api::{
33
+ common::v1::WorkflowExecution,
34
+ history::v1::History,
35
+ workflowservice::v1::{
36
+ RespondWorkflowTaskCompletedResponse, RespondWorkflowTaskFailedResponse,
37
+ StartWorkflowExecutionResponse,
38
+ },
39
+ },
40
+ };
41
+
42
+ pub use temporal_sdk_core_protos::{
43
+ default_wes_attribs, HistoryInfo, TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
44
+ };
45
+
46
+ /// Create a core instance that can be used for replay. See the [ReplayCore] trait for adding
47
+ /// canned history.
48
+ pub fn init_core_replay(opts: TelemetryOptions) -> ReplayCoreImpl {
49
+ let shared_mock_gateway = mock_gateway();
50
+ let init_opts = CoreInitOptionsBuilder::default()
51
+ .gateway_opts(shared_mock_gateway.get_options().clone())
52
+ .telemetry_opts(opts)
53
+ .build()
54
+ .expect("replay core options init properly");
55
+ let replay_core =
56
+ init_mock_gateway(init_opts, shared_mock_gateway).expect("init replay core works");
57
+ ReplayCoreImpl { inner: replay_core }
58
+ }
59
+
60
+ /// An extension of the [Core] trait to be used for testing workflows against canned histories.
61
+ pub trait ReplayCore: Core {
62
+ /// Make a fake worker which will use the provided history to simulate poll responses containing
63
+ /// that entire history. The configuration should have a unique task queue name.
64
+ ///
65
+ /// Note that some of the worker config options do not apply, and some will be overridden.
66
+ /// Replay workers always are cached, have only 1 poller, and do not poll for activities.
67
+ fn make_replay_worker(
68
+ &self,
69
+ config: WorkerConfig,
70
+ history: &History,
71
+ ) -> Result<(), anyhow::Error>;
72
+ }
73
+
74
+ /// Implements [ReplayCore] functionality
75
+ pub struct ReplayCoreImpl {
76
+ pub(crate) inner: CoreSDK,
77
+ }
78
+
79
+ impl ReplayCore for ReplayCoreImpl {
80
+ fn make_replay_worker(
81
+ &self,
82
+ mut config: WorkerConfig,
83
+ history: &History,
84
+ ) -> Result<(), anyhow::Error> {
85
+ let mock_g = mock_gateway_from_history(history, config.task_queue.clone());
86
+ config.max_cached_workflows = 1;
87
+ config.max_concurrent_wft_polls = 1;
88
+ config.no_remote_activities = true;
89
+ self.inner
90
+ .register_replay_worker(config, Arc::new(mock_g), history)
91
+ }
92
+ }
93
+
94
+ #[async_trait::async_trait]
95
+ impl Core for ReplayCoreImpl {
96
+ fn register_worker(&self, _config: WorkerConfig) -> Result<(), WorkerRegistrationError> {
97
+ panic!("Do not use `register_worker` with a replay core instance")
98
+ }
99
+
100
+ async fn poll_workflow_activation(
101
+ &self,
102
+ task_queue: &str,
103
+ ) -> Result<WorkflowActivation, PollWfError> {
104
+ self.inner.poll_workflow_activation(task_queue).await
105
+ }
106
+
107
+ async fn poll_activity_task(
108
+ &self,
109
+ task_queue: &str,
110
+ ) -> Result<ActivityTask, PollActivityError> {
111
+ self.inner.poll_activity_task(task_queue).await
112
+ }
113
+
114
+ async fn complete_workflow_activation(
115
+ &self,
116
+ completion: WorkflowActivationCompletion,
117
+ ) -> Result<(), CompleteWfError> {
118
+ self.inner.complete_workflow_activation(completion).await
119
+ }
120
+
121
+ async fn complete_activity_task(
122
+ &self,
123
+ completion: ActivityTaskCompletion,
124
+ ) -> Result<(), CompleteActivityError> {
125
+ self.inner.complete_activity_task(completion).await
126
+ }
127
+
128
+ fn record_activity_heartbeat(&self, _details: ActivityHeartbeat) {
129
+ // do nothing
130
+ }
131
+
132
+ fn request_workflow_eviction(&self, task_queue: &str, run_id: &str) {
133
+ self.inner.request_workflow_eviction(task_queue, run_id)
134
+ }
135
+
136
+ fn server_gateway(&self) -> Arc<dyn ServerGatewayApis + Send + Sync> {
137
+ self.inner.server_gateway()
138
+ }
139
+
140
+ async fn shutdown(&self) {
141
+ self.inner.shutdown().await
142
+ }
143
+
144
+ async fn shutdown_worker(&self, task_queue: &str) {
145
+ self.inner.shutdown_worker(task_queue).await
146
+ }
147
+
148
+ fn fetch_buffered_logs(&self) -> Vec<CoreLog> {
149
+ self.inner.fetch_buffered_logs()
150
+ }
151
+ }
152
+
153
+ /// Create a mock gateway which can be used by a replay worker to serve up canned history.
154
+ /// It will return the entire history in one workflow task, after that it will return default
155
+ /// responses (with a 10s wait). If a workflow task failure is sent to the mock, it will send
156
+ /// the complete response again.
157
+ pub fn mock_gateway_from_history(
158
+ history: &History,
159
+ task_queue: impl Into<String>,
160
+ ) -> impl ServerGatewayApis {
161
+ let mut mg = mock_manual_gateway();
162
+
163
+ let hist_info = HistoryInfo::new_from_history(history, None).unwrap();
164
+ let wf = WorkflowExecution {
165
+ workflow_id: "fake_wf_id".to_string(),
166
+ run_id: hist_info.orig_run_id().to_string(),
167
+ };
168
+
169
+ let wf_clone = wf.clone();
170
+ mg.expect_start_workflow().returning(move |_, _, _, _, _| {
171
+ let wf_clone = wf_clone.clone();
172
+ async move {
173
+ Ok(StartWorkflowExecutionResponse {
174
+ run_id: wf_clone.run_id.clone(),
175
+ })
176
+ }
177
+ .boxed()
178
+ });
179
+
180
+ let did_send = Arc::new(AtomicBool::new(false));
181
+ let did_send_clone = did_send.clone();
182
+ let tq = task_queue.into();
183
+ mg.expect_poll_workflow_task().returning(move |_, _| {
184
+ let hist_info = hist_info.clone();
185
+ let wf = wf.clone();
186
+ let did_send_clone = did_send_clone.clone();
187
+ let tq = tq.clone();
188
+ async move {
189
+ if !did_send_clone.swap(true, Ordering::AcqRel) {
190
+ let mut resp = hist_info.as_poll_wft_response(tq);
191
+ resp.workflow_execution = Some(wf.clone());
192
+ Ok(resp)
193
+ } else {
194
+ tokio::time::sleep(Duration::from_secs(10)).await;
195
+ Ok(Default::default())
196
+ }
197
+ }
198
+ .boxed()
199
+ });
200
+
201
+ mg.expect_complete_workflow_task()
202
+ .returning(|_| async move { Ok(RespondWorkflowTaskCompletedResponse::default()) }.boxed());
203
+ mg.expect_fail_workflow_task().returning(move |_, _, _| {
204
+ // We'll need to re-send the history if WFT fails
205
+ did_send.store(false, Ordering::Release);
206
+ async move { Ok(RespondWorkflowTaskFailedResponse {}) }.boxed()
207
+ });
208
+
209
+ mg
210
+ }