@temporalio/core-bridge 0.19.2 → 0.20.2

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 (125) hide show
  1. package/Cargo.lock +90 -157
  2. package/Cargo.toml +1 -0
  3. package/index.d.ts +11 -27
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.buildkite/docker/Dockerfile +1 -1
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.cargo/config.toml +1 -0
  13. package/sdk-core/CODEOWNERS +1 -1
  14. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +119 -86
  15. package/sdk-core/bridge-ffi/src/lib.rs +311 -315
  16. package/sdk-core/bridge-ffi/src/wrappers.rs +108 -113
  17. package/sdk-core/client/Cargo.toml +13 -9
  18. package/sdk-core/client/LICENSE.txt +23 -0
  19. package/sdk-core/client/src/lib.rs +286 -174
  20. package/sdk-core/client/src/metrics.rs +86 -12
  21. package/sdk-core/client/src/raw.rs +566 -0
  22. package/sdk-core/client/src/retry.rs +137 -99
  23. package/sdk-core/core/Cargo.toml +15 -10
  24. package/sdk-core/core/LICENSE.txt +23 -0
  25. package/sdk-core/core/benches/workflow_replay.rs +79 -0
  26. package/sdk-core/core/src/abstractions.rs +38 -0
  27. package/sdk-core/core/src/core_tests/activity_tasks.rs +108 -182
  28. package/sdk-core/core/src/core_tests/child_workflows.rs +16 -11
  29. package/sdk-core/core/src/core_tests/determinism.rs +24 -12
  30. package/sdk-core/core/src/core_tests/local_activities.rs +53 -27
  31. package/sdk-core/core/src/core_tests/mod.rs +30 -43
  32. package/sdk-core/core/src/core_tests/queries.rs +82 -81
  33. package/sdk-core/core/src/core_tests/workers.rs +111 -296
  34. package/sdk-core/core/src/core_tests/workflow_cancels.rs +4 -4
  35. package/sdk-core/core/src/core_tests/workflow_tasks.rs +257 -242
  36. package/sdk-core/core/src/lib.rs +73 -318
  37. package/sdk-core/core/src/pollers/mod.rs +4 -6
  38. package/sdk-core/core/src/pollers/poll_buffer.rs +20 -14
  39. package/sdk-core/core/src/protosext/mod.rs +7 -10
  40. package/sdk-core/core/src/replay/mod.rs +11 -150
  41. package/sdk-core/core/src/telemetry/metrics.rs +35 -2
  42. package/sdk-core/core/src/telemetry/mod.rs +49 -16
  43. package/sdk-core/core/src/telemetry/prometheus_server.rs +14 -35
  44. package/sdk-core/core/src/test_help/mod.rs +104 -170
  45. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +57 -34
  46. package/sdk-core/core/src/worker/activities/local_activities.rs +95 -23
  47. package/sdk-core/core/src/worker/activities.rs +23 -16
  48. package/sdk-core/core/src/worker/client/mocks.rs +86 -0
  49. package/sdk-core/core/src/worker/client.rs +209 -0
  50. package/sdk-core/core/src/worker/mod.rs +207 -108
  51. package/sdk-core/core/src/workflow/driven_workflow.rs +21 -6
  52. package/sdk-core/core/src/workflow/history_update.rs +107 -24
  53. package/sdk-core/core/src/workflow/machines/activity_state_machine.rs +2 -3
  54. package/sdk-core/core/src/workflow/machines/child_workflow_state_machine.rs +2 -3
  55. package/sdk-core/core/src/workflow/machines/mod.rs +20 -17
  56. package/sdk-core/core/src/workflow/machines/signal_external_state_machine.rs +56 -19
  57. package/sdk-core/core/src/workflow/machines/transition_coverage.rs +5 -0
  58. package/sdk-core/core/src/workflow/machines/upsert_search_attributes_state_machine.rs +230 -22
  59. package/sdk-core/core/src/workflow/machines/workflow_machines.rs +81 -115
  60. package/sdk-core/core/src/workflow/machines/workflow_task_state_machine.rs +4 -4
  61. package/sdk-core/core/src/workflow/mod.rs +13 -1
  62. package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +70 -11
  63. package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +65 -41
  64. package/sdk-core/core-api/Cargo.toml +9 -1
  65. package/sdk-core/core-api/LICENSE.txt +23 -0
  66. package/sdk-core/core-api/src/errors.rs +7 -38
  67. package/sdk-core/core-api/src/lib.rs +44 -52
  68. package/sdk-core/core-api/src/worker.rs +10 -2
  69. package/sdk-core/etc/deps.svg +127 -96
  70. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -7
  71. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +10 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +6 -1
  73. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +6 -0
  74. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +6 -0
  75. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +2 -1
  76. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +3 -0
  77. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +12 -0
  78. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +25 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -0
  80. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +19 -35
  81. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +2 -6
  82. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +53 -11
  83. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +14 -7
  84. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -5
  85. package/sdk-core/sdk/Cargo.toml +16 -2
  86. package/sdk-core/sdk/LICENSE.txt +23 -0
  87. package/sdk-core/sdk/src/interceptors.rs +11 -0
  88. package/sdk-core/sdk/src/lib.rs +139 -151
  89. package/sdk-core/sdk/src/workflow_context/options.rs +86 -1
  90. package/sdk-core/sdk/src/workflow_context.rs +36 -17
  91. package/sdk-core/sdk/src/workflow_future.rs +19 -25
  92. package/sdk-core/sdk-core-protos/Cargo.toml +1 -1
  93. package/sdk-core/sdk-core-protos/build.rs +1 -0
  94. package/sdk-core/sdk-core-protos/src/history_info.rs +17 -4
  95. package/sdk-core/sdk-core-protos/src/lib.rs +251 -47
  96. package/sdk-core/test-utils/Cargo.toml +3 -1
  97. package/sdk-core/test-utils/src/canned_histories.rs +27 -0
  98. package/sdk-core/test-utils/src/histfetch.rs +3 -3
  99. package/sdk-core/test-utils/src/lib.rs +223 -68
  100. package/sdk-core/tests/integ_tests/client_tests.rs +27 -4
  101. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +93 -14
  102. package/sdk-core/tests/integ_tests/polling_tests.rs +18 -12
  103. package/sdk-core/tests/integ_tests/queries_tests.rs +50 -53
  104. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +117 -103
  105. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +8 -1
  106. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +10 -5
  107. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +7 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +32 -9
  109. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +7 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +76 -15
  111. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +19 -3
  112. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +39 -42
  113. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +84 -0
  114. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +30 -8
  115. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +21 -6
  116. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +26 -16
  117. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +66 -0
  118. package/sdk-core/tests/integ_tests/workflow_tests.rs +78 -74
  119. package/sdk-core/tests/load_tests.rs +9 -6
  120. package/sdk-core/tests/main.rs +43 -10
  121. package/src/conversions.rs +7 -12
  122. package/src/lib.rs +322 -357
  123. package/sdk-core/client/src/mocks.rs +0 -167
  124. package/sdk-core/core/src/worker/dispatcher.rs +0 -171
  125. package/sdk-core/protos/local/temporal/sdk/core/bridge/service.proto +0 -61
@@ -1,40 +1,248 @@
1
+ use super::{
2
+ workflow_machines::{MachineResponse, WFMachinesError},
3
+ NewMachineWithCommand,
4
+ };
5
+ use crate::workflow::machines::{Cancellable, EventInfo, MachineKind, WFMachinesAdapter};
1
6
  use rustfsm::{fsm, TransitionResult};
7
+ use temporal_sdk_core_protos::{
8
+ coresdk::workflow_commands::UpsertWorkflowSearchAttributes,
9
+ temporal::api::{
10
+ command::v1::Command,
11
+ enums::v1::{CommandType, EventType},
12
+ history::v1::HistoryEvent,
13
+ },
14
+ };
2
15
 
3
16
  fsm! {
4
- pub(super) name UpsertSearchAttributesMachine; command UpsertSearchAttributesCommand; error UpsertSearchAttributesMachineError;
17
+ pub(super) name UpsertSearchAttributesMachine;
18
+ command UpsertSearchAttributesMachineCommand;
19
+ error WFMachinesError;
20
+ shared_state SharedState;
5
21
 
6
- Created --(Schedule, on_schedule) --> UpsertCommandCreated;
22
+ // Machine is instantiated into the Created state and then transitions into the CommandIssued
23
+ // state when it receives the CommandScheduled event that is a result of looping back the
24
+ // Command initially packaged with NewMachineWithCommand (see upsert_search_attrs)
25
+ Created --(CommandScheduled) --> CommandIssued;
7
26
 
8
- UpsertCommandCreated --(CommandUpsertWorkflowSearchAttributes) --> UpsertCommandCreated;
9
- UpsertCommandCreated --(UpsertWorkflowSearchAttributes, on_upsert_workflow_search_attributes) --> UpsertCommandRecorded;
27
+ // Having sent the command to the server, the machine transitions into a terminal state (Done)
28
+ // upon observing a history event indicating that the command has been recorded. Note that this
29
+ // does not imply that the command has been _executed_, only that it _will be_ executed at some
30
+ // point in the future.
31
+ CommandIssued --(CommandRecorded) --> Done;
10
32
  }
11
33
 
12
- #[derive(thiserror::Error, Debug)]
13
- pub(super) enum UpsertSearchAttributesMachineError {}
34
+ /// Instantiates an UpsertSearchAttributesMachine and packs it together with an initial command
35
+ /// to apply the provided search attribute update.
36
+ pub(super) fn upsert_search_attrs(
37
+ attribs: UpsertWorkflowSearchAttributes,
38
+ ) -> NewMachineWithCommand {
39
+ let sm = UpsertSearchAttributesMachine::new();
40
+ let cmd = Command {
41
+ command_type: CommandType::UpsertWorkflowSearchAttributes as i32,
42
+ attributes: Some(attribs.into()),
43
+ };
44
+ NewMachineWithCommand {
45
+ command: cmd,
46
+ machine: sm.into(),
47
+ }
48
+ }
49
+
50
+ /// Unused but must exist
51
+ type SharedState = ();
14
52
 
15
- pub(super) enum UpsertSearchAttributesCommand {}
53
+ /// The state-machine-specific set of commands that are the results of state transition in the
54
+ /// UpsertSearchAttributesMachine. There are none of these because this state machine emits the
55
+ /// UpsertSearchAttributes API command during construction and then does not emit any subsequent
56
+ /// state-machine specific commands.
57
+ #[derive(Debug, derive_more::Display)]
58
+ pub(super) enum UpsertSearchAttributesMachineCommand {}
16
59
 
17
- #[derive(Default, Clone)]
60
+ /// The state of the UpsertSearchAttributesMachine at time zero (i.e. at instantiation)
61
+ #[derive(Debug, Default, Clone, derive_more::Display)]
18
62
  pub(super) struct Created {}
19
63
 
20
- impl Created {
21
- pub(super) fn on_schedule(
22
- self,
23
- ) -> UpsertSearchAttributesMachineTransition<UpsertCommandCreated> {
24
- unimplemented!()
64
+ /// Once the UpsertSearchAttributesMachine has been known to have issued the upsert command to
65
+ /// higher-level machinery, it transitions into this state.
66
+ #[derive(Debug, Default, Clone, derive_more::Display)]
67
+ pub(super) struct CommandIssued {}
68
+
69
+ /// Once the server has recorded its receipt of the search attribute update, the
70
+ /// UpsertSearchAttributesMachine transitions into this terminal state.
71
+ #[derive(Debug, Default, Clone, derive_more::Display)]
72
+ pub(super) struct Done {}
73
+
74
+ impl UpsertSearchAttributesMachine {
75
+ fn new() -> Self {
76
+ Self {
77
+ state: Created {}.into(),
78
+ shared_state: (),
79
+ }
80
+ }
81
+ }
82
+
83
+ impl WFMachinesAdapter for UpsertSearchAttributesMachine {
84
+ /// Transforms an UpsertSearchAttributesMachine-specific command (i.e. an instance of the type
85
+ /// UpsertSearchAttributesMachineCommand) to a more generic form supported by the abstract
86
+ /// StateMachine type.
87
+ fn adapt_response(
88
+ &self,
89
+ _my_command: Self::Command,
90
+ _event_info: Option<EventInfo>,
91
+ ) -> Result<Vec<MachineResponse>, Self::Error> {
92
+ // No implementation needed until this state machine emits state machine commands
93
+ Err(Self::Error::Nondeterminism(
94
+ "UpsertWorkflowSearchAttributesMachine does not use commands".to_string(),
95
+ ))
96
+ }
97
+
98
+ /// Filters for EventType::UpsertWorkflowSearchAttributes
99
+ fn matches_event(&self, event: &HistoryEvent) -> bool {
100
+ matches!(
101
+ event.event_type(),
102
+ EventType::UpsertWorkflowSearchAttributes
103
+ )
104
+ }
105
+
106
+ fn kind(&self) -> MachineKind {
107
+ MachineKind::UpsertSearchAttributes
108
+ }
109
+ }
110
+
111
+ impl Cancellable for UpsertSearchAttributesMachine {}
112
+
113
+ // Converts the generic history event with type EventType::UpsertWorkflowSearchAttributes into the
114
+ // UpsertSearchAttributesMachine-specific event type
115
+ // UpsertSearchAttributesMachineEvents::CommandRecorded.
116
+ impl TryFrom<HistoryEvent> for UpsertSearchAttributesMachineEvents {
117
+ type Error = WFMachinesError;
118
+
119
+ fn try_from(e: HistoryEvent) -> Result<Self, Self::Error> {
120
+ match e.event_type() {
121
+ EventType::UpsertWorkflowSearchAttributes => {
122
+ Ok(UpsertSearchAttributesMachineEvents::CommandRecorded)
123
+ }
124
+ _ => Err(Self::Error::Nondeterminism(format!(
125
+ "UpsertWorkflowSearchAttributesMachine does not handle {e}"
126
+ ))),
127
+ }
128
+ }
129
+ }
130
+
131
+ // Converts generic state machine command type CommandType::UpsertWorkflowSearchAttributes into
132
+ // the UpsertSearchAttributesMachine-specific event
133
+ impl TryFrom<CommandType> for UpsertSearchAttributesMachineEvents {
134
+ type Error = WFMachinesError;
135
+
136
+ fn try_from(c: CommandType) -> Result<Self, Self::Error> {
137
+ match c {
138
+ CommandType::UpsertWorkflowSearchAttributes => {
139
+ Ok(UpsertSearchAttributesMachineEvents::CommandScheduled)
140
+ }
141
+ _ => Err(Self::Error::Nondeterminism(format!(
142
+ "UpsertWorkflowSearchAttributesMachine does not handle command type {c:?}"
143
+ ))),
144
+ }
25
145
  }
26
146
  }
27
147
 
28
- #[derive(Default, Clone)]
29
- pub(super) struct UpsertCommandCreated {}
148
+ // There is no Command/Response associated with this transition
149
+ impl From<CommandIssued> for Done {
150
+ fn from(_: CommandIssued) -> Self {
151
+ Self {}
152
+ }
153
+ }
30
154
 
31
- impl UpsertCommandCreated {
32
- pub(super) fn on_upsert_workflow_search_attributes(
33
- self,
34
- ) -> UpsertSearchAttributesMachineTransition<UpsertCommandRecorded> {
35
- unimplemented!()
155
+ // There is no Command/Response associated with this transition
156
+ impl From<Created> for CommandIssued {
157
+ fn from(_: Created) -> Self {
158
+ Self {}
36
159
  }
37
160
  }
38
161
 
39
- #[derive(Default, Clone)]
40
- pub(super) struct UpsertCommandRecorded {}
162
+ #[cfg(test)]
163
+ mod tests {
164
+ use rustfsm::StateMachine;
165
+ use temporal_sdk::{WfContext, WorkflowFunction};
166
+ use temporal_sdk_core_protos::{
167
+ coresdk::common::Payload, temporal::api::command::v1::command::Attributes,
168
+ };
169
+
170
+ use crate::{replay::TestHistoryBuilder, workflow::managed_wf::ManagedWFFunc};
171
+
172
+ use super::{super::OnEventWrapper, *};
173
+
174
+ #[tokio::test]
175
+ async fn upsert_search_attrs_from_workflow() {
176
+ let mut t = TestHistoryBuilder::default();
177
+ t.add_by_type(EventType::WorkflowExecutionStarted);
178
+ t.add_full_wf_task();
179
+ t.add_workflow_execution_completed();
180
+
181
+ let (k1, k2) = ("foo", "bar");
182
+
183
+ let wff = WorkflowFunction::new(move |ctx: WfContext| async move {
184
+ ctx.upsert_search_attributes([
185
+ (
186
+ String::from(k1),
187
+ Payload {
188
+ data: vec![0x01],
189
+ ..Default::default()
190
+ },
191
+ ),
192
+ (
193
+ String::from(k2),
194
+ Payload {
195
+ data: vec![0x02],
196
+ ..Default::default()
197
+ },
198
+ ),
199
+ ]);
200
+ Ok(().into())
201
+ });
202
+ let mut wfm = ManagedWFFunc::new(t, wff, vec![]);
203
+
204
+ wfm.get_next_activation().await.unwrap();
205
+ let commands = wfm.get_server_commands().commands;
206
+ assert!(!commands.is_empty());
207
+ let cmd = commands[0].clone();
208
+ assert_eq!(
209
+ cmd.command_type,
210
+ CommandType::UpsertWorkflowSearchAttributes as i32
211
+ );
212
+ assert_matches!(
213
+ cmd.attributes.unwrap(),
214
+ Attributes::UpsertWorkflowSearchAttributesCommandAttributes(msg) => {
215
+ let fields = &msg.search_attributes.unwrap().indexed_fields;
216
+ let payload1 = fields.get(k1).unwrap();
217
+ let payload2 = fields.get(k2).unwrap();
218
+ assert_eq!(payload1.data[0], 0x01);
219
+ assert_eq!(payload2.data[0], 0x02);
220
+ assert_eq!(fields.len(), 2);
221
+ });
222
+ wfm.shutdown().await.unwrap();
223
+ }
224
+
225
+ #[tokio::test]
226
+ async fn upsert_search_attrs_sm() {
227
+ let mut sm = UpsertSearchAttributesMachine::new();
228
+ assert_eq!(Created {}.to_string(), sm.state().to_string());
229
+
230
+ let cmd_scheduled_sm_event = CommandType::UpsertWorkflowSearchAttributes
231
+ .try_into()
232
+ .unwrap();
233
+ let recorded_history_event = HistoryEvent {
234
+ event_type: EventType::UpsertWorkflowSearchAttributes as i32,
235
+ ..Default::default()
236
+ };
237
+ assert!(sm.matches_event(&recorded_history_event));
238
+ let cmd_recorded_sm_event = recorded_history_event.try_into().unwrap();
239
+
240
+ OnEventWrapper::on_event_mut(&mut sm, cmd_scheduled_sm_event)
241
+ .expect("CommandScheduled should transition Created -> CommandIssued");
242
+ assert_eq!(CommandIssued {}.to_string(), sm.state().to_string());
243
+
244
+ OnEventWrapper::on_event_mut(&mut sm, cmd_recorded_sm_event)
245
+ .expect("CommandRecorded should transition CommandIssued -> Done");
246
+ assert_eq!(Done {}.to_string(), sm.state().to_string());
247
+ }
248
+ }
@@ -10,7 +10,8 @@ use super::{
10
10
  continue_as_new_workflow_state_machine::continue_as_new,
11
11
  fail_workflow_state_machine::fail_workflow, local_activity_state_machine::new_local_activity,
12
12
  patch_state_machine::has_change, signal_external_state_machine::new_external_signal,
13
- timer_state_machine::new_timer, workflow_machines::local_acts::LocalActivityData,
13
+ timer_state_machine::new_timer, upsert_search_attributes_state_machine::upsert_search_attrs,
14
+ workflow_machines::local_acts::LocalActivityData,
14
15
  workflow_task_state_machine::WorkflowTaskMachine, MachineKind, Machines, NewMachineWithCommand,
15
16
  TemporalStateMachine,
16
17
  };
@@ -22,36 +23,32 @@ use crate::{
22
23
  },
23
24
  workflow::{
24
25
  CommandID, DrivenWorkflow, HistoryUpdate, LocalResolution, WFCommand, WorkflowFetcher,
26
+ WorkflowStartedInfo,
25
27
  },
26
28
  };
29
+ use siphasher::sip::SipHasher13;
27
30
  use slotmap::SlotMap;
28
31
  use std::{
29
32
  borrow::{Borrow, BorrowMut},
30
- collections::{hash_map::DefaultHasher, HashMap, VecDeque},
33
+ collections::{HashMap, VecDeque},
31
34
  convert::TryInto,
32
35
  hash::{Hash, Hasher},
33
36
  time::{Duration, Instant, SystemTime},
34
37
  };
35
38
  use temporal_sdk_core_protos::{
36
39
  coresdk::{
37
- common::{NamespacedWorkflowExecution, Payload},
40
+ common::NamespacedWorkflowExecution,
38
41
  workflow_activation::{
39
42
  workflow_activation_job::{self, Variant},
40
- NotifyHasPatch, StartWorkflow, UpdateRandomSeed, WorkflowActivation,
43
+ NotifyHasPatch, UpdateRandomSeed, WorkflowActivation,
41
44
  },
42
- workflow_commands::{
43
- request_cancel_external_workflow_execution as cancel_we,
44
- signal_external_workflow_execution as sig_we,
45
- },
46
- FromPayloadsExt,
45
+ workflow_commands::request_cancel_external_workflow_execution as cancel_we,
47
46
  },
48
47
  temporal::api::{
49
48
  command::v1::Command as ProtoCommand,
50
- common::v1::Header,
51
49
  enums::v1::EventType,
52
- history::v1::{history_event, HistoryEvent, WorkflowExecutionStartedEventAttributes},
50
+ history::v1::{history_event, HistoryEvent},
53
51
  },
54
- utilities::TryIntoOrNone,
55
52
  };
56
53
 
57
54
  type Result<T, E = WFMachinesError> = std::result::Result<T, E>;
@@ -296,17 +293,21 @@ impl WorkflowMachines {
296
293
  self.local_activity_data.outstanding_la_count()
297
294
  }
298
295
 
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()
296
+ /// Returns start info for the workflow if it has started
297
+ pub(crate) fn get_started_info(&self) -> Option<&WorkflowStartedInfo> {
298
+ self.drive_me.get_started_info()
302
299
  }
303
300
 
304
301
  /// Handle a single event from the workflow history. `has_next_event` should be false if `event`
305
302
  /// is the last event in the history.
306
303
  ///
307
- /// TODO: Describe what actually happens in here
304
+ /// This function will attempt to apply the event to the workflow state machines. If there is
305
+ /// not a matching machine for the event, a nondeterminism error is returned. Otherwise, the
306
+ /// event is applied to the machine, which may also return a nondeterminism error if the machine
307
+ /// does not match the expected type. A fatal error may be returned if the machine is in an
308
+ /// invalid state.
308
309
  #[instrument(level = "debug", skip(self, event), fields(event=%event))]
309
- fn handle_event(&mut self, event: &HistoryEvent, has_next_event: bool) -> Result<()> {
310
+ fn handle_event(&mut self, event: HistoryEvent, has_next_event: bool) -> Result<()> {
310
311
  if event.is_final_wf_execution_event() {
311
312
  self.have_seen_terminal_event = true;
312
313
  }
@@ -329,20 +330,20 @@ impl WorkflowMachines {
329
330
  // We remove the machine while we it handles events, then return it, to avoid
330
331
  // borrowing from ourself mutably.
331
332
  let maybe_machine = self.machines_by_event_id.remove(&initial_cmd_id);
332
- if let Some(sm) = maybe_machine {
333
- self.submachine_handle_event(sm, event, has_next_event)?;
334
- } else {
335
- return Err(WFMachinesError::Nondeterminism(format!(
336
- "During event handling, this event had an initial command ID but we could \
337
- not find a matching command for it: {:?}",
338
- event
339
- )));
340
- }
341
-
342
- // Restore machine if not in it's final state
343
- if let Some(sm) = maybe_machine {
344
- if !self.machine(sm).is_final_state() {
345
- self.machines_by_event_id.insert(initial_cmd_id, sm);
333
+ match maybe_machine {
334
+ Some(sm) => {
335
+ self.submachine_handle_event(sm, event, has_next_event)?;
336
+ // Restore machine if not in it's final state
337
+ if !self.machine(sm).is_final_state() {
338
+ self.machines_by_event_id.insert(initial_cmd_id, sm);
339
+ }
340
+ }
341
+ None => {
342
+ return Err(WFMachinesError::Nondeterminism(format!(
343
+ "During event handling, this event had an initial command ID but we \
344
+ could not find a matching command for it: {:?}",
345
+ event
346
+ )));
346
347
  }
347
348
  }
348
349
  }
@@ -385,7 +386,7 @@ impl WorkflowMachines {
385
386
  /// The handling consists of verifying that the next command in the commands queue is associated
386
387
  /// with a state machine, which is then notified about the event and the command is removed from
387
388
  /// the commands queue.
388
- fn handle_command_event(&mut self, event: &HistoryEvent) -> Result<()> {
389
+ fn handle_command_event(&mut self, event: HistoryEvent) -> Result<()> {
389
390
  if event.is_local_activity_marker() {
390
391
  let deets = event.extract_local_activity_marker_data().ok_or_else(|| {
391
392
  WFMachinesError::Fatal(format!("Local activity marker was unparsable: {:?}", event))
@@ -406,10 +407,12 @@ impl WorkflowMachines {
406
407
  }
407
408
  }
408
409
 
410
+ let event_id = event.event_id;
411
+
409
412
  let consumed_cmd = loop {
410
413
  if let Some(peek_machine) = self.commands.front() {
411
414
  let mach = self.machine(peek_machine.machine);
412
- match change_marker_handling(event, mach)? {
415
+ match change_marker_handling(&event, mach)? {
413
416
  ChangeMarkerOutcome::SkipEvent => return Ok(()),
414
417
  ChangeMarkerOutcome::SkipCommand => {
415
418
  self.commands.pop_front();
@@ -442,7 +445,7 @@ impl WorkflowMachines {
442
445
 
443
446
  if !self.machine(consumed_cmd.machine).is_final_state() {
444
447
  self.machines_by_event_id
445
- .insert(event.event_id, consumed_cmd.machine);
448
+ .insert(event_id, consumed_cmd.machine);
446
449
  }
447
450
 
448
451
  Ok(())
@@ -450,48 +453,30 @@ impl WorkflowMachines {
450
453
 
451
454
  fn handle_non_stateful_event(
452
455
  &mut self,
453
- event: &HistoryEvent,
456
+ event: HistoryEvent,
454
457
  has_next_event: bool,
455
458
  ) -> Result<()> {
456
459
  debug!(
457
460
  event = %event,
458
461
  "handling non-stateful event"
459
462
  );
463
+ let event_id = event.event_id;
460
464
  match EventType::from_i32(event.event_type) {
461
465
  Some(EventType::WorkflowExecutionStarted) => {
462
466
  if let Some(history_event::Attributes::WorkflowExecutionStartedEventAttributes(
463
467
  attrs,
464
- )) = &event.attributes
468
+ )) = event.attributes
465
469
  {
466
- self.run_id = attrs.original_execution_run_id.clone();
467
- if let Some(st) = event.event_time.as_ref() {
468
- let as_systime: SystemTime = st.clone().try_into()?;
470
+ if let Some(st) = event.event_time {
471
+ let as_systime: SystemTime = st.try_into()?;
469
472
  self.workflow_start_time = Some(as_systime);
470
473
  }
471
- // We need to notify the lang sdk that it's time to kick off a workflow
472
- self.drive_me.send_job(
473
- StartWorkflow {
474
- workflow_type: attrs
475
- .workflow_type
476
- .as_ref()
477
- .map(|wt| wt.name.clone())
478
- .unwrap_or_default(),
479
- workflow_id: self.workflow_id.clone(),
480
- arguments: Vec::from_payloads(attrs.input.clone()),
481
- randomness_seed: str_to_randomness_seed(
482
- &attrs.original_execution_run_id,
483
- ),
484
- headers: match &attrs.header {
485
- None => HashMap::new(),
486
- Some(Header { fields }) => fields
487
- .iter()
488
- .map(|(k, v)| (k.clone(), Payload::from(v.clone())))
489
- .collect(),
490
- },
491
- }
492
- .into(),
474
+ // Notify the lang sdk that it's time to kick off a workflow
475
+ self.drive_me.start(
476
+ self.workflow_id.clone(),
477
+ str_to_randomness_seed(&attrs.original_execution_run_id),
478
+ attrs,
493
479
  );
494
- self.drive_me.start(attrs.clone());
495
480
  } else {
496
481
  return Err(WFMachinesError::Fatal(format!(
497
482
  "WorkflowExecutionStarted event did not have appropriate attributes: {}",
@@ -503,14 +488,14 @@ impl WorkflowMachines {
503
488
  let wf_task_sm = WorkflowTaskMachine::new(self.next_started_event_id);
504
489
  let key = self.all_machines.insert(wf_task_sm.into());
505
490
  self.submachine_handle_event(key, event, has_next_event)?;
506
- self.machines_by_event_id.insert(event.event_id, key);
491
+ self.machines_by_event_id.insert(event_id, key);
507
492
  }
508
493
  Some(EventType::WorkflowExecutionSignaled) => {
509
494
  if let Some(history_event::Attributes::WorkflowExecutionSignaledEventAttributes(
510
495
  attrs,
511
- )) = &event.attributes
496
+ )) = event.attributes
512
497
  {
513
- self.drive_me.signal(attrs.clone().into());
498
+ self.drive_me.signal(attrs.into());
514
499
  } else {
515
500
  // err
516
501
  }
@@ -520,9 +505,9 @@ impl WorkflowMachines {
520
505
  history_event::Attributes::WorkflowExecutionCancelRequestedEventAttributes(
521
506
  attrs,
522
507
  ),
523
- ) = &event.attributes
508
+ ) = event.attributes
524
509
  {
525
- self.drive_me.cancel(attrs.clone().into());
510
+ self.drive_me.cancel(attrs.into());
526
511
  } else {
527
512
  // err
528
513
  }
@@ -640,25 +625,20 @@ impl WorkflowMachines {
640
625
  }
641
626
  }
642
627
 
643
- let first_event_id = match events.first() {
644
- Some(event) => event.event_id,
645
- None => 0,
646
- };
647
- // Workflow has been evicted, but we've received partial history from the server.
648
- // Need to reset sticky and trigger another poll.
649
- if self.current_started_event_id == 0 && first_event_id != 1 && !events.is_empty() {
650
- debug!("Cache miss.");
651
- self.metrics.sticky_cache_miss();
652
- return Err(WFMachinesError::CacheMiss);
653
- }
654
-
655
- let mut history = events.iter().peekable();
656
-
628
+ let mut history = events.into_iter().peekable();
657
629
  while let Some(event) = history.next() {
630
+ if event.event_id != self.last_processed_event + 1 {
631
+ return Err(WFMachinesError::Fatal(format!(
632
+ "History is out of order. Last processed event: {}, event id: {}",
633
+ self.last_processed_event, event.event_id
634
+ )));
635
+ }
658
636
  let next_event = history.peek();
637
+ let eid = event.event_id;
638
+ let etype = event.event_type;
659
639
  self.handle_event(event, next_event.is_some())?;
660
- self.last_processed_event = event.event_id;
661
- if event.event_type == EventType::WorkflowTaskStarted as i32 && next_event.is_none() {
640
+ self.last_processed_event = eid;
641
+ if etype == EventType::WorkflowTaskStarted as i32 && next_event.is_none() {
662
642
  break;
663
643
  }
664
644
  }
@@ -695,7 +675,7 @@ impl WorkflowMachines {
695
675
  fn submachine_handle_event(
696
676
  &mut self,
697
677
  sm: MachineKey,
698
- event: &HistoryEvent,
678
+ event: HistoryEvent,
699
679
  has_next_event: bool,
700
680
  ) -> Result<()> {
701
681
  let machine_responses = self.machine_mut(sm).handle_event(event, has_next_event)?;
@@ -813,6 +793,13 @@ impl WorkflowMachines {
813
793
  let seq = attrs.seq;
814
794
  self.add_cmd_to_wf_task(new_timer(attrs), Some(CommandID::Timer(seq)));
815
795
  }
796
+ WFCommand::UpsertSearchAttributes(attrs) => {
797
+ let seq = attrs.seq;
798
+ self.add_cmd_to_wf_task(
799
+ upsert_search_attrs(attrs),
800
+ Some(CommandID::Timer(seq)),
801
+ );
802
+ }
816
803
  WFCommand::CancelTimer(attrs) => {
817
804
  jobs.extend(self.process_cancellation(CommandID::Timer(attrs.seq))?);
818
805
  }
@@ -824,10 +811,9 @@ impl WorkflowMachines {
824
811
  let seq = attrs.seq;
825
812
  let attrs: ValidScheduleLA = ValidScheduleLA::from_schedule_la(
826
813
  attrs,
827
- self.started_attrs()
814
+ self.get_started_info()
828
815
  .as_ref()
829
- .map(|x| x.workflow_execution_timeout.clone().try_into_or_none())
830
- .flatten(),
816
+ .and_then(|x| x.workflow_execution_timeout),
831
817
  )
832
818
  .map_err(|e| {
833
819
  WFMachinesError::Fatal(format!(
@@ -925,33 +911,10 @@ impl WorkflowMachines {
925
911
  );
926
912
  }
927
913
  WFCommand::SignalExternalWorkflow(attrs) => {
928
- let (we, only_child) = match attrs.target {
929
- None => {
930
- return Err(WFMachinesError::Fatal(
931
- "Signal external workflow command had empty target field"
932
- .to_string(),
933
- ))
934
- }
935
- Some(sig_we::Target::ChildWorkflowId(wfid)) => (
936
- NamespacedWorkflowExecution {
937
- namespace: self.namespace.clone(),
938
- workflow_id: wfid,
939
- run_id: "".to_string(),
940
- },
941
- true,
942
- ),
943
- Some(sig_we::Target::WorkflowExecution(we)) => (we, false),
944
- };
945
-
914
+ let seq = attrs.seq;
946
915
  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)),
916
+ new_external_signal(attrs, &self.namespace)?,
917
+ Some(CommandID::SignalExternal(seq)),
955
918
  );
956
919
  }
957
920
  WFCommand::CancelSignalWorkflow(attrs) => {
@@ -973,7 +936,8 @@ impl WorkflowMachines {
973
936
  let mut jobs = vec![];
974
937
  let m_key = self.get_machine_key(id)?;
975
938
  let machine_resps = self.machine_mut(m_key).cancel()?;
976
- debug!(machine_responses = ?machine_resps, cmd_id = ?id, "Cancel request responses");
939
+ debug!(machine_responses = %machine_resps.display(), cmd_id = ?id,
940
+ "Cancel request responses");
977
941
  for r in machine_resps {
978
942
  match r {
979
943
  MachineResponse::IssueNewCommand(c) => {
@@ -1084,7 +1048,9 @@ impl WorkflowMachines {
1084
1048
  }
1085
1049
 
1086
1050
  fn str_to_randomness_seed(run_id: &str) -> u64 {
1087
- let mut s = DefaultHasher::new();
1051
+ // This was originally `DefaultHasher` but that is potentially unstable across Rust releases.
1052
+ // This must forever be `SipHasher13` now or we risk breaking history compat.
1053
+ let mut s = SipHasher13::new();
1088
1054
  run_id.hash(&mut s);
1089
1055
  s.finish()
1090
1056
  }
@@ -64,8 +64,8 @@ impl WFMachinesAdapter for WorkflowTaskMachine {
64
64
  task_started_event_id,
65
65
  time,
66
66
  } => {
67
- let (event, has_next_event) = if let Some(ei) = event_info {
68
- (ei.event, ei.has_next_event)
67
+ let (event_id, event_type, has_next_event) = if let Some(ei) = event_info {
68
+ (ei.event_id, ei.event_type, ei.has_next_event)
69
69
  } else {
70
70
  return Err(WFMachinesError::Fatal(
71
71
  "WF Task machine should never issue a task started trigger \
@@ -74,8 +74,8 @@ impl WFMachinesAdapter for WorkflowTaskMachine {
74
74
  ));
75
75
  };
76
76
 
77
- let cur_event_past_or_at_start = event.event_id >= task_started_event_id;
78
- if event.event_type() == EventType::WorkflowTaskStarted
77
+ let cur_event_past_or_at_start = event_id >= task_started_event_id;
78
+ if event_type == EventType::WorkflowTaskStarted
79
79
  && (!cur_event_past_or_at_start || has_next_event)
80
80
  {
81
81
  return Ok(vec![]);