@temporalio/core-bridge 1.6.0 → 1.7.1

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 (138) hide show
  1. package/Cargo.lock +520 -456
  2. package/lib/index.d.ts +8 -6
  3. package/lib/index.js.map +1 -1
  4. package/package.json +8 -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 +2 -2
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.buildkite/pipeline.yml +1 -1
  13. package/sdk-core/.github/workflows/heavy.yml +1 -0
  14. package/sdk-core/README.md +13 -7
  15. package/sdk-core/client/src/lib.rs +27 -9
  16. package/sdk-core/client/src/metrics.rs +17 -8
  17. package/sdk-core/client/src/raw.rs +3 -3
  18. package/sdk-core/core/Cargo.toml +3 -4
  19. package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
  20. package/sdk-core/core/src/abstractions.rs +197 -18
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +137 -45
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
  23. package/sdk-core/core/src/core_tests/determinism.rs +212 -2
  24. package/sdk-core/core/src/core_tests/local_activities.rs +183 -36
  25. package/sdk-core/core/src/core_tests/queries.rs +32 -14
  26. package/sdk-core/core/src/core_tests/workers.rs +8 -5
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +340 -51
  28. package/sdk-core/core/src/ephemeral_server/mod.rs +110 -8
  29. package/sdk-core/core/src/internal_flags.rs +141 -0
  30. package/sdk-core/core/src/lib.rs +14 -9
  31. package/sdk-core/core/src/replay/mod.rs +16 -27
  32. package/sdk-core/core/src/telemetry/metrics.rs +69 -35
  33. package/sdk-core/core/src/telemetry/mod.rs +38 -14
  34. package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
  35. package/sdk-core/core/src/test_help/mod.rs +65 -13
  36. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
  37. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  38. package/sdk-core/core/src/worker/activities/local_activities.rs +122 -6
  39. package/sdk-core/core/src/worker/activities.rs +347 -173
  40. package/sdk-core/core/src/worker/client/mocks.rs +22 -2
  41. package/sdk-core/core/src/worker/client.rs +18 -2
  42. package/sdk-core/core/src/worker/mod.rs +137 -44
  43. package/sdk-core/core/src/worker/workflow/history_update.rs +132 -51
  44. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +207 -166
  45. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +6 -7
  46. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -7
  47. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +157 -82
  48. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +12 -12
  49. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -7
  50. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +13 -15
  51. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +170 -60
  52. package/sdk-core/core/src/worker/workflow/machines/mod.rs +24 -16
  53. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +6 -8
  54. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +320 -204
  55. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +10 -13
  56. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +15 -23
  57. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +187 -46
  58. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +237 -111
  59. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +13 -13
  60. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +10 -6
  61. package/sdk-core/core/src/worker/workflow/managed_run.rs +81 -62
  62. package/sdk-core/core/src/worker/workflow/mod.rs +341 -79
  63. package/sdk-core/core/src/worker/workflow/run_cache.rs +18 -11
  64. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +15 -3
  65. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +2 -0
  66. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +75 -52
  67. package/sdk-core/core-api/Cargo.toml +0 -1
  68. package/sdk-core/core-api/src/lib.rs +13 -7
  69. package/sdk-core/core-api/src/telemetry.rs +4 -6
  70. package/sdk-core/core-api/src/worker.rs +5 -0
  71. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +80 -55
  72. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +22 -68
  73. package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
  74. package/sdk-core/histories/old_change_marker_format.bin +0 -0
  75. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
  76. package/sdk-core/protos/api_upstream/Makefile +1 -1
  77. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
  78. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
  80. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
  81. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
  83. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
  84. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
  85. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
  87. package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  88. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
  89. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
  90. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
  91. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
  92. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
  93. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
  94. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  95. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  96. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
  97. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +65 -60
  98. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
  99. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
  100. package/sdk-core/sdk/Cargo.toml +1 -1
  101. package/sdk-core/sdk/src/lib.rs +21 -5
  102. package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
  103. package/sdk-core/sdk/src/workflow_context.rs +24 -17
  104. package/sdk-core/sdk/src/workflow_future.rs +9 -3
  105. package/sdk-core/sdk-core-protos/src/history_builder.rs +114 -89
  106. package/sdk-core/sdk-core-protos/src/history_info.rs +6 -1
  107. package/sdk-core/sdk-core-protos/src/lib.rs +205 -64
  108. package/sdk-core/test-utils/src/canned_histories.rs +106 -296
  109. package/sdk-core/test-utils/src/lib.rs +32 -5
  110. package/sdk-core/tests/heavy_tests.rs +10 -43
  111. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  112. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
  113. package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  114. package/sdk-core/tests/integ_tests/polling_tests.rs +3 -8
  115. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -2
  116. package/sdk-core/tests/integ_tests/visibility_tests.rs +34 -23
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +97 -81
  118. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -0
  120. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
  121. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +5 -1
  122. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +1 -0
  123. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +25 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
  125. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +30 -0
  126. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +64 -0
  127. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  128. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +4 -0
  129. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -1
  130. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +7 -2
  131. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -7
  132. package/sdk-core/tests/integ_tests/workflow_tests.rs +8 -8
  133. package/sdk-core/tests/main.rs +16 -25
  134. package/sdk-core/tests/runner.rs +11 -9
  135. package/src/conversions.rs +14 -8
  136. package/src/runtime.rs +9 -8
  137. package/ts/index.ts +8 -6
  138. package/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
@@ -21,19 +21,37 @@ use super::{
21
21
  workflow_machines::MachineResponse, Cancellable, EventInfo, NewMachineWithCommand,
22
22
  OnEventWrapper, WFMachinesAdapter, WFMachinesError,
23
23
  };
24
- use crate::protosext::HistoryEventExt;
25
- use rustfsm::{fsm, TransitionResult};
26
- use std::convert::TryFrom;
24
+ use crate::{
25
+ internal_flags::CoreInternalFlags,
26
+ protosext::HistoryEventExt,
27
+ worker::workflow::{
28
+ machines::{
29
+ upsert_search_attributes_state_machine::MAX_SEARCH_ATTR_PAYLOAD_SIZE, HistEventData,
30
+ },
31
+ InternalFlagsRef,
32
+ },
33
+ };
34
+ use anyhow::Context;
35
+ use rustfsm::{fsm, StateMachine, TransitionResult};
36
+ use std::{
37
+ collections::{BTreeSet, HashMap},
38
+ convert::TryFrom,
39
+ };
27
40
  use temporal_sdk_core_protos::{
28
41
  constants::PATCH_MARKER_NAME,
29
- coresdk::common::build_has_change_marker_details,
42
+ coresdk::{common::build_has_change_marker_details, AsJsonPayloadExt},
30
43
  temporal::api::{
31
- command::v1::{Command, RecordMarkerCommandAttributes},
44
+ command::v1::{
45
+ Command, RecordMarkerCommandAttributes, UpsertWorkflowSearchAttributesCommandAttributes,
46
+ },
47
+ common::v1::SearchAttributes,
32
48
  enums::v1::CommandType,
33
49
  history::v1::HistoryEvent,
34
50
  },
35
51
  };
36
52
 
53
+ pub(crate) const VERSION_SEARCH_ATTR_KEY: &str = "TemporalChangeVersion";
54
+
37
55
  fsm! {
38
56
  pub(super) name PatchMachine;
39
57
  command PatchCommand;
@@ -71,53 +89,90 @@ pub(super) enum PatchCommand {}
71
89
  /// are guaranteed to return the same value.
72
90
  /// `replaying_when_invoked`: If the workflow is replaying when this invocation occurs, this needs
73
91
  /// to be set to true.
74
- pub(super) fn has_change(
92
+ pub(super) fn has_change<'a>(
75
93
  patch_id: String,
76
94
  replaying_when_invoked: bool,
77
95
  deprecated: bool,
78
- ) -> NewMachineWithCommand {
79
- let (machine, command) =
80
- PatchMachine::new_scheduled(SharedState { patch_id }, replaying_when_invoked, deprecated);
81
- NewMachineWithCommand {
82
- command,
83
- machine: machine.into(),
84
- }
85
- }
86
-
87
- impl PatchMachine {
88
- fn new_scheduled(
89
- state: SharedState,
90
- replaying_when_invoked: bool,
91
- deprecated: bool,
92
- ) -> (Self, Command) {
93
- let initial_state = if replaying_when_invoked {
94
- Replaying {}.into()
96
+ seen_in_peekahead: bool,
97
+ existing_patch_ids: impl Iterator<Item = &'a str>,
98
+ internal_flags: InternalFlagsRef,
99
+ ) -> Result<(NewMachineWithCommand, Vec<MachineResponse>), WFMachinesError> {
100
+ let shared_state = SharedState { patch_id };
101
+ let initial_state = if replaying_when_invoked {
102
+ Replaying {}.into()
103
+ } else {
104
+ Executing {}.into()
105
+ };
106
+ let command = Command {
107
+ command_type: CommandType::RecordMarker as i32,
108
+ attributes: Some(
109
+ RecordMarkerCommandAttributes {
110
+ marker_name: PATCH_MARKER_NAME.to_string(),
111
+ details: build_has_change_marker_details(&shared_state.patch_id, deprecated)
112
+ .context("While encoding patch marker details")?,
113
+ header: None,
114
+ failure: None,
115
+ }
116
+ .into(),
117
+ ),
118
+ };
119
+ let mut machine = PatchMachine::from_parts(initial_state, shared_state);
120
+
121
+ OnEventWrapper::on_event_mut(&mut machine, PatchMachineEvents::Schedule)
122
+ .expect("Patch machine scheduling doesn't fail");
123
+
124
+ // If we're replaying but this patch isn't in the peekahead, then we wouldn't have
125
+ // upserted either, and thus should not create the machine
126
+ let replaying_and_not_in_history = replaying_when_invoked && !seen_in_peekahead;
127
+ let cannot_use_flag = !internal_flags.borrow_mut().try_use(
128
+ CoreInternalFlags::UpsertSearchAttributeOnPatch,
129
+ !replaying_when_invoked,
130
+ );
131
+ let maybe_upsert_cmd = if replaying_and_not_in_history || cannot_use_flag {
132
+ vec![]
133
+ } else {
134
+ // Produce an upsert SA command for this patch.
135
+ let mut all_ids = BTreeSet::from_iter(existing_patch_ids);
136
+ all_ids.insert(machine.shared_state.patch_id.as_str());
137
+ let serialized = all_ids
138
+ .as_json_payload()
139
+ .context("Could not serialize search attribute value for patch machine")
140
+ .map_err(|e| WFMachinesError::Fatal(e.to_string()))?;
141
+
142
+ if serialized.data.len() >= MAX_SEARCH_ATTR_PAYLOAD_SIZE {
143
+ warn!(
144
+ "Serialized size of {VERSION_SEARCH_ATTR_KEY} search attribute update would \
145
+ exceed the maximum value size. Skipping this upsert. Be aware that your \
146
+ visibility records will not include the following patch: {}",
147
+ machine.shared_state.patch_id
148
+ );
149
+ vec![]
95
150
  } else {
96
- Executing {}.into()
97
- };
98
- let cmd = Command {
99
- command_type: CommandType::RecordMarker as i32,
100
- attributes: Some(
101
- RecordMarkerCommandAttributes {
102
- marker_name: PATCH_MARKER_NAME.to_string(),
103
- details: build_has_change_marker_details(&state.patch_id, deprecated),
104
- header: None,
105
- failure: None,
151
+ let indexed_fields = {
152
+ let mut m = HashMap::new();
153
+ m.insert(VERSION_SEARCH_ATTR_KEY.to_string(), serialized);
154
+ m
155
+ };
156
+ vec![MachineResponse::NewCoreOriginatedCommand(
157
+ UpsertWorkflowSearchAttributesCommandAttributes {
158
+ search_attributes: Some(SearchAttributes { indexed_fields }),
106
159
  }
107
160
  .into(),
108
- ),
109
- };
110
- let mut machine = Self {
111
- state: initial_state,
112
- shared_state: state,
113
- };
114
- OnEventWrapper::on_event_mut(&mut machine, PatchMachineEvents::Schedule)
115
- .expect("Patch machine scheduling doesn't fail");
161
+ )]
162
+ }
163
+ };
116
164
 
117
- (machine, cmd)
118
- }
165
+ Ok((
166
+ NewMachineWithCommand {
167
+ command,
168
+ machine: machine.into(),
169
+ },
170
+ maybe_upsert_cmd,
171
+ ))
119
172
  }
120
173
 
174
+ impl PatchMachine {}
175
+
121
176
  #[derive(Default, Clone)]
122
177
  pub(super) struct Executing {}
123
178
 
@@ -132,7 +187,7 @@ pub(super) struct MarkerCommandCreated {}
132
187
 
133
188
  impl MarkerCommandCreated {
134
189
  pub(super) fn on_command_record_marker(self) -> PatchMachineTransition<Notified> {
135
- TransitionResult::commands(vec![])
190
+ TransitionResult::default()
136
191
  }
137
192
  }
138
193
 
@@ -161,7 +216,7 @@ impl From<MarkerCommandCreatedReplaying> for Notified {
161
216
  impl Notified {
162
217
  pub(super) fn on_marker_recorded(
163
218
  self,
164
- dat: SharedState,
219
+ dat: &mut SharedState,
165
220
  id: String,
166
221
  ) -> PatchMachineTransition<MarkerCommandRecorded> {
167
222
  if id != dat.patch_id {
@@ -201,10 +256,11 @@ impl TryFrom<CommandType> for PatchMachineEvents {
201
256
  }
202
257
  }
203
258
 
204
- impl TryFrom<HistoryEvent> for PatchMachineEvents {
259
+ impl TryFrom<HistEventData> for PatchMachineEvents {
205
260
  type Error = WFMachinesError;
206
261
 
207
- fn try_from(e: HistoryEvent) -> Result<Self, Self::Error> {
262
+ fn try_from(e: HistEventData) -> Result<Self, Self::Error> {
263
+ let e = e.event;
208
264
  match e.get_patch_marker_details() {
209
265
  Some((id, _)) => Ok(Self::MarkerRecorded(id)),
210
266
  _ => Err(WFMachinesError::Nondeterminism(format!(
@@ -217,35 +273,43 @@ impl TryFrom<HistoryEvent> for PatchMachineEvents {
217
273
  #[cfg(test)]
218
274
  mod tests {
219
275
  use crate::{
276
+ internal_flags::CoreInternalFlags,
220
277
  replay::TestHistoryBuilder,
221
- worker::workflow::{machines::WFMachinesError, ManagedWFFunc},
278
+ worker::workflow::{
279
+ machines::{patch_state_machine::VERSION_SEARCH_ATTR_KEY, WFMachinesError},
280
+ ManagedWFFunc,
281
+ },
222
282
  };
223
283
  use rstest::rstest;
224
- use std::time::Duration;
284
+ use std::{
285
+ collections::{hash_map::RandomState, HashSet, VecDeque},
286
+ time::Duration,
287
+ };
225
288
  use temporal_sdk::{ActivityOptions, WfContext, WorkflowFunction};
226
289
  use temporal_sdk_core_protos::{
227
290
  constants::PATCH_MARKER_NAME,
228
291
  coresdk::{
229
292
  common::decode_change_marker_details,
230
293
  workflow_activation::{workflow_activation_job, NotifyHasPatch, WorkflowActivationJob},
294
+ AsJsonPayloadExt, FromJsonPayloadExt,
231
295
  },
232
296
  temporal::api::{
233
297
  command::v1::{
234
298
  command::Attributes, RecordMarkerCommandAttributes,
235
299
  ScheduleActivityTaskCommandAttributes,
300
+ UpsertWorkflowSearchAttributesCommandAttributes,
236
301
  },
237
302
  common::v1::ActivityType,
238
303
  enums::v1::{CommandType, EventType},
239
304
  history::v1::{
240
- history_event, ActivityTaskCompletedEventAttributes,
241
- ActivityTaskScheduledEventAttributes, ActivityTaskStartedEventAttributes,
242
- TimerFiredEventAttributes,
305
+ ActivityTaskCompletedEventAttributes, ActivityTaskScheduledEventAttributes,
306
+ ActivityTaskStartedEventAttributes, TimerFiredEventAttributes,
243
307
  },
244
308
  },
245
309
  };
246
310
 
247
311
  const MY_PATCH_ID: &str = "test_patch_id";
248
- #[derive(Eq, PartialEq, Copy, Clone)]
312
+ #[derive(Eq, PartialEq, Copy, Clone, Debug)]
249
313
  enum MarkerType {
250
314
  Deprecated,
251
315
  NotDeprecated,
@@ -266,51 +330,65 @@ mod tests {
266
330
  /// EVENT_TYPE_WORKFLOW_TASK_STARTED
267
331
  /// EVENT_TYPE_WORKFLOW_TASK_COMPLETED
268
332
  /// EVENT_TYPE_WORKFLOW_EXECUTION_COMPLETED
269
- fn patch_marker_single_activity(marker_type: MarkerType) -> TestHistoryBuilder {
333
+ fn patch_marker_single_activity(
334
+ marker_type: MarkerType,
335
+ version: usize,
336
+ replay: bool,
337
+ ) -> TestHistoryBuilder {
270
338
  let mut t = TestHistoryBuilder::default();
271
339
  t.add_by_type(EventType::WorkflowExecutionStarted);
272
340
  t.add_full_wf_task();
341
+ t.set_flags_first_wft(
342
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
343
+ &[],
344
+ );
273
345
  match marker_type {
274
- MarkerType::Deprecated => t.add_has_change_marker(MY_PATCH_ID, true),
275
- MarkerType::NotDeprecated => t.add_has_change_marker(MY_PATCH_ID, false),
346
+ MarkerType::Deprecated => {
347
+ t.add_has_change_marker(MY_PATCH_ID, true);
348
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
349
+ }
350
+ MarkerType::NotDeprecated => {
351
+ t.add_has_change_marker(MY_PATCH_ID, false);
352
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
353
+ }
276
354
  MarkerType::NoMarker => {}
277
355
  };
278
356
 
279
- let scheduled_event_id = t.add_get_event_id(
280
- EventType::ActivityTaskScheduled,
281
- Some(
282
- history_event::Attributes::ActivityTaskScheduledEventAttributes(
283
- ActivityTaskScheduledEventAttributes {
284
- activity_id: "0".to_string(),
285
- activity_type: Some(ActivityType {
286
- name: "".to_string(),
287
- }),
288
- ..Default::default()
289
- },
290
- ),
291
- ),
292
- );
293
- let started_event_id = t.add_get_event_id(
294
- EventType::ActivityTaskStarted,
295
- Some(
296
- history_event::Attributes::ActivityTaskStartedEventAttributes(
297
- ActivityTaskStartedEventAttributes {
298
- scheduled_event_id,
299
- ..Default::default()
300
- },
301
- ),
302
- ),
303
- );
304
- t.add(
305
- EventType::ActivityTaskCompleted,
306
- history_event::Attributes::ActivityTaskCompletedEventAttributes(
307
- ActivityTaskCompletedEventAttributes {
308
- scheduled_event_id,
309
- started_event_id,
310
- ..Default::default()
311
- },
312
- ),
313
- );
357
+ let activity_id = if replay {
358
+ match (marker_type, version) {
359
+ (_, 1) => "no_change",
360
+ (MarkerType::NotDeprecated, 2) => "had_change",
361
+ (MarkerType::Deprecated, 2) => "had_change",
362
+ (MarkerType::NoMarker, 2) => "no_change",
363
+ (_, 3) => "had_change",
364
+ (_, 4) => "had_change",
365
+ v => panic!("Nonsense marker / version combo {v:?}"),
366
+ }
367
+ } else {
368
+ // If the workflow isn't replaying (we're creating history here for a workflow which
369
+ // wasn't replaying at the time of scheduling the activity, and has done that, and now
370
+ // we're feeding back the history it would have produced) then it always has the change,
371
+ // except in v1.
372
+ if version > 1 {
373
+ "had_change"
374
+ } else {
375
+ "no_change"
376
+ }
377
+ };
378
+
379
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
380
+ activity_id: activity_id.to_string(),
381
+ ..Default::default()
382
+ });
383
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
384
+ scheduled_event_id,
385
+ ..Default::default()
386
+ });
387
+ t.add(ActivityTaskCompletedEventAttributes {
388
+ scheduled_event_id,
389
+ started_event_id,
390
+ ..Default::default()
391
+ });
314
392
  t.add_full_wf_task();
315
393
  t.add_workflow_execution_completed();
316
394
  t
@@ -383,7 +461,7 @@ mod tests {
383
461
  Ok(().into())
384
462
  });
385
463
 
386
- let t = patch_marker_single_activity(marker_type);
464
+ let t = patch_marker_single_activity(marker_type, workflow_version, replaying);
387
465
  let histinfo = if replaying {
388
466
  t.get_full_history_info()
389
467
  } else {
@@ -421,7 +499,7 @@ mod tests {
421
499
  } else {
422
500
  // Feed more history
423
501
  wfm.new_history(
424
- patch_marker_single_activity(marker_type)
502
+ patch_marker_single_activity(marker_type, wf_version, replaying)
425
503
  .get_full_history_info()
426
504
  .unwrap()
427
505
  .into(),
@@ -446,14 +524,13 @@ mod tests {
446
524
  wfm.shutdown().await.unwrap();
447
525
  }
448
526
 
527
+ // Note that the not-replaying and no-marker cases don't make sense and hence are absent
449
528
  #[rstest]
450
- #[case::v2_no_marker_old_path(false, MarkerType::NoMarker, 2)]
451
529
  #[case::v2_marker_new_path(false, MarkerType::NotDeprecated, 2)]
452
530
  #[case::v2_dep_marker_new_path(false, MarkerType::Deprecated, 2)]
453
531
  #[case::v2_replay_no_marker_old_path(true, MarkerType::NoMarker, 2)]
454
532
  #[case::v2_replay_marker_new_path(true, MarkerType::NotDeprecated, 2)]
455
533
  #[case::v2_replay_dep_marker_new_path(true, MarkerType::Deprecated, 2)]
456
- #[case::v3_no_marker_old_path(false, MarkerType::NoMarker, 3)]
457
534
  #[case::v3_marker_new_path(false, MarkerType::NotDeprecated, 3)]
458
535
  #[case::v3_dep_marker_new_path(false, MarkerType::Deprecated, 3)]
459
536
  #[case::v3_replay_no_marker_old_path(true, MarkerType::NoMarker, 3)]
@@ -482,17 +559,32 @@ mod tests {
482
559
  } else {
483
560
  assert_eq!(act.jobs.len(), 1);
484
561
  }
485
- let commands = wfm.get_server_commands().commands;
486
- assert_eq!(commands.len(), 2);
562
+ let mut commands = VecDeque::from(wfm.get_server_commands().commands);
563
+ let expected_num_cmds = if marker_type == MarkerType::NoMarker {
564
+ 2
565
+ } else {
566
+ 3
567
+ };
568
+ assert_eq!(commands.len(), expected_num_cmds);
487
569
  let dep_flag_expected = wf_version != 2;
488
570
  assert_matches!(
489
- commands[0].attributes.as_ref().unwrap(),
571
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
490
572
  Attributes::RecordMarkerCommandAttributes(
491
573
  RecordMarkerCommandAttributes { marker_name, details,.. })
492
574
 
493
575
  if marker_name == PATCH_MARKER_NAME
494
576
  && decode_change_marker_details(details).unwrap().1 == dep_flag_expected
495
577
  );
578
+ if expected_num_cmds == 3 {
579
+ assert_matches!(
580
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
581
+ Attributes::UpsertWorkflowSearchAttributesCommandAttributes(
582
+ UpsertWorkflowSearchAttributesCommandAttributes{ search_attributes: Some(attrs) }
583
+ )
584
+ if attrs.indexed_fields.get(VERSION_SEARCH_ATTR_KEY).unwrap()
585
+ == &[MY_PATCH_ID].as_json_payload().unwrap()
586
+ );
587
+ }
496
588
  // The only time the "old" timer should fire is in v2, replaying, without a marker.
497
589
  let expected_activity_id =
498
590
  if replaying && marker_type == MarkerType::NoMarker && wf_version == 2 {
@@ -501,7 +593,7 @@ mod tests {
501
593
  "had_change"
502
594
  };
503
595
  assert_matches!(
504
- commands[1].attributes.as_ref().unwrap(),
596
+ commands.pop_front().unwrap().attributes.as_ref().unwrap(),
505
597
  Attributes::ScheduleActivityTaskCommandAttributes(
506
598
  ScheduleActivityTaskCommandAttributes { activity_id, .. }
507
599
  )
@@ -515,7 +607,7 @@ mod tests {
515
607
  // and the history should have the has-change timer. v3 of course always has the change
516
608
  // regardless.
517
609
  wfm.new_history(
518
- patch_marker_single_activity(marker_type)
610
+ patch_marker_single_activity(marker_type, wf_version, replaying)
519
611
  .get_full_history_info()
520
612
  .unwrap()
521
613
  .into(),
@@ -562,120 +654,73 @@ mod tests {
562
654
  let mut t = TestHistoryBuilder::default();
563
655
  t.add_by_type(EventType::WorkflowExecutionStarted);
564
656
  t.add_full_wf_task();
657
+ t.set_flags_first_wft(
658
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
659
+ &[],
660
+ );
565
661
  if have_marker_in_hist {
566
662
  t.add_has_change_marker(MY_PATCH_ID, false);
567
- let scheduled_event_id = t.add_get_event_id(
568
- EventType::ActivityTaskScheduled,
569
- Some(
570
- history_event::Attributes::ActivityTaskScheduledEventAttributes(
571
- ActivityTaskScheduledEventAttributes {
572
- activity_id: "1".to_owned(),
573
- activity_type: Some(ActivityType {
574
- name: "".to_string(),
575
- }),
576
- ..Default::default()
577
- },
578
- ),
579
- ),
580
- );
581
- let started_event_id = t.add_get_event_id(
582
- EventType::ActivityTaskStarted,
583
- Some(
584
- history_event::Attributes::ActivityTaskStartedEventAttributes(
585
- ActivityTaskStartedEventAttributes {
586
- scheduled_event_id,
587
- ..Default::default()
588
- },
589
- ),
590
- ),
591
- );
592
- t.add(
593
- EventType::ActivityTaskCompleted,
594
- history_event::Attributes::ActivityTaskCompletedEventAttributes(
595
- ActivityTaskCompletedEventAttributes {
596
- scheduled_event_id,
597
- started_event_id,
598
- // TODO result: Some(Payloads { payloads: vec![Payload{ metadata: Default::default(), data: vec![] }] }),
599
- ..Default::default()
600
- },
601
- ),
602
- );
603
- t.add_full_wf_task();
604
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
605
- t.add(
606
- EventType::TimerFired,
607
- history_event::Attributes::TimerFiredEventAttributes(TimerFiredEventAttributes {
608
- started_event_id: timer_started_event_id,
609
- timer_id: "1".to_owned(),
663
+ t.add_upsert_search_attrs_for_patch(&[MY_PATCH_ID.to_string()]);
664
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
665
+ activity_id: "1".to_owned(),
666
+ activity_type: Some(ActivityType {
667
+ name: "".to_string(),
610
668
  }),
611
- );
669
+ ..Default::default()
670
+ });
671
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
672
+ scheduled_event_id,
673
+ ..Default::default()
674
+ });
675
+ t.add(ActivityTaskCompletedEventAttributes {
676
+ scheduled_event_id,
677
+ started_event_id,
678
+ ..Default::default()
679
+ });
680
+ t.add_full_wf_task();
681
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
682
+ t.add(TimerFiredEventAttributes {
683
+ started_event_id: timer_started_event_id,
684
+ timer_id: "1".to_owned(),
685
+ });
612
686
  } else {
613
- let started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
614
- t.add(
615
- EventType::TimerFired,
616
- history_event::Attributes::TimerFiredEventAttributes(TimerFiredEventAttributes {
617
- started_event_id,
618
- timer_id: "1".to_owned(),
619
- }),
620
- );
687
+ let started_event_id = t.add_by_type(EventType::TimerStarted);
688
+ t.add(TimerFiredEventAttributes {
689
+ started_event_id,
690
+ timer_id: "1".to_owned(),
691
+ });
621
692
  t.add_full_wf_task();
622
- let timer_started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
623
- t.add(
624
- EventType::TimerFired,
625
- history_event::Attributes::TimerFiredEventAttributes(TimerFiredEventAttributes {
626
- started_event_id: timer_started_event_id,
627
- timer_id: "2".to_owned(),
628
- }),
629
- );
693
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
694
+ t.add(TimerFiredEventAttributes {
695
+ started_event_id: timer_started_event_id,
696
+ timer_id: "2".to_owned(),
697
+ });
630
698
  }
631
699
  t.add_full_wf_task();
632
700
 
633
701
  if have_marker_in_hist {
634
- let scheduled_event_id = t.add_get_event_id(
635
- EventType::ActivityTaskScheduled,
636
- Some(
637
- history_event::Attributes::ActivityTaskScheduledEventAttributes(
638
- ActivityTaskScheduledEventAttributes {
639
- activity_id: "2".to_string(),
640
- activity_type: Some(ActivityType {
641
- name: "".to_string(),
642
- }),
643
- ..Default::default()
644
- },
645
- ),
646
- ),
647
- );
648
- let started_event_id = t.add_get_event_id(
649
- EventType::ActivityTaskStarted,
650
- Some(
651
- history_event::Attributes::ActivityTaskStartedEventAttributes(
652
- ActivityTaskStartedEventAttributes {
653
- scheduled_event_id,
654
- ..Default::default()
655
- },
656
- ),
657
- ),
658
- );
659
- t.add(
660
- EventType::ActivityTaskCompleted,
661
- history_event::Attributes::ActivityTaskCompletedEventAttributes(
662
- ActivityTaskCompletedEventAttributes {
663
- scheduled_event_id,
664
- started_event_id,
665
- // TODO result: Some(Payloads { payloads: vec![Payload{ metadata: Default::default(), data: vec![] }] }),
666
- ..Default::default()
667
- },
668
- ),
669
- );
670
- } else {
671
- let started_event_id = t.add_get_event_id(EventType::TimerStarted, None);
672
- t.add(
673
- EventType::TimerFired,
674
- history_event::Attributes::TimerFiredEventAttributes(TimerFiredEventAttributes {
675
- started_event_id,
676
- timer_id: "3".to_owned(),
702
+ let scheduled_event_id = t.add(ActivityTaskScheduledEventAttributes {
703
+ activity_id: "2".to_string(),
704
+ activity_type: Some(ActivityType {
705
+ name: "".to_string(),
677
706
  }),
678
- );
707
+ ..Default::default()
708
+ });
709
+ let started_event_id = t.add(ActivityTaskStartedEventAttributes {
710
+ scheduled_event_id,
711
+ ..Default::default()
712
+ });
713
+ t.add(ActivityTaskCompletedEventAttributes {
714
+ scheduled_event_id,
715
+ started_event_id,
716
+ ..Default::default()
717
+ });
718
+ } else {
719
+ let started_event_id = t.add_by_type(EventType::TimerStarted);
720
+ t.add(TimerFiredEventAttributes {
721
+ started_event_id,
722
+ timer_id: "3".to_owned(),
723
+ });
679
724
  }
680
725
  t.add_full_wf_task();
681
726
  t.add_workflow_execution_completed();
@@ -704,4 +749,75 @@ mod tests {
704
749
 
705
750
  wfm.shutdown().await.unwrap();
706
751
  }
752
+
753
+ const SIZE_OVERFLOW_PATCH_AMOUNT: usize = 180;
754
+ #[rstest]
755
+ #[case::happy_path(50)]
756
+ // We start exceeding the 2k size limit at 180 patches with this format
757
+ #[case::size_overflow(SIZE_OVERFLOW_PATCH_AMOUNT)]
758
+ #[tokio::test]
759
+ async fn many_patches_combine_in_search_attrib_update(#[case] num_patches: usize) {
760
+ let mut t = TestHistoryBuilder::default();
761
+ t.add_by_type(EventType::WorkflowExecutionStarted);
762
+ t.add_full_wf_task();
763
+ t.set_flags_first_wft(
764
+ &[CoreInternalFlags::UpsertSearchAttributeOnPatch as u32],
765
+ &[],
766
+ );
767
+ for i in 1..=num_patches {
768
+ let id = format!("patch-{i}");
769
+ t.add_has_change_marker(&id, false);
770
+ if i < SIZE_OVERFLOW_PATCH_AMOUNT {
771
+ t.add_upsert_search_attrs_for_patch(&[id]);
772
+ }
773
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
774
+ t.add(TimerFiredEventAttributes {
775
+ started_event_id: timer_started_event_id,
776
+ timer_id: i.to_string(),
777
+ });
778
+ t.add_full_wf_task();
779
+ }
780
+ t.add_workflow_execution_completed();
781
+
782
+ let mut wfm = ManagedWFFunc::new_from_update(
783
+ t.get_history_info(1).unwrap().into(),
784
+ WorkflowFunction::new(move |ctx: WfContext| async move {
785
+ for i in 1..=num_patches {
786
+ let _dontcare = ctx.patched(&format!("patch-{i}"));
787
+ ctx.timer(ONE_SECOND).await;
788
+ }
789
+ Ok(().into())
790
+ }),
791
+ vec![],
792
+ );
793
+ // Iterate through all activations/responses except the final one with complete workflow
794
+ for i in 2..=num_patches + 1 {
795
+ wfm.get_next_activation().await.unwrap();
796
+ let cmds = wfm.get_server_commands();
797
+ wfm.new_history(t.get_history_info(i).unwrap().into())
798
+ .await
799
+ .unwrap();
800
+ if i > SIZE_OVERFLOW_PATCH_AMOUNT {
801
+ assert_eq!(2, cmds.commands.len());
802
+ assert_matches!(cmds.commands[1].command_type(), CommandType::StartTimer);
803
+ continue;
804
+ }
805
+ assert_eq!(3, cmds.commands.len());
806
+ let attrs = assert_matches!(
807
+ cmds.commands[1].attributes.as_ref().unwrap(),
808
+ Attributes::UpsertWorkflowSearchAttributesCommandAttributes(
809
+ UpsertWorkflowSearchAttributesCommandAttributes{ search_attributes: Some(attrs) }
810
+ )
811
+ => attrs
812
+ );
813
+ let expected_patches: HashSet<String, _> =
814
+ (1..i).map(|i| format!("patch-{i}")).collect();
815
+ let deserialized = HashSet::<String, RandomState>::from_json_payload(
816
+ attrs.indexed_fields.get(VERSION_SEARCH_ATTR_KEY).unwrap(),
817
+ )
818
+ .unwrap();
819
+ assert_eq!(deserialized, expected_patches);
820
+ }
821
+ wfm.shutdown().await.unwrap();
822
+ }
707
823
  }