@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
@@ -1,20 +1,29 @@
1
+ mod local_acts;
2
+
3
+ pub(crate) use temporal_sdk_core_api::errors::WFMachinesError;
4
+
5
+ use super::{
6
+ activity_state_machine::new_activity, cancel_external_state_machine::new_external_cancel,
7
+ cancel_workflow_state_machine::cancel_workflow,
8
+ child_workflow_state_machine::new_child_workflow,
9
+ complete_workflow_state_machine::complete_workflow,
10
+ continue_as_new_workflow_state_machine::continue_as_new,
11
+ fail_workflow_state_machine::fail_workflow, local_activity_state_machine::new_local_activity,
12
+ patch_state_machine::has_change, signal_external_state_machine::new_external_signal,
13
+ timer_state_machine::new_timer, workflow_machines::local_acts::LocalActivityData,
14
+ workflow_task_state_machine::WorkflowTaskMachine, MachineKind, Machines, NewMachineWithCommand,
15
+ TemporalStateMachine,
16
+ };
1
17
  use crate::{
2
- machines::{
3
- activity_state_machine::new_activity, cancel_external_state_machine::new_external_cancel,
4
- cancel_workflow_state_machine::cancel_workflow,
5
- child_workflow_state_machine::new_child_workflow,
6
- complete_workflow_state_machine::complete_workflow,
7
- continue_as_new_workflow_state_machine::continue_as_new,
8
- fail_workflow_state_machine::fail_workflow, patch_state_machine::has_change,
9
- signal_external_state_machine::new_external_signal, timer_state_machine::new_timer,
10
- workflow_task_state_machine::WorkflowTaskMachine, MachineKind, NewMachineWithCommand,
11
- ProtoCommand, TemporalStateMachine, WFCommand,
12
- },
13
- protosext::HistoryEventExt,
18
+ protosext::{HistoryEventExt, ValidScheduleLA},
14
19
  telemetry::{metrics::MetricsContext, VecDisplayer},
15
- workflow::{CommandID, DrivenWorkflow, HistoryUpdate, WorkflowFetcher},
20
+ worker::{
21
+ ExecutingLAId, LocalActRequest, LocalActivityExecutionResult, LocalActivityResolution,
22
+ },
23
+ workflow::{
24
+ CommandID, DrivenWorkflow, HistoryUpdate, LocalResolution, WFCommand, WorkflowFetcher,
25
+ },
16
26
  };
17
- use prost_types::TimestampOutOfSystemRangeError;
18
27
  use slotmap::SlotMap;
19
28
  use std::{
20
29
  borrow::{Borrow, BorrowMut},
@@ -27,8 +36,8 @@ use temporal_sdk_core_protos::{
27
36
  coresdk::{
28
37
  common::{NamespacedWorkflowExecution, Payload},
29
38
  workflow_activation::{
30
- wf_activation_job::{self, Variant},
31
- NotifyHasPatch, StartWorkflow, UpdateRandomSeed, WfActivation,
39
+ workflow_activation_job::{self, Variant},
40
+ NotifyHasPatch, StartWorkflow, UpdateRandomSeed, WorkflowActivation,
32
41
  },
33
42
  workflow_commands::{
34
43
  request_cancel_external_workflow_execution as cancel_we,
@@ -37,12 +46,13 @@ use temporal_sdk_core_protos::{
37
46
  FromPayloadsExt,
38
47
  },
39
48
  temporal::api::{
49
+ command::v1::Command as ProtoCommand,
40
50
  common::v1::Header,
41
51
  enums::v1::EventType,
42
- history::v1::{history_event, HistoryEvent},
52
+ history::v1::{history_event, HistoryEvent, WorkflowExecutionStartedEventAttributes},
43
53
  },
54
+ utilities::TryIntoOrNone,
44
55
  };
45
- use tracing::Level;
46
56
 
47
57
  type Result<T, E = WFMachinesError> = std::result::Result<T, E>;
48
58
 
@@ -64,13 +74,15 @@ pub(crate) struct WorkflowMachines {
64
74
  /// The event id of the most recent event processed. It's possible in some situations (ex legacy
65
75
  /// queries) to receive a history with no new workflow tasks. If the last history we processed
66
76
  /// also had no new tasks, we need a way to know not to apply the same events over again.
67
- last_processed_event: i64,
77
+ pub last_processed_event: i64,
68
78
  /// True if the workflow is replaying from history
69
79
  pub replaying: bool,
70
80
  /// Namespace this workflow exists in
71
81
  pub namespace: String,
72
82
  /// Workflow identifier
73
83
  pub workflow_id: String,
84
+ /// Workflow type identifier. (Function name, class, etc)
85
+ pub workflow_type: String,
74
86
  /// Identifies the current run
75
87
  pub run_id: String,
76
88
  /// The time the workflow execution began, as told by the WEStarted event
@@ -78,17 +90,18 @@ pub(crate) struct WorkflowMachines {
78
90
  /// The time the workflow execution finished, as determined by when the machines handled
79
91
  /// a terminal workflow command. If this is `Some`, you know the workflow is ended.
80
92
  workflow_end_time: Option<SystemTime>,
81
- /// The current workflow time if it has been established
93
+ /// The WFT start time if it has been established
94
+ wft_start_time: Option<SystemTime>,
95
+ /// The current workflow time if it has been established. This may differ from the WFT start
96
+ /// time since local activities may advance the clock
82
97
  current_wf_time: Option<SystemTime>,
83
98
 
84
- // TODO: Nothing gets deleted from here
85
- all_machines: SlotMap<MachineKey, Box<dyn TemporalStateMachine + 'static>>,
99
+ all_machines: SlotMap<MachineKey, Machines>,
86
100
 
87
101
  /// A mapping for accessing machines associated to a particular event, where the key is the id
88
102
  /// of the initiating event for that machine.
89
103
  machines_by_event_id: HashMap<i64, MachineKey>,
90
104
 
91
- // TODO: Nothing gets deleted from here
92
105
  /// Maps command ids as created by workflow authors to their associated machines.
93
106
  id_to_machine: HashMap<CommandID, MachineKey>,
94
107
 
@@ -97,19 +110,20 @@ pub(crate) struct WorkflowMachines {
97
110
  commands: VecDeque<CommandAndMachine>,
98
111
  /// Commands generated by the currently processing workflow task, which will eventually be
99
112
  /// transferred to `commands` (and hence eventually sent to the server)
100
- ///
101
- /// Old note: It is a queue as commands can be added (due to marker based commands) while
102
- /// iterating over already added commands.
103
113
  current_wf_task_commands: VecDeque<CommandAndMachine>,
114
+
104
115
  /// Information about patch markers we have already seen while replaying history
105
116
  encountered_change_markers: HashMap<String, ChangeInfo>,
106
117
 
118
+ /// Contains extra local-activity related data
119
+ local_activity_data: LocalActivityData,
120
+
107
121
  /// The workflow that is being driven by this instance of the machines
108
122
  drive_me: DrivenWorkflow,
109
123
 
110
124
  /// Is set to true once we've seen the final event in workflow history, to avoid accidentally
111
125
  /// re-applying the final workflow task.
112
- have_seen_terminal_event: bool,
126
+ pub have_seen_terminal_event: bool,
113
127
 
114
128
  /// Metrics context
115
129
  pub metrics: MetricsContext,
@@ -118,10 +132,17 @@ pub(crate) struct WorkflowMachines {
118
132
  #[derive(Debug, derive_more::Display)]
119
133
  #[display(fmt = "Cmd&Machine({})", "command")]
120
134
  struct CommandAndMachine {
121
- command: ProtoCommand,
135
+ command: MachineAssociatedCommand,
122
136
  machine: MachineKey,
123
137
  }
124
138
 
139
+ #[derive(Debug, derive_more::Display)]
140
+ enum MachineAssociatedCommand {
141
+ Real(Box<ProtoCommand>),
142
+ #[display(fmt = "FakeLocalActivityMarker({})", "_0")]
143
+ FakeLocalActivityMarker(u32),
144
+ }
145
+
125
146
  #[derive(Debug, Clone, Copy)]
126
147
  struct ChangeInfo {
127
148
  created_command: bool,
@@ -132,10 +153,12 @@ struct ChangeInfo {
132
153
  #[must_use]
133
154
  #[allow(clippy::large_enum_variant)]
134
155
  pub enum MachineResponse {
135
- #[display(fmt = "PushWFJob")]
136
- PushWFJob(wf_activation_job::Variant),
156
+ #[display(fmt = "PushWFJob({})", "_0")]
157
+ PushWFJob(workflow_activation_job::Variant),
137
158
 
138
159
  IssueNewCommand(ProtoCommand),
160
+ #[display(fmt = "IssueFakeLocalActivityMarker({})", "_0")]
161
+ IssueFakeLocalActivityMarker(u32),
139
162
  #[display(fmt = "TriggerWFTaskStarted")]
140
163
  TriggerWFTaskStarted {
141
164
  task_started_event_id: i64,
@@ -145,43 +168,37 @@ pub enum MachineResponse {
145
168
  UpdateRunIdOnWorkflowReset {
146
169
  run_id: String,
147
170
  },
171
+
172
+ /// Queue a local activity to be processed by the worker
173
+ #[display(fmt = "QueueLocalActivity")]
174
+ QueueLocalActivity(ValidScheduleLA),
175
+ /// Request cancellation of an executing local activity
176
+ #[display(fmt = "RequestCancelLocalActivity({})", "_0")]
177
+ RequestCancelLocalActivity(u32),
178
+ /// Indicates we are abandoning the indicated LA, so we can remove it from "outstanding" LAs
179
+ /// and we will not try to WFT heartbeat because of it.
180
+ #[display(fmt = "AbandonLocalActivity({:?})", "_0")]
181
+ AbandonLocalActivity(u32),
182
+
183
+ /// Set the workflow time to the provided time
184
+ #[display(fmt = "UpdateWFTime({:?})", "_0")]
185
+ UpdateWFTime(Option<SystemTime>),
148
186
  }
149
187
 
150
- // Must use `From` b/c ofZZ
151
188
  impl<T> From<T> for MachineResponse
152
189
  where
153
- T: Into<wf_activation_job::Variant>,
190
+ T: Into<workflow_activation_job::Variant>,
154
191
  {
155
192
  fn from(v: T) -> Self {
156
193
  Self::PushWFJob(v.into())
157
194
  }
158
195
  }
159
196
 
160
- #[derive(thiserror::Error, Debug)]
161
- pub(crate) enum WFMachinesError {
162
- #[error("Nondeterminism error: {0}")]
163
- Nondeterminism(String),
164
- #[error("Fatal error in workflow machines: {0}")]
165
- Fatal(String),
166
-
167
- #[error("Unrecoverable network error while fetching history: {0}")]
168
- HistoryFetchingError(tonic::Status),
169
-
170
- /// Should always be caught internally and turned into a workflow task failure
171
- #[error("Unable to process partial event history because workflow is no longer cached.")]
172
- CacheMiss,
173
- }
174
-
175
- impl From<TimestampOutOfSystemRangeError> for WFMachinesError {
176
- fn from(_: TimestampOutOfSystemRangeError) -> Self {
177
- Self::Fatal("Could not decode timestamp".to_string())
178
- }
179
- }
180
-
181
197
  impl WorkflowMachines {
182
198
  pub(crate) fn new(
183
199
  namespace: String,
184
200
  workflow_id: String,
201
+ workflow_type: String,
185
202
  run_id: String,
186
203
  history: HistoryUpdate,
187
204
  driven_wf: DrivenWorkflow,
@@ -192,6 +209,7 @@ impl WorkflowMachines {
192
209
  last_history_from_server: history,
193
210
  namespace,
194
211
  workflow_id,
212
+ workflow_type,
195
213
  run_id,
196
214
  drive_me: driven_wf,
197
215
  replaying,
@@ -202,6 +220,7 @@ impl WorkflowMachines {
202
220
  last_processed_event: 0,
203
221
  workflow_start_time: None,
204
222
  workflow_end_time: None,
223
+ wft_start_time: None,
205
224
  current_wf_time: None,
206
225
  all_machines: Default::default(),
207
226
  machines_by_event_id: Default::default(),
@@ -209,6 +228,7 @@ impl WorkflowMachines {
209
228
  commands: Default::default(),
210
229
  current_wf_task_commands: Default::default(),
211
230
  encountered_change_markers: Default::default(),
231
+ local_activity_data: LocalActivityData::default(),
212
232
  have_seen_terminal_event: false,
213
233
  }
214
234
  }
@@ -233,16 +253,60 @@ impl WorkflowMachines {
233
253
  Ok(())
234
254
  }
235
255
 
256
+ /// Let this workflow know that something we've been waiting locally on has resolved, like a
257
+ /// local activity or side effect
258
+ pub(crate) fn local_resolution(&mut self, resolution: LocalResolution) -> Result<()> {
259
+ match resolution {
260
+ LocalResolution::LocalActivity(LocalActivityResolution {
261
+ seq,
262
+ result,
263
+ runtime,
264
+ attempt,
265
+ backoff,
266
+ original_schedule_time,
267
+ }) => {
268
+ let act_id = CommandID::LocalActivity(seq);
269
+ let mk = self.get_machine_key(act_id)?;
270
+ let mach = self.machine_mut(mk);
271
+ if let Machines::LocalActivityMachine(ref mut lam) = *mach {
272
+ let resps =
273
+ lam.try_resolve(result, runtime, attempt, backoff, original_schedule_time)?;
274
+ self.process_machine_responses(mk, resps)?;
275
+ } else {
276
+ return Err(WFMachinesError::Nondeterminism(format!(
277
+ "Command matching activity with seq num {} existed but was not a \
278
+ local activity!",
279
+ seq
280
+ )));
281
+ }
282
+ self.local_activity_data.done_executing(seq);
283
+ }
284
+ }
285
+ Ok(())
286
+ }
287
+
288
+ /// Drain all queued local activities that need executing or cancellation
289
+ pub(crate) fn drain_queued_local_activities(&mut self) -> Vec<LocalActRequest> {
290
+ self.local_activity_data
291
+ .take_all_reqs(&self.workflow_type, &self.workflow_id, &self.run_id)
292
+ }
293
+
294
+ /// Returns the number of local activities we know we need to execute but have not yet finished
295
+ pub(crate) fn outstanding_local_activity_count(&self) -> usize {
296
+ self.local_activity_data.outstanding_la_count()
297
+ }
298
+
299
+ /// Returns the start attributes for the workflow if it has started
300
+ pub(crate) fn started_attrs(&self) -> Option<&WorkflowExecutionStartedEventAttributes> {
301
+ self.drive_me.get_started_attrs()
302
+ }
303
+
236
304
  /// Handle a single event from the workflow history. `has_next_event` should be false if `event`
237
305
  /// is the last event in the history.
238
306
  ///
239
307
  /// TODO: Describe what actually happens in here
240
308
  #[instrument(level = "debug", skip(self, event), fields(event=%event))]
241
- pub(crate) fn handle_event(
242
- &mut self,
243
- event: &HistoryEvent,
244
- has_next_event: bool,
245
- ) -> Result<()> {
309
+ fn handle_event(&mut self, event: &HistoryEvent, has_next_event: bool) -> Result<()> {
246
310
  if event.is_final_wf_execution_event() {
247
311
  self.have_seen_terminal_event = true;
248
312
  }
@@ -291,21 +355,24 @@ impl WorkflowMachines {
291
355
  /// Called when a workflow task started event has triggered. Ensures we are tracking the ID
292
356
  /// of the current started event as well as workflow time properly.
293
357
  fn task_started(&mut self, task_started_event_id: i64, time: SystemTime) -> Result<()> {
294
- let s = span!(Level::DEBUG, "Task started trigger");
295
- let _enter = s.enter();
296
-
297
- // TODO: Local activity machines
298
- // // Give local activities a chance to recreate their requests if they were lost due
299
- // // to the last workflow task failure. The loss could happen only the last workflow task
300
- // // was forcibly created by setting forceCreate on RespondWorkflowTaskCompletedRequest.
301
- // if (nonProcessedWorkflowTask) {
302
- // for (LocalActivityStateMachine value : localActivityMap.values()) {
303
- // value.nonReplayWorkflowTaskStarted();
304
- // }
305
- // }
306
-
307
358
  self.current_started_event_id = task_started_event_id;
359
+ self.wft_start_time = Some(time);
308
360
  self.set_current_time(time);
361
+
362
+ // Notify local activity machines that we started a non-replay WFT, which will allow any
363
+ // which were waiting for a marker to instead decide to execute the LA since it clearly
364
+ // will not be resolved via marker.
365
+ if !self.replaying {
366
+ let mut resps = vec![];
367
+ for (k, mach) in self.all_machines.iter_mut() {
368
+ if let Machines::LocalActivityMachine(lam) = mach {
369
+ resps.push((k, lam.encountered_non_replay_wft()?));
370
+ }
371
+ }
372
+ for (mkey, resp_set) in resps {
373
+ self.process_machine_responses(mkey, resp_set)?;
374
+ }
375
+ }
309
376
  Ok(())
310
377
  }
311
378
 
@@ -319,10 +386,25 @@ impl WorkflowMachines {
319
386
  /// with a state machine, which is then notified about the event and the command is removed from
320
387
  /// the commands queue.
321
388
  fn handle_command_event(&mut self, event: &HistoryEvent) -> Result<()> {
322
- // TODO: Local activity handling stuff
323
- // if (handleLocalActivityMarker(event)) {
324
- // return;
325
- // }
389
+ if event.is_local_activity_marker() {
390
+ let deets = event.extract_local_activity_marker_data().ok_or_else(|| {
391
+ WFMachinesError::Fatal(format!("Local activity marker was unparsable: {:?}", event))
392
+ })?;
393
+ let cmdid = CommandID::LocalActivity(deets.seq);
394
+ let mkey = self.get_machine_key(cmdid)?;
395
+ if let Machines::LocalActivityMachine(lam) = self.machine(mkey) {
396
+ if lam.marker_should_get_special_handling()? {
397
+ self.submachine_handle_event(mkey, event, false)?;
398
+ return Ok(());
399
+ }
400
+ } else {
401
+ return Err(WFMachinesError::Fatal(format!(
402
+ "Encountered local activity marker but the associated machine was of the \
403
+ wrong type! {:?}",
404
+ event
405
+ )));
406
+ }
407
+ }
326
408
 
327
409
  let consumed_cmd = loop {
328
410
  if let Some(peek_machine) = self.commands.front() {
@@ -347,19 +429,17 @@ impl WorkflowMachines {
347
429
  )));
348
430
  };
349
431
 
350
- // Feed the machine the event
351
432
  let canceled_before_sent = self
352
433
  .machine(command.machine)
353
434
  .was_cancelled_before_sent_to_server();
354
435
 
355
436
  if !canceled_before_sent {
437
+ // Feed the machine the event
356
438
  self.submachine_handle_event(command.machine, event, true)?;
357
439
  break command;
358
440
  }
359
441
  };
360
442
 
361
- // TODO: validate command
362
-
363
443
  if !self.machine(consumed_cmd.machine).is_final_state() {
364
444
  self.machines_by_event_id
365
445
  .insert(event.event_id, consumed_cmd.machine);
@@ -421,7 +501,7 @@ impl WorkflowMachines {
421
501
  }
422
502
  Some(EventType::WorkflowTaskScheduled) => {
423
503
  let wf_task_sm = WorkflowTaskMachine::new(self.next_started_event_id);
424
- let key = self.all_machines.insert(Box::new(wf_task_sm));
504
+ let key = self.all_machines.insert(wf_task_sm.into());
425
505
  self.submachine_handle_event(key, event, has_next_event)?;
426
506
  self.machines_by_event_id.insert(event.event_id, key);
427
507
  }
@@ -465,7 +545,10 @@ impl WorkflowMachines {
465
545
  .iter()
466
546
  .filter_map(|c| {
467
547
  if !self.machine(c.machine).is_final_state() {
468
- Some(c.command.clone())
548
+ match &c.command {
549
+ MachineAssociatedCommand::Real(cmd) => Some((**cmd).clone()),
550
+ MachineAssociatedCommand::FakeLocalActivityMarker(_) => None,
551
+ }
469
552
  } else {
470
553
  None
471
554
  }
@@ -479,9 +562,9 @@ impl WorkflowMachines {
479
562
  ///
480
563
  /// The job list may be empty, in which case it is expected the caller handles what to do in a
481
564
  /// "no work" situation. Possibly, it may know about some work the machines don't, like queries.
482
- pub(crate) fn get_wf_activation(&mut self) -> WfActivation {
565
+ pub(crate) fn get_wf_activation(&mut self) -> WorkflowActivation {
483
566
  let jobs = self.drive_me.drain_jobs();
484
- WfActivation {
567
+ WorkflowActivation {
485
568
  timestamp: self.current_wf_time.map(Into::into),
486
569
  is_replaying: self.replaying,
487
570
  run_id: self.run_id.clone(),
@@ -489,6 +572,10 @@ impl WorkflowMachines {
489
572
  }
490
573
  }
491
574
 
575
+ pub(crate) fn has_pending_jobs(&self) -> bool {
576
+ self.drive_me.has_pending_jobs()
577
+ }
578
+
492
579
  fn set_current_time(&mut self, time: SystemTime) -> SystemTime {
493
580
  if self.current_wf_time.map_or(true, |t| t < time) {
494
581
  self.current_wf_time = Some(time);
@@ -499,14 +586,9 @@ impl WorkflowMachines {
499
586
 
500
587
  /// Iterate the state machines, which consists of grabbing any pending outgoing commands from
501
588
  /// the workflow code, handling them, and preparing them to be sent off to the server.
502
- ///
503
- /// Returns a boolean flag which indicates whether or not new activations were produced by the
504
- /// state machine. If true, pending activation should be created by the caller making jobs
505
- /// available to the lang side.
506
- pub(crate) async fn iterate_machines(&mut self) -> Result<bool> {
589
+ pub(crate) async fn iterate_machines(&mut self) -> Result<()> {
507
590
  let results = self.drive_me.fetch_workflow_iteration_output().await;
508
591
  let jobs = self.handle_driven_results(results)?;
509
- let has_new_lang_jobs = !jobs.is_empty();
510
592
  for job in jobs {
511
593
  self.drive_me.send_job(job);
512
594
  }
@@ -516,12 +598,12 @@ impl WorkflowMachines {
516
598
  self.metrics.wf_e2e_latency(rt);
517
599
  }
518
600
  }
519
- Ok(has_new_lang_jobs)
601
+ Ok(())
520
602
  }
521
603
 
522
604
  /// Apply the next (unapplied) entire workflow task from history to these machines. Will replay
523
605
  /// any events that need to be replayed until caught up to the newest WFT.
524
- pub(crate) async fn apply_next_wft_from_history(&mut self) -> Result<()> {
606
+ pub(crate) async fn apply_next_wft_from_history(&mut self) -> Result<usize> {
525
607
  // A much higher-up span (ex: poll) may want this field filled
526
608
  tracing::Span::current().record("run_id", &self.run_id.as_str());
527
609
 
@@ -529,7 +611,7 @@ impl WorkflowMachines {
529
611
  // then we don't need to do anything here, and in fact we need to avoid re-applying the
530
612
  // final WFT.
531
613
  if self.have_seen_terminal_event {
532
- return Ok(());
614
+ return Ok(0);
533
615
  }
534
616
 
535
617
  let last_handled_wft_started_id = self.current_started_event_id;
@@ -543,6 +625,7 @@ impl WorkflowMachines {
543
625
  evts.retain(|e| e.event_id > self.last_processed_event);
544
626
  evts
545
627
  };
628
+ let num_events_to_process = events.len();
546
629
 
547
630
  // We're caught up on reply if there are no new events to process
548
631
  // TODO: Probably this is unneeded if we evict whenever history is from non-sticky queue
@@ -575,12 +658,15 @@ impl WorkflowMachines {
575
658
  let next_event = history.peek();
576
659
  self.handle_event(event, next_event.is_some())?;
577
660
  self.last_processed_event = event.event_id;
661
+ if event.event_type == EventType::WorkflowTaskStarted as i32 && next_event.is_none() {
662
+ break;
663
+ }
578
664
  }
579
665
 
580
666
  // Scan through to the next WFT, searching for any patch markers, so that we can
581
667
  // pre-resolve them.
582
668
  for e in self.last_history_from_server.peek_next_wft_sequence() {
583
- if let Some((patch_id, _)) = e.get_changed_marker_details() {
669
+ if let Some((patch_id, _)) = e.get_patch_marker_details() {
584
670
  self.encountered_change_markers.insert(
585
671
  patch_id.clone(),
586
672
  ChangeInfo {
@@ -589,9 +675,11 @@ impl WorkflowMachines {
589
675
  );
590
676
  // Found a patch marker
591
677
  self.drive_me
592
- .send_job(wf_activation_job::Variant::NotifyHasPatch(NotifyHasPatch {
593
- patch_id,
594
- }));
678
+ .send_job(workflow_activation_job::Variant::NotifyHasPatch(
679
+ NotifyHasPatch { patch_id },
680
+ ));
681
+ } else if e.is_local_activity_marker() {
682
+ self.local_activity_data.process_peekahead_marker(e)?;
595
683
  }
596
684
  }
597
685
 
@@ -599,7 +687,7 @@ impl WorkflowMachines {
599
687
  self.metrics.wf_task_replay_latency(replay_start.elapsed());
600
688
  }
601
689
 
602
- Ok(())
690
+ Ok(num_events_to_process)
603
691
  }
604
692
 
605
693
  /// Wrapper for calling [TemporalStateMachine::handle_event] which appropriately takes action
@@ -625,10 +713,15 @@ impl WorkflowMachines {
625
713
  .machine(c.machine)
626
714
  .was_cancelled_before_sent_to_server()
627
715
  {
628
- let machine_responses = self
629
- .machine_mut(c.machine)
630
- .handle_command(c.command.command_type())?;
631
- self.process_machine_responses(c.machine, machine_responses)?;
716
+ match &c.command {
717
+ MachineAssociatedCommand::Real(cmd) => {
718
+ let machine_responses = self
719
+ .machine_mut(c.machine)
720
+ .handle_command(cmd.command_type())?;
721
+ self.process_machine_responses(c.machine, machine_responses)?;
722
+ }
723
+ MachineAssociatedCommand::FakeLocalActivityMarker(_) => {}
724
+ }
632
725
  self.commands.push_back(c);
633
726
  }
634
727
  }
@@ -640,10 +733,10 @@ impl WorkflowMachines {
640
733
  /// this function uses to drive sending jobs to lang, triggering new workflow tasks, etc.
641
734
  fn process_machine_responses(
642
735
  &mut self,
643
- sm: MachineKey,
736
+ smk: MachineKey,
644
737
  machine_responses: Vec<MachineResponse>,
645
738
  ) -> Result<()> {
646
- let sm = self.machine_mut(sm);
739
+ let sm = self.machine(smk);
647
740
  if !machine_responses.is_empty() {
648
741
  debug!(responses = %machine_responses.display(), machine_name = %sm.kind(),
649
742
  "Machine produced responses");
@@ -663,14 +756,40 @@ impl WorkflowMachines {
663
756
  // TODO: Should this also update self.run_id? Should we track orig/current
664
757
  // separately?
665
758
  self.drive_me
666
- .send_job(wf_activation_job::Variant::UpdateRandomSeed(
759
+ .send_job(workflow_activation_job::Variant::UpdateRandomSeed(
667
760
  UpdateRandomSeed {
668
761
  randomness_seed: str_to_randomness_seed(&new_run_id),
669
762
  },
670
763
  ));
671
764
  }
672
- MachineResponse::IssueNewCommand(_) => {
673
- panic!("Issue new command machine response not expected here")
765
+ MachineResponse::IssueNewCommand(c) => {
766
+ self.current_wf_task_commands.push_back(CommandAndMachine {
767
+ command: MachineAssociatedCommand::Real(Box::new(c)),
768
+ machine: smk,
769
+ })
770
+ }
771
+ MachineResponse::IssueFakeLocalActivityMarker(seq) => {
772
+ self.current_wf_task_commands.push_back(CommandAndMachine {
773
+ command: MachineAssociatedCommand::FakeLocalActivityMarker(seq),
774
+ machine: smk,
775
+ });
776
+ }
777
+ MachineResponse::QueueLocalActivity(act) => {
778
+ self.local_activity_data.enqueue(act);
779
+ }
780
+ MachineResponse::RequestCancelLocalActivity(_) => {
781
+ panic!(
782
+ "Request cancel local activity should not be returned from \
783
+ anything other than explicit cancellation"
784
+ )
785
+ }
786
+ MachineResponse::AbandonLocalActivity(seq) => {
787
+ self.local_activity_data.done_executing(seq);
788
+ }
789
+ MachineResponse::UpdateWFTime(t) => {
790
+ if let Some(t) = t {
791
+ self.set_current_time(t);
792
+ }
674
793
  }
675
794
  }
676
795
  }
@@ -686,30 +805,53 @@ impl WorkflowMachines {
686
805
  fn handle_driven_results(
687
806
  &mut self,
688
807
  results: Vec<WFCommand>,
689
- ) -> Result<Vec<wf_activation_job::Variant>> {
808
+ ) -> Result<Vec<workflow_activation_job::Variant>> {
690
809
  let mut jobs = vec![];
691
810
  for cmd in results {
692
811
  match cmd {
693
812
  WFCommand::AddTimer(attrs) => {
694
813
  let seq = attrs.seq;
695
- let timer = self.add_new_command_machine(new_timer(attrs));
696
- self.id_to_machine
697
- .insert(CommandID::Timer(seq), timer.machine);
698
- self.current_wf_task_commands.push_back(timer);
814
+ self.add_cmd_to_wf_task(new_timer(attrs), Some(CommandID::Timer(seq)));
699
815
  }
700
816
  WFCommand::CancelTimer(attrs) => {
701
817
  jobs.extend(self.process_cancellation(CommandID::Timer(attrs.seq))?);
702
818
  }
703
819
  WFCommand::AddActivity(attrs) => {
704
820
  let seq = attrs.seq;
705
- let activity = self.add_new_command_machine(new_activity(attrs));
821
+ self.add_cmd_to_wf_task(new_activity(attrs), Some(CommandID::Activity(seq)));
822
+ }
823
+ WFCommand::AddLocalActivity(attrs) => {
824
+ let seq = attrs.seq;
825
+ let attrs: ValidScheduleLA = ValidScheduleLA::from_schedule_la(
826
+ attrs,
827
+ self.started_attrs()
828
+ .as_ref()
829
+ .map(|x| x.workflow_execution_timeout.clone().try_into_or_none())
830
+ .flatten(),
831
+ )
832
+ .map_err(|e| {
833
+ WFMachinesError::Fatal(format!(
834
+ "Invalid schedule local activity request (seq {}): {}",
835
+ seq, e
836
+ ))
837
+ })?;
838
+ let (la, mach_resp) = new_local_activity(
839
+ attrs,
840
+ self.replaying,
841
+ self.local_activity_data.take_preresolution(seq),
842
+ self.current_wf_time,
843
+ )?;
844
+ let machkey = self.all_machines.insert(la.into());
706
845
  self.id_to_machine
707
- .insert(CommandID::Activity(seq), activity.machine);
708
- self.current_wf_task_commands.push_back(activity);
846
+ .insert(CommandID::LocalActivity(seq), machkey);
847
+ self.process_machine_responses(machkey, mach_resp)?;
709
848
  }
710
849
  WFCommand::RequestCancelActivity(attrs) => {
711
850
  jobs.extend(self.process_cancellation(CommandID::Activity(attrs.seq))?);
712
851
  }
852
+ WFCommand::RequestCancelLocalActivity(attrs) => {
853
+ jobs.extend(self.process_cancellation(CommandID::LocalActivity(attrs.seq))?);
854
+ }
713
855
  WFCommand::CompleteWorkflow(attrs) => {
714
856
  self.metrics.wf_completed();
715
857
  self.add_terminal_command(complete_workflow(attrs));
@@ -730,15 +872,12 @@ impl WorkflowMachines {
730
872
  // Do not create commands for change IDs that we have already created commands
731
873
  // for.
732
874
  if !matches!(self.encountered_change_markers.get(&attrs.patch_id),
733
- Some(ChangeInfo {created_command, ..})
734
- if *created_command)
875
+ Some(ChangeInfo {created_command}) if *created_command)
735
876
  {
736
- let verm = self.add_new_command_machine(has_change(
737
- attrs.patch_id.clone(),
738
- self.replaying,
739
- attrs.deprecated,
740
- ));
741
- self.current_wf_task_commands.push_back(verm);
877
+ self.add_cmd_to_wf_task(
878
+ has_change(attrs.patch_id.clone(), self.replaying, attrs.deprecated),
879
+ None,
880
+ );
742
881
 
743
882
  if let Some(ci) = self.encountered_change_markers.get_mut(&attrs.patch_id) {
744
883
  ci.created_command = true;
@@ -754,10 +893,10 @@ impl WorkflowMachines {
754
893
  }
755
894
  WFCommand::AddChildWorkflow(attrs) => {
756
895
  let seq = attrs.seq;
757
- let child_workflow = self.add_new_command_machine(new_child_workflow(attrs));
758
- self.id_to_machine
759
- .insert(CommandID::ChildWorkflowStart(seq), child_workflow.machine);
760
- self.current_wf_task_commands.push_back(child_workflow);
896
+ self.add_cmd_to_wf_task(
897
+ new_child_workflow(attrs),
898
+ Some(CommandID::ChildWorkflowStart(seq)),
899
+ );
761
900
  }
762
901
  WFCommand::CancelUnstartedChild(attrs) => jobs.extend(self.process_cancellation(
763
902
  CommandID::ChildWorkflowStart(attrs.child_workflow_seq),
@@ -780,11 +919,10 @@ impl WorkflowMachines {
780
919
  ),
781
920
  Some(cancel_we::Target::WorkflowExecution(we)) => (we, false),
782
921
  };
783
- let mach = self
784
- .add_new_command_machine(new_external_cancel(attrs.seq, we, only_child));
785
- self.id_to_machine
786
- .insert(CommandID::CancelExternal(attrs.seq), mach.machine);
787
- self.current_wf_task_commands.push_back(mach);
922
+ self.add_cmd_to_wf_task(
923
+ new_external_cancel(attrs.seq, we, only_child),
924
+ Some(CommandID::CancelExternal(attrs.seq)),
925
+ );
788
926
  }
789
927
  WFCommand::SignalExternalWorkflow(attrs) => {
790
928
  let (we, only_child) = match attrs.target {
@@ -805,16 +943,16 @@ impl WorkflowMachines {
805
943
  Some(sig_we::Target::WorkflowExecution(we)) => (we, false),
806
944
  };
807
945
 
808
- let sigm = self.add_new_command_machine(new_external_signal(
809
- attrs.seq,
810
- we,
811
- attrs.signal_name,
812
- attrs.args,
813
- only_child,
814
- ));
815
- self.id_to_machine
816
- .insert(CommandID::SignalExternal(attrs.seq), sigm.machine);
817
- self.current_wf_task_commands.push_back(sigm);
946
+ self.add_cmd_to_wf_task(
947
+ new_external_signal(
948
+ attrs.seq,
949
+ we,
950
+ attrs.signal_name,
951
+ attrs.args,
952
+ only_child,
953
+ ),
954
+ Some(CommandID::SignalExternal(attrs.seq)),
955
+ );
818
956
  }
819
957
  WFCommand::CancelSignalWorkflow(attrs) => {
820
958
  jobs.extend(self.process_cancellation(CommandID::SignalExternal(attrs.seq))?);
@@ -834,19 +972,62 @@ impl WorkflowMachines {
834
972
  fn process_cancellation(&mut self, id: CommandID) -> Result<Vec<Variant>> {
835
973
  let mut jobs = vec![];
836
974
  let m_key = self.get_machine_key(id)?;
837
- let res = self.machine_mut(m_key).cancel()?;
838
- debug!(machine_responses = ?res, cmd_id = ?id, "Cancel request responses");
839
- for r in res {
975
+ let machine_resps = self.machine_mut(m_key).cancel()?;
976
+ debug!(machine_responses = ?machine_resps, cmd_id = ?id, "Cancel request responses");
977
+ for r in machine_resps {
840
978
  match r {
841
979
  MachineResponse::IssueNewCommand(c) => {
842
980
  self.current_wf_task_commands.push_back(CommandAndMachine {
843
- command: c,
981
+ command: MachineAssociatedCommand::Real(Box::new(c)),
844
982
  machine: m_key,
845
983
  });
846
984
  }
847
985
  MachineResponse::PushWFJob(j) => {
848
986
  jobs.push(j);
849
987
  }
988
+ MachineResponse::RequestCancelLocalActivity(seq) => {
989
+ // We might already know about the status from a pre-resolution. Apply it if so.
990
+ // We need to do this because otherwise we might need to perform additional
991
+ // activations during replay that didn't happen during execution, just like
992
+ // we sometimes pre-resolve activities when first requested.
993
+ if let Some(preres) = self.local_activity_data.take_preresolution(seq) {
994
+ if let Machines::LocalActivityMachine(lam) = self.machine_mut(m_key) {
995
+ let more_responses = lam.try_resolve_with_dat(preres)?;
996
+ self.process_machine_responses(m_key, more_responses)?;
997
+ } else {
998
+ panic!("A non local-activity machine returned a request cancel LA response");
999
+ }
1000
+ }
1001
+ // If it's in the request queue, just rip it out.
1002
+ else if let Some(removed_act) =
1003
+ self.local_activity_data.remove_from_queue(seq)
1004
+ {
1005
+ // We removed it. Notify the machine that the activity cancelled.
1006
+ if let Machines::LocalActivityMachine(lam) = self.machine_mut(m_key) {
1007
+ let more_responses = lam.try_resolve(
1008
+ LocalActivityExecutionResult::empty_cancel(),
1009
+ Duration::from_secs(0),
1010
+ removed_act.attempt,
1011
+ None,
1012
+ None,
1013
+ )?;
1014
+ self.process_machine_responses(m_key, more_responses)?;
1015
+ } else {
1016
+ panic!("A non local-activity machine returned a request cancel LA response");
1017
+ }
1018
+ } else {
1019
+ // Finally, if we know about the LA at all, it's currently running, so
1020
+ // queue the cancel request to be given to the LA manager.
1021
+ self.local_activity_data.enqueue_cancel(ExecutingLAId {
1022
+ run_id: self.run_id.clone(),
1023
+ seq_num: seq,
1024
+ });
1025
+ }
1026
+ }
1027
+ MachineResponse::AbandonLocalActivity(seq) => {
1028
+ self.local_activity_data.done_executing(seq);
1029
+ }
1030
+ MachineResponse::UpdateWFTime(None) => {}
850
1031
  v => {
851
1032
  return Err(WFMachinesError::Fatal(format!(
852
1033
  "Unexpected machine response {:?} when cancelling {:?}",
@@ -864,34 +1045,37 @@ impl WorkflowMachines {
864
1045
  })?)
865
1046
  }
866
1047
 
867
- fn add_terminal_command<T: TemporalStateMachine + 'static>(
868
- &mut self,
869
- machine: NewMachineWithCommand<T>,
870
- ) {
1048
+ fn add_terminal_command(&mut self, machine: NewMachineWithCommand) {
871
1049
  let cwfm = self.add_new_command_machine(machine);
872
1050
  self.workflow_end_time = Some(SystemTime::now());
873
1051
  self.current_wf_task_commands.push_back(cwfm);
874
1052
  }
875
1053
 
876
- fn add_new_command_machine<T: TemporalStateMachine + 'static>(
877
- &mut self,
878
- machine: NewMachineWithCommand<T>,
879
- ) -> CommandAndMachine {
880
- let k = self.all_machines.insert(Box::new(machine.machine));
1054
+ /// Add a new command/machines for that command to the current workflow task
1055
+ fn add_cmd_to_wf_task(&mut self, machine: NewMachineWithCommand, id: Option<CommandID>) {
1056
+ let mach = self.add_new_command_machine(machine);
1057
+ if let Some(id) = id {
1058
+ self.id_to_machine.insert(id, mach.machine);
1059
+ }
1060
+ self.current_wf_task_commands.push_back(mach);
1061
+ }
1062
+
1063
+ fn add_new_command_machine(&mut self, machine: NewMachineWithCommand) -> CommandAndMachine {
1064
+ let k = self.all_machines.insert(machine.machine);
881
1065
  CommandAndMachine {
882
- command: machine.command,
1066
+ command: MachineAssociatedCommand::Real(Box::new(machine.command)),
883
1067
  machine: k,
884
1068
  }
885
1069
  }
886
1070
 
887
- fn machine(&self, m: MachineKey) -> &dyn TemporalStateMachine {
1071
+ fn machine(&self, m: MachineKey) -> &Machines {
888
1072
  self.all_machines
889
1073
  .get(m)
890
1074
  .expect("Machine must exist")
891
1075
  .borrow()
892
1076
  }
893
1077
 
894
- fn machine_mut(&mut self, m: MachineKey) -> &mut (dyn TemporalStateMachine + 'static) {
1078
+ fn machine_mut(&mut self, m: MachineKey) -> &mut Machines {
895
1079
  self.all_machines
896
1080
  .get_mut(m)
897
1081
  .expect("Machine must exist")
@@ -919,23 +1103,23 @@ fn change_marker_handling(
919
1103
  ) -> Result<ChangeMarkerOutcome> {
920
1104
  if !mach.matches_event(event) {
921
1105
  // Version markers can be skipped in the event they are deprecated
922
- if let Some(changed_info) = event.get_changed_marker_details() {
1106
+ if let Some((patch_name, deprecated)) = event.get_patch_marker_details() {
923
1107
  // Is deprecated. We can simply ignore this event, as deprecated change
924
1108
  // markers are allowed without matching changed calls.
925
- if changed_info.1 {
1109
+ if deprecated {
926
1110
  debug!("Deprecated patch marker tried against wrong machine, skipping.");
927
1111
  return Ok(ChangeMarkerOutcome::SkipEvent);
928
1112
  }
929
1113
  return Err(WFMachinesError::Nondeterminism(format!(
930
1114
  "Non-deprecated patch marker encountered for change {}, \
931
1115
  but there is no corresponding change command!",
932
- changed_info.0
1116
+ patch_name
933
1117
  )));
934
1118
  }
935
1119
  // Version machines themselves may also not *have* matching markers, where non-deprecated
936
1120
  // calls take the old path, and deprecated calls assume history is produced by a new-code
937
1121
  // worker.
938
- if mach.kind() == MachineKind::Version {
1122
+ if mach.kind() == MachineKind::Patch {
939
1123
  debug!("Skipping non-matching event against version machine");
940
1124
  return Ok(ChangeMarkerOutcome::SkipCommand);
941
1125
  }