@temporalio/core-bridge 0.23.0 → 1.0.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 (135) hide show
  1. package/Cargo.lock +118 -15
  2. package/Cargo.toml +2 -1
  3. package/LICENSE.md +1 -1
  4. package/README.md +1 -1
  5. package/index.d.ts +47 -18
  6. package/package.json +7 -7
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/releases/x86_64-apple-darwin/index.node +0 -0
  10. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  11. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  12. package/sdk-core/.buildkite/docker/docker-compose.yaml +4 -2
  13. package/sdk-core/ARCHITECTURE.md +9 -7
  14. package/sdk-core/README.md +5 -1
  15. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +1 -0
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -3
  17. package/sdk-core/client/src/lib.rs +26 -8
  18. package/sdk-core/client/src/raw.rs +166 -54
  19. package/sdk-core/client/src/retry.rs +9 -4
  20. package/sdk-core/client/src/workflow_handle/mod.rs +4 -2
  21. package/sdk-core/core/Cargo.toml +2 -0
  22. package/sdk-core/core/src/abstractions.rs +137 -16
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +258 -63
  24. package/sdk-core/core/src/core_tests/child_workflows.rs +1 -2
  25. package/sdk-core/core/src/core_tests/determinism.rs +2 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +8 -7
  27. package/sdk-core/core/src/core_tests/queries.rs +146 -60
  28. package/sdk-core/core/src/core_tests/replay_flag.rs +1 -1
  29. package/sdk-core/core/src/core_tests/workers.rs +39 -23
  30. package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
  31. package/sdk-core/core/src/core_tests/workflow_tasks.rs +387 -280
  32. package/sdk-core/core/src/lib.rs +6 -4
  33. package/sdk-core/core/src/pollers/poll_buffer.rs +16 -10
  34. package/sdk-core/core/src/protosext/mod.rs +6 -6
  35. package/sdk-core/core/src/retry_logic.rs +1 -1
  36. package/sdk-core/core/src/telemetry/metrics.rs +21 -7
  37. package/sdk-core/core/src/telemetry/mod.rs +18 -4
  38. package/sdk-core/core/src/test_help/mod.rs +341 -109
  39. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +18 -9
  40. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -16
  41. package/sdk-core/core/src/worker/activities.rs +156 -29
  42. package/sdk-core/core/src/worker/client.rs +1 -0
  43. package/sdk-core/core/src/worker/mod.rs +132 -659
  44. package/sdk-core/core/src/{workflow → worker/workflow}/bridge.rs +1 -1
  45. package/sdk-core/core/src/{workflow → worker/workflow}/driven_workflow.rs +1 -1
  46. package/sdk-core/core/src/{workflow → worker/workflow}/history_update.rs +16 -2
  47. package/sdk-core/core/src/{workflow → worker/workflow}/machines/activity_state_machine.rs +39 -4
  48. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_external_state_machine.rs +5 -2
  49. package/sdk-core/core/src/{workflow → worker/workflow}/machines/cancel_workflow_state_machine.rs +1 -1
  50. package/sdk-core/core/src/{workflow → worker/workflow}/machines/child_workflow_state_machine.rs +2 -4
  51. package/sdk-core/core/src/{workflow → worker/workflow}/machines/complete_workflow_state_machine.rs +0 -0
  52. package/sdk-core/core/src/{workflow → worker/workflow}/machines/continue_as_new_workflow_state_machine.rs +1 -1
  53. package/sdk-core/core/src/{workflow → worker/workflow}/machines/fail_workflow_state_machine.rs +0 -0
  54. package/sdk-core/core/src/{workflow → worker/workflow}/machines/local_activity_state_machine.rs +2 -5
  55. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mod.rs +1 -1
  56. package/sdk-core/core/src/{workflow → worker/workflow}/machines/mutable_side_effect_state_machine.rs +0 -0
  57. package/sdk-core/core/src/{workflow → worker/workflow}/machines/patch_state_machine.rs +1 -1
  58. package/sdk-core/core/src/{workflow → worker/workflow}/machines/side_effect_state_machine.rs +0 -0
  59. package/sdk-core/core/src/{workflow → worker/workflow}/machines/signal_external_state_machine.rs +4 -2
  60. package/sdk-core/core/src/{workflow → worker/workflow}/machines/timer_state_machine.rs +1 -2
  61. package/sdk-core/core/src/{workflow → worker/workflow}/machines/transition_coverage.rs +1 -1
  62. package/sdk-core/core/src/{workflow → worker/workflow}/machines/upsert_search_attributes_state_machine.rs +5 -7
  63. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines/local_acts.rs +2 -2
  64. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_machines.rs +40 -16
  65. package/sdk-core/core/src/{workflow → worker/workflow}/machines/workflow_task_state_machine.rs +0 -0
  66. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +198 -0
  67. package/sdk-core/core/src/worker/workflow/managed_run.rs +627 -0
  68. package/sdk-core/core/src/worker/workflow/mod.rs +1115 -0
  69. package/sdk-core/core/src/worker/workflow/run_cache.rs +143 -0
  70. package/sdk-core/core/src/worker/workflow/wft_poller.rs +88 -0
  71. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +936 -0
  72. package/sdk-core/core-api/src/errors.rs +3 -10
  73. package/sdk-core/core-api/src/lib.rs +2 -1
  74. package/sdk-core/core-api/src/worker.rs +26 -2
  75. package/sdk-core/etc/dynamic-config.yaml +2 -0
  76. package/sdk-core/integ-with-otel.sh +1 -1
  77. package/sdk-core/protos/api_upstream/Makefile +4 -4
  78. package/sdk-core/protos/api_upstream/api-linter.yaml +2 -0
  79. package/sdk-core/protos/api_upstream/buf.yaml +8 -9
  80. package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +83 -0
  81. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -1
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +40 -0
  83. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
  84. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +3 -1
  85. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +60 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +3 -0
  87. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +32 -4
  88. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +69 -19
  89. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +13 -0
  90. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +163 -0
  91. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +97 -0
  92. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +300 -0
  93. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +25 -0
  94. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +180 -3
  95. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +53 -3
  96. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +2 -2
  97. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +6 -5
  98. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -1
  99. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +2 -1
  100. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +0 -64
  101. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -1
  102. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +11 -8
  103. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +30 -25
  104. package/sdk-core/sdk/src/activity_context.rs +12 -5
  105. package/sdk-core/sdk/src/app_data.rs +37 -0
  106. package/sdk-core/sdk/src/lib.rs +76 -43
  107. package/sdk-core/sdk/src/workflow_context/options.rs +8 -6
  108. package/sdk-core/sdk/src/workflow_context.rs +14 -19
  109. package/sdk-core/sdk/src/workflow_future.rs +11 -6
  110. package/sdk-core/sdk-core-protos/src/history_builder.rs +19 -5
  111. package/sdk-core/sdk-core-protos/src/history_info.rs +11 -6
  112. package/sdk-core/sdk-core-protos/src/lib.rs +74 -176
  113. package/sdk-core/test-utils/src/lib.rs +85 -72
  114. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +11 -9
  115. package/sdk-core/tests/integ_tests/polling_tests.rs +12 -0
  116. package/sdk-core/tests/integ_tests/queries_tests.rs +39 -22
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +49 -4
  118. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +61 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  120. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +74 -13
  121. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +19 -0
  122. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  123. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests.rs +10 -23
  125. package/sdk-core/tests/load_tests.rs +8 -3
  126. package/sdk-core/tests/main.rs +2 -1
  127. package/src/conversions.rs +47 -39
  128. package/src/errors.rs +10 -21
  129. package/src/lib.rs +342 -325
  130. package/sdk-core/core/src/pending_activations.rs +0 -173
  131. package/sdk-core/core/src/worker/wft_delivery.rs +0 -81
  132. package/sdk-core/core/src/workflow/mod.rs +0 -478
  133. package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +0 -194
  134. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +0 -418
  135. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +0 -989
@@ -1,478 +0,0 @@
1
- pub(crate) mod workflow_tasks;
2
-
3
- mod bridge;
4
- mod driven_workflow;
5
- mod history_update;
6
- mod machines;
7
-
8
- pub(crate) use bridge::WorkflowBridge;
9
- pub(crate) use driven_workflow::{DrivenWorkflow, WorkflowFetcher};
10
- pub(crate) use history_update::{HistoryPaginator, HistoryUpdate};
11
- pub(crate) use machines::WFMachinesError;
12
-
13
- use crate::{
14
- telemetry::metrics::MetricsContext,
15
- worker::{LocalActRequest, LocalActivityResolution},
16
- };
17
- use machines::WorkflowMachines;
18
- use std::{result, sync::mpsc::Sender, time::Duration};
19
- use temporal_sdk_core_protos::{
20
- coresdk::{workflow_activation::WorkflowActivation, workflow_commands::*},
21
- temporal::api::command::v1::Command as ProtoCommand,
22
- };
23
-
24
- pub(crate) const LEGACY_QUERY_ID: &str = "legacy_query";
25
-
26
- type Result<T, E = WFMachinesError> = std::result::Result<T, E>;
27
-
28
- /// Manages an instance of a [WorkflowMachines], which is not thread-safe, as well as other data
29
- /// associated with that specific workflow run.
30
- pub(crate) struct WorkflowManager {
31
- machines: WorkflowMachines,
32
- /// Is always `Some` in normal operation. Optional to allow for unit testing with the test
33
- /// workflow driver, which does not need to complete activations the normal way.
34
- command_sink: Option<Sender<Vec<WFCommand>>>,
35
- }
36
-
37
- impl WorkflowManager {
38
- /// Create a new workflow manager given workflow history and execution info as would be found
39
- /// in [PollWorkflowTaskQueueResponse]
40
- pub fn new(
41
- history: HistoryUpdate,
42
- namespace: String,
43
- workflow_id: String,
44
- workflow_type: String,
45
- run_id: String,
46
- metrics: MetricsContext,
47
- ) -> Self {
48
- let (wfb, cmd_sink) = WorkflowBridge::new();
49
- let state_machines = WorkflowMachines::new(
50
- namespace,
51
- workflow_id,
52
- workflow_type,
53
- run_id,
54
- history,
55
- Box::new(wfb).into(),
56
- metrics,
57
- );
58
- Self {
59
- machines: state_machines,
60
- command_sink: Some(cmd_sink),
61
- }
62
- }
63
-
64
- #[cfg(test)]
65
- pub const fn new_from_machines(workflow_machines: WorkflowMachines) -> Self {
66
- Self {
67
- machines: workflow_machines,
68
- command_sink: None,
69
- }
70
- }
71
- }
72
-
73
- #[derive(Debug)]
74
- pub struct OutgoingServerCommands {
75
- pub commands: Vec<ProtoCommand>,
76
- pub replaying: bool,
77
- }
78
-
79
- #[derive(Debug)]
80
- pub(crate) enum LocalResolution {
81
- LocalActivity(LocalActivityResolution),
82
- }
83
-
84
- impl WorkflowManager {
85
- /// Given history that was just obtained from the server, pipe it into this workflow's machines.
86
- ///
87
- /// Should only be called when a workflow has caught up on replay (or is just beginning). It
88
- /// will return a workflow activation if one is needed.
89
- pub async fn feed_history_from_server(
90
- &mut self,
91
- update: HistoryUpdate,
92
- ) -> Result<WorkflowActivation> {
93
- self.machines.new_history_from_server(update).await?;
94
- self.get_next_activation().await
95
- }
96
-
97
- /// Let this workflow know that something we've been waiting locally on has resolved, like a
98
- /// local activity or side effect
99
- ///
100
- /// Returns true if the resolution did anything. EX: If the activity is already canceled and
101
- /// used the TryCancel or Abandon modes, the resolution is uninteresting.
102
- pub fn notify_of_local_result(&mut self, resolved: LocalResolution) -> Result<bool> {
103
- self.machines.local_resolution(resolved)
104
- }
105
-
106
- /// Fetch the next workflow activation for this workflow if one is required. Doing so will apply
107
- /// the next unapplied workflow task if such a sequence exists in history we already know about.
108
- ///
109
- /// Callers may also need to call [get_server_commands] after this to issue any pending commands
110
- /// to the server.
111
- pub async fn get_next_activation(&mut self) -> Result<WorkflowActivation> {
112
- // First check if there are already some pending jobs, which can be a result of replay.
113
- let activation = self.machines.get_wf_activation();
114
- if !activation.jobs.is_empty() {
115
- return Ok(activation);
116
- }
117
-
118
- self.machines.apply_next_wft_from_history().await?;
119
- Ok(self.machines.get_wf_activation())
120
- }
121
-
122
- /// If there are no pending jobs for the workflow, apply the next workflow task and check
123
- /// again if there are any jobs. Importantly, does not *drain* jobs.
124
- ///
125
- /// Returns true if there are jobs (before or after applying the next WFT).
126
- pub async fn apply_next_task_if_ready(&mut self) -> Result<bool> {
127
- if self.machines.has_pending_jobs() {
128
- return Ok(true);
129
- }
130
- loop {
131
- let consumed_events = self.machines.apply_next_wft_from_history().await?;
132
-
133
- if consumed_events == 0 || !self.machines.replaying || self.machines.has_pending_jobs()
134
- {
135
- // Keep applying tasks while there are events, we are still replaying, and there are
136
- // no jobs
137
- break;
138
- }
139
- }
140
- Ok(self.machines.has_pending_jobs())
141
- }
142
-
143
- /// Typically called after [get_next_activation], use this to retrieve commands to be sent to
144
- /// the server which have been generated by the machines. Does *not* drain those commands.
145
- /// See [WorkflowMachines::get_commands].
146
- pub fn get_server_commands(&mut self) -> OutgoingServerCommands {
147
- OutgoingServerCommands {
148
- commands: self.machines.get_commands(),
149
- replaying: self.machines.replaying,
150
- }
151
- }
152
-
153
- /// Remove and return all queued local activities. Once this is called, they need to be
154
- /// dispatched for execution.
155
- pub fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
156
- self.machines.drain_queued_local_activities()
157
- }
158
-
159
- /// Feed the workflow machines new commands issued by the executing workflow code, and iterate
160
- /// the machines.
161
- pub async fn push_commands(&mut self, cmds: Vec<WFCommand>) -> Result<()> {
162
- if let Some(cs) = self.command_sink.as_mut() {
163
- cs.send(cmds).map_err(|_| {
164
- WFMachinesError::Fatal("Internal error buffering workflow commands".to_string())
165
- })?;
166
- }
167
- self.machines.iterate_machines().await?;
168
- Ok(())
169
- }
170
- }
171
-
172
- /// Determines when workflows are kept in the cache or evicted
173
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
174
- pub(crate) enum WorkflowCachingPolicy {
175
- /// Workflows are cached until evicted explicitly or the cache size limit is reached, in which
176
- /// case they are evicted by least-recently-used ordering.
177
- Sticky {
178
- /// The maximum number of workflows that will be kept in the cache
179
- max_cached_workflows: usize,
180
- },
181
- /// Workflows are evicted after each workflow task completion. Note that this is *not* after
182
- /// each workflow activation - there are often multiple activations per workflow task.
183
- NonSticky,
184
-
185
- /// Not a real mode, but good for imitating crashes. Evict workflows after *every* reply,
186
- /// even if there are pending activations
187
- #[cfg(test)]
188
- AfterEveryReply,
189
- }
190
-
191
- #[derive(thiserror::Error, Debug, derive_more::From)]
192
- #[error("Lang provided workflow command with empty variant")]
193
- pub struct EmptyWorkflowCommandErr;
194
-
195
- /// [DrivenWorkflow]s respond with these when called, to indicate what they want to do next.
196
- /// EX: Create a new timer, complete the workflow, etc.
197
- #[derive(Debug, derive_more::From, derive_more::Display)]
198
- #[allow(clippy::large_enum_variant)]
199
- pub enum WFCommand {
200
- /// Returned when we need to wait for the lang sdk to send us something
201
- NoCommandsFromLang,
202
- AddActivity(ScheduleActivity),
203
- AddLocalActivity(ScheduleLocalActivity),
204
- RequestCancelActivity(RequestCancelActivity),
205
- RequestCancelLocalActivity(RequestCancelLocalActivity),
206
- AddTimer(StartTimer),
207
- CancelTimer(CancelTimer),
208
- CompleteWorkflow(CompleteWorkflowExecution),
209
- FailWorkflow(FailWorkflowExecution),
210
- QueryResponse(QueryResult),
211
- ContinueAsNew(ContinueAsNewWorkflowExecution),
212
- CancelWorkflow(CancelWorkflowExecution),
213
- SetPatchMarker(SetPatchMarker),
214
- AddChildWorkflow(StartChildWorkflowExecution),
215
- CancelUnstartedChild(CancelUnstartedChildWorkflowExecution),
216
- RequestCancelExternalWorkflow(RequestCancelExternalWorkflowExecution),
217
- SignalExternalWorkflow(SignalExternalWorkflowExecution),
218
- CancelSignalWorkflow(CancelSignalWorkflow),
219
- UpsertSearchAttributes(UpsertWorkflowSearchAttributes),
220
- }
221
-
222
- impl TryFrom<WorkflowCommand> for WFCommand {
223
- type Error = EmptyWorkflowCommandErr;
224
-
225
- fn try_from(c: WorkflowCommand) -> result::Result<Self, Self::Error> {
226
- match c.variant.ok_or(EmptyWorkflowCommandErr)? {
227
- workflow_command::Variant::StartTimer(s) => Ok(Self::AddTimer(s)),
228
- workflow_command::Variant::CancelTimer(s) => Ok(Self::CancelTimer(s)),
229
- workflow_command::Variant::ScheduleActivity(s) => Ok(Self::AddActivity(s)),
230
- workflow_command::Variant::RequestCancelActivity(s) => {
231
- Ok(Self::RequestCancelActivity(s))
232
- }
233
- workflow_command::Variant::CompleteWorkflowExecution(c) => {
234
- Ok(Self::CompleteWorkflow(c))
235
- }
236
- workflow_command::Variant::FailWorkflowExecution(s) => Ok(Self::FailWorkflow(s)),
237
- workflow_command::Variant::RespondToQuery(s) => Ok(Self::QueryResponse(s)),
238
- workflow_command::Variant::ContinueAsNewWorkflowExecution(s) => {
239
- Ok(Self::ContinueAsNew(s))
240
- }
241
- workflow_command::Variant::CancelWorkflowExecution(s) => Ok(Self::CancelWorkflow(s)),
242
- workflow_command::Variant::SetPatchMarker(s) => Ok(Self::SetPatchMarker(s)),
243
- workflow_command::Variant::StartChildWorkflowExecution(s) => {
244
- Ok(Self::AddChildWorkflow(s))
245
- }
246
- workflow_command::Variant::RequestCancelExternalWorkflowExecution(s) => {
247
- Ok(Self::RequestCancelExternalWorkflow(s))
248
- }
249
- workflow_command::Variant::SignalExternalWorkflowExecution(s) => {
250
- Ok(Self::SignalExternalWorkflow(s))
251
- }
252
- workflow_command::Variant::CancelSignalWorkflow(s) => Ok(Self::CancelSignalWorkflow(s)),
253
- workflow_command::Variant::CancelUnstartedChildWorkflowExecution(s) => {
254
- Ok(Self::CancelUnstartedChild(s))
255
- }
256
- workflow_command::Variant::ScheduleLocalActivity(s) => Ok(Self::AddLocalActivity(s)),
257
- workflow_command::Variant::RequestCancelLocalActivity(s) => {
258
- Ok(Self::RequestCancelLocalActivity(s))
259
- }
260
- workflow_command::Variant::UpsertWorkflowSearchAttributesCommandAttributes(s) => {
261
- Ok(Self::UpsertSearchAttributes(s))
262
- }
263
- }
264
- }
265
- }
266
-
267
- #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
268
- enum CommandID {
269
- Timer(u32),
270
- Activity(u32),
271
- LocalActivity(u32),
272
- ChildWorkflowStart(u32),
273
- SignalExternal(u32),
274
- CancelExternal(u32),
275
- }
276
-
277
- /// Details remembered from the workflow execution started event that we may need to recall later.
278
- /// Is a subset of `WorkflowExecutionStartedEventAttributes`, but avoids holding on to huge fields.
279
- #[derive(Debug, Clone)]
280
- pub struct WorkflowStartedInfo {
281
- workflow_task_timeout: Option<Duration>,
282
- workflow_execution_timeout: Option<Duration>,
283
- }
284
-
285
- #[cfg(test)]
286
- pub mod managed_wf {
287
- use super::*;
288
- use crate::{
289
- replay::TestHistoryBuilder,
290
- test_help::TEST_Q,
291
- workflow::{history_update::TestHBExt, WFCommand, WorkflowFetcher},
292
- };
293
- use std::{convert::TryInto, time::Duration};
294
- use temporal_sdk::{WorkflowFunction, WorkflowResult};
295
- use temporal_sdk_core_protos::coresdk::{
296
- activity_result::ActivityExecutionResult,
297
- common::Payload,
298
- workflow_activation::{create_evict_activation, remove_from_cache::EvictionReason},
299
- workflow_completion::{
300
- workflow_activation_completion::Status, WorkflowActivationCompletion,
301
- },
302
- };
303
- use tokio::{
304
- sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
305
- task::JoinHandle,
306
- };
307
-
308
- pub(crate) struct WFFutureDriver {
309
- completions_rx: UnboundedReceiver<WorkflowActivationCompletion>,
310
- }
311
-
312
- #[async_trait::async_trait]
313
- impl WorkflowFetcher for WFFutureDriver {
314
- async fn fetch_workflow_iteration_output(&mut self) -> Vec<WFCommand> {
315
- if let Some(completion) = self.completions_rx.recv().await {
316
- debug!("Managed wf completion: {}", completion);
317
- completion
318
- .status
319
- .map(|s| match s {
320
- Status::Successful(s) => s
321
- .commands
322
- .into_iter()
323
- .map(|cmd| cmd.try_into().unwrap())
324
- .collect(),
325
- Status::Failed(_) => panic!("Ahh failed"),
326
- })
327
- .unwrap_or_default()
328
- } else {
329
- // Sender went away so nothing to do here. End of wf/test.
330
- vec![]
331
- }
332
- }
333
- }
334
-
335
- #[must_use]
336
- pub struct ManagedWFFunc {
337
- mgr: WorkflowManager,
338
- activation_tx: UnboundedSender<WorkflowActivation>,
339
- future_handle: Option<JoinHandle<WorkflowResult<()>>>,
340
- was_shutdown: bool,
341
- }
342
-
343
- impl ManagedWFFunc {
344
- pub fn new(hist: TestHistoryBuilder, func: WorkflowFunction, args: Vec<Payload>) -> Self {
345
- Self::new_from_update(hist.as_history_update(), func, args)
346
- }
347
-
348
- pub fn new_from_update(
349
- hist: HistoryUpdate,
350
- func: WorkflowFunction,
351
- args: Vec<Payload>,
352
- ) -> Self {
353
- let (completions_tx, completions_rx) = unbounded_channel();
354
- let (wff, activations) = func.start_workflow(
355
- "testnamespace".to_string(),
356
- TEST_Q.to_string(),
357
- args,
358
- completions_tx,
359
- );
360
- let spawned = tokio::spawn(wff);
361
- let driver = WFFutureDriver { completions_rx };
362
- let state_machines = WorkflowMachines::new(
363
- "test_namespace".to_string(),
364
- "wfid".to_string(),
365
- "wftype".to_string(),
366
- "runid".to_string(),
367
- hist,
368
- Box::new(driver).into(),
369
- Default::default(),
370
- );
371
- let mgr = WorkflowManager::new_from_machines(state_machines);
372
- Self {
373
- mgr,
374
- activation_tx: activations,
375
- future_handle: Some(spawned),
376
- was_shutdown: false,
377
- }
378
- }
379
-
380
- #[instrument(level = "debug", skip(self))]
381
- pub(crate) async fn get_next_activation(&mut self) -> Result<WorkflowActivation> {
382
- let res = self.mgr.get_next_activation().await?;
383
- debug!("Managed wf next activation: {}", &res);
384
- self.push_activation_to_wf(&res).await?;
385
- Ok(res)
386
- }
387
-
388
- /// Return outgoing server commands as of the last iteration
389
- pub(crate) fn get_server_commands(&mut self) -> OutgoingServerCommands {
390
- self.mgr.get_server_commands()
391
- }
392
-
393
- pub(crate) fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
394
- self.mgr.drain_queued_local_activities()
395
- }
396
-
397
- /// Feed new history, as if received a new poll result. Returns new activation
398
- #[instrument(level = "debug", skip(self, update))]
399
- pub(crate) async fn new_history(
400
- &mut self,
401
- update: HistoryUpdate,
402
- ) -> Result<WorkflowActivation> {
403
- let res = self.mgr.feed_history_from_server(update).await?;
404
- self.push_activation_to_wf(&res).await?;
405
- Ok(res)
406
- }
407
-
408
- /// Say a local activity completed (they always take 1 second in these tests)
409
- pub(crate) fn complete_local_activity(
410
- &mut self,
411
- seq_num: u32,
412
- result: ActivityExecutionResult,
413
- ) -> Result<bool> {
414
- self.mgr
415
- .notify_of_local_result(LocalResolution::LocalActivity(LocalActivityResolution {
416
- seq: seq_num,
417
- // We accept normal execution results and do this conversion because there
418
- // are more helpers for constructing them.
419
- result: result
420
- .status
421
- .expect("LA result must have a status")
422
- .try_into()
423
- .expect("LA execution result must be a valid LA result"),
424
- runtime: Duration::from_secs(1),
425
- attempt: 1,
426
- backoff: None,
427
- // Tests at this level don't use the LA dispatcher, so this is irrelevant
428
- original_schedule_time: None,
429
- }))
430
- }
431
-
432
- /// During testing it can be useful to run through all activations to simulate replay
433
- /// easily. Returns the last produced activation with jobs in it, or an activation with no
434
- /// jobs if the first call had no jobs.
435
- pub(crate) async fn process_all_activations(&mut self) -> Result<WorkflowActivation> {
436
- let mut last_act = self.get_next_activation().await?;
437
- let mut next_act = self.get_next_activation().await?;
438
- while !next_act.jobs.is_empty() {
439
- last_act = next_act;
440
- next_act = self.get_next_activation().await?;
441
- }
442
- Ok(last_act)
443
- }
444
-
445
- pub async fn shutdown(&mut self) -> WorkflowResult<()> {
446
- self.was_shutdown = true;
447
- // Send an eviction to ensure wf exits if it has not finished (ex: feeding partial hist)
448
- let _ = self.activation_tx.send(create_evict_activation(
449
- "not actually important".to_string(),
450
- "force shutdown".to_string(),
451
- EvictionReason::Unspecified,
452
- ));
453
- self.future_handle.take().unwrap().await.unwrap()
454
- }
455
-
456
- #[instrument(level = "debug", skip(self, res))]
457
- async fn push_activation_to_wf(&mut self, res: &WorkflowActivation) -> Result<()> {
458
- if res.jobs.is_empty() {
459
- // Nothing to do here
460
- return Ok(());
461
- }
462
- self.activation_tx
463
- .send(res.clone())
464
- .expect("Workflow should not be dropped if we are still sending activations");
465
- self.mgr.machines.iterate_machines().await?;
466
- Ok(())
467
- }
468
- }
469
-
470
- impl Drop for ManagedWFFunc {
471
- fn drop(&mut self) {
472
- // Double panics cause a SIGILL
473
- if !self.was_shutdown && !std::thread::panicking() {
474
- panic!("You must call `shutdown` to properly use ManagedWFFunc in tests")
475
- }
476
- }
477
- }
478
- }
@@ -1,194 +0,0 @@
1
- use crate::{telemetry::metrics::MetricsContext, workflow::WorkflowCachingPolicy};
2
- use lru::LruCache;
3
- use std::{
4
- future::Future,
5
- sync::{
6
- atomic::{AtomicUsize, Ordering},
7
- Arc,
8
- },
9
- };
10
- use tokio::sync::Notify;
11
-
12
- /// Helps to maintain an LRU ordering in which workflow runs have been accessed so that old runs may
13
- /// be evicted once we reach the cap.
14
- #[derive(Debug)]
15
- pub(crate) struct WorkflowCacheManager {
16
- cache: LruCache<String, ()>,
17
- metrics: MetricsContext,
18
- cap_notify: Arc<Notify>,
19
- cache_size: Arc<AtomicUsize>,
20
- cap_mutex: Arc<tokio::sync::Mutex<()>>,
21
- }
22
-
23
- impl WorkflowCacheManager {
24
- pub fn new(policy: WorkflowCachingPolicy, metrics: MetricsContext) -> Self {
25
- let cap = match policy {
26
- WorkflowCachingPolicy::Sticky {
27
- max_cached_workflows,
28
- } => max_cached_workflows,
29
- _ => 0,
30
- };
31
- Self {
32
- cache: LruCache::new(cap),
33
- metrics,
34
- cap_notify: Arc::new(Notify::new()),
35
- cache_size: Arc::new(AtomicUsize::new(0)),
36
- cap_mutex: Arc::new(tokio::sync::Mutex::new(())),
37
- }
38
- }
39
-
40
- #[cfg(test)]
41
- fn new_test(policy: WorkflowCachingPolicy) -> Self {
42
- Self::new(policy, Default::default())
43
- }
44
-
45
- /// Resolves once there is an open slot in the cache. The passed in closure can be used to
46
- /// exit the wait loop if it returns true even if the cache is not below the limit. It will be
47
- /// re-evaluated every time there is an insert or remove to the cache, even if it did not change
48
- /// the size.
49
- pub fn wait_for_capacity<Fun>(&self, early_exit: Fun) -> Option<impl Future<Output = ()>>
50
- where
51
- Fun: Fn() -> bool,
52
- {
53
- if self.cache.cap() == 0 {
54
- return None;
55
- }
56
-
57
- let size = self.cache_size.clone();
58
- let notify = self.cap_notify.clone();
59
- let cap = self.cache.cap();
60
- let mx = self.cap_mutex.clone();
61
- Some(async move {
62
- let _l = mx.lock().await;
63
- while !early_exit() && size.load(Ordering::Acquire) >= cap {
64
- notify.notified().await;
65
- }
66
- })
67
- }
68
-
69
- /// Inserts a record associated with the run id into the lru cache.
70
- /// Once cache reaches capacity, overflow records will be returned back to the caller.
71
- pub fn insert(&mut self, run_id: &str) -> Option<String> {
72
- let res = if self.cache.len() < self.cache.cap() {
73
- // Blindly add a record into the cache, since it still has capacity.
74
- self.cache.put(run_id.to_owned(), ());
75
- None
76
- } else if self.cache.cap() == 0 {
77
- // Run id should be evicted right away as cache size is 0.
78
- Some(run_id.to_owned())
79
- } else {
80
- let maybe_got_evicted = self.cache.peek_lru().map(|r| r.0.clone());
81
- let not_cached = self.cache.put(run_id.to_owned(), ()).is_none();
82
- not_cached.then(|| maybe_got_evicted).flatten()
83
- };
84
-
85
- self.size_changed();
86
-
87
- res
88
- }
89
-
90
- /// If run id exists in the cache it will be moved to the top of the LRU cache.
91
- pub fn touch(&mut self, run_id: &str) {
92
- self.cache.get(run_id);
93
- }
94
-
95
- pub fn remove(&mut self, run_id: &str) {
96
- self.cache.pop(run_id);
97
- self.size_changed();
98
- }
99
-
100
- fn size_changed(&self) {
101
- let size = self.cache.len();
102
- self.metrics.cache_size(size as u64);
103
- self.cache_size.store(size, Ordering::Release);
104
- self.cap_notify.notify_one();
105
- }
106
- }
107
-
108
- #[cfg(test)]
109
- mod tests {
110
- use super::*;
111
- #[test]
112
- fn insert_with_overflow() {
113
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
114
- max_cached_workflows: 2,
115
- });
116
- assert_matches!(wcm.insert("1"), None);
117
- assert_matches!(wcm.insert("2"), None);
118
- assert_matches!(wcm.insert("3"), Some(run_id) => {
119
- assert_eq!(run_id, "1");
120
- });
121
- }
122
-
123
- #[test]
124
- fn insert_remove_insert() {
125
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
126
- max_cached_workflows: 1,
127
- });
128
- assert_matches!(wcm.insert("1"), None);
129
- wcm.remove("1");
130
- assert_matches!(wcm.insert("2"), None);
131
- assert_matches!(wcm.insert("3"), Some(run_id) => {
132
- assert_eq!(run_id, "2");
133
- });
134
- }
135
-
136
- #[test]
137
- fn insert_same_id_twice_doesnt_evict_self() {
138
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
139
- max_cached_workflows: 1,
140
- });
141
- assert_matches!(wcm.insert("1"), None);
142
- assert_matches!(wcm.insert("1"), None);
143
- }
144
-
145
- #[test]
146
- fn insert_and_touch() {
147
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
148
- max_cached_workflows: 2,
149
- });
150
- assert_matches!(wcm.insert("1"), None);
151
- assert_matches!(wcm.insert("2"), None);
152
- wcm.touch("1");
153
- assert_matches!(wcm.insert("3"), Some(run_id) => {
154
- assert_eq!(run_id, "2");
155
- });
156
- }
157
-
158
- #[test]
159
- fn touch_early() {
160
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
161
- max_cached_workflows: 2,
162
- });
163
- wcm.touch("1");
164
- assert_matches!(wcm.insert("1"), None);
165
- assert_matches!(wcm.insert("2"), None);
166
- assert_matches!(wcm.insert("3"), Some(run_id) => {
167
- assert_eq!(run_id, "1");
168
- });
169
- }
170
-
171
- #[test]
172
- fn zero_cache_size() {
173
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::Sticky {
174
- max_cached_workflows: 0,
175
- });
176
- assert_matches!(wcm.insert("1"), Some(run_id) => {
177
- assert_eq!(run_id, "1");
178
- });
179
- assert_matches!(wcm.insert("2"), Some(run_id) => {
180
- assert_eq!(run_id, "2");
181
- });
182
- }
183
-
184
- #[test]
185
- fn non_sticky_always_pending_eviction() {
186
- let mut wcm = WorkflowCacheManager::new_test(WorkflowCachingPolicy::NonSticky);
187
- assert_matches!(wcm.insert("1"), Some(run_id) => {
188
- assert_eq!(run_id, "1");
189
- });
190
- assert_matches!(wcm.insert("2"), Some(run_id) => {
191
- assert_eq!(run_id, "2");
192
- });
193
- }
194
- }