@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
@@ -1,9 +1,8 @@
1
1
  mod local_acts;
2
2
 
3
3
  use super::{
4
- activity_state_machine::new_activity, cancel_external_state_machine::new_external_cancel,
4
+ cancel_external_state_machine::new_external_cancel,
5
5
  cancel_workflow_state_machine::cancel_workflow,
6
- child_workflow_state_machine::new_child_workflow,
7
6
  complete_workflow_state_machine::complete_workflow,
8
7
  continue_as_new_workflow_state_machine::continue_as_new,
9
8
  fail_workflow_state_machine::fail_workflow, local_activity_state_machine::new_local_activity,
@@ -14,14 +13,23 @@ use super::{
14
13
  TemporalStateMachine,
15
14
  };
16
15
  use crate::{
16
+ internal_flags::InternalFlags,
17
17
  protosext::{HistoryEventExt, ValidScheduleLA},
18
18
  telemetry::{metrics::MetricsContext, VecDisplayer},
19
19
  worker::{
20
20
  workflow::{
21
21
  history_update::NextWFT,
22
- machines::modify_workflow_properties_state_machine::modify_workflow_properties,
23
- CommandID, DrivenWorkflow, HistoryUpdate, LocalResolution, OutgoingJob, WFCommand,
24
- WFMachinesError, WorkflowFetcher, WorkflowStartedInfo,
22
+ machines::{
23
+ activity_state_machine::ActivityMachine,
24
+ child_workflow_state_machine::ChildWorkflowMachine,
25
+ modify_workflow_properties_state_machine::modify_workflow_properties,
26
+ patch_state_machine::VERSION_SEARCH_ATTR_KEY,
27
+ upsert_search_attributes_state_machine::upsert_search_attrs_internal,
28
+ HistEventData,
29
+ },
30
+ CommandID, DrivenWorkflow, HistoryUpdate, InternalFlagsRef, LocalResolution,
31
+ OutgoingJob, RunBasics, WFCommand, WFMachinesError, WorkflowFetcher,
32
+ WorkflowStartedInfo,
25
33
  },
26
34
  ExecutingLAId, LocalActRequest, LocalActivityExecutionResult, LocalActivityResolution,
27
35
  },
@@ -30,9 +38,11 @@ use siphasher::sip::SipHasher13;
30
38
  use slotmap::{SlotMap, SparseSecondaryMap};
31
39
  use std::{
32
40
  borrow::{Borrow, BorrowMut},
41
+ cell::RefCell,
33
42
  collections::{HashMap, VecDeque},
34
43
  convert::TryInto,
35
44
  hash::{Hash, Hasher},
45
+ rc::Rc,
36
46
  time::{Duration, Instant, SystemTime},
37
47
  };
38
48
  use temporal_sdk_core_protos::{
@@ -49,7 +59,8 @@ use temporal_sdk_core_protos::{
49
59
  temporal::api::{
50
60
  command::v1::{command::Attributes as ProtoCmdAttrs, Command as ProtoCommand},
51
61
  enums::v1::EventType,
52
- history::v1::{history_event, HistoryEvent},
62
+ history::v1::{history_event, history_event::Attributes, HistoryEvent},
63
+ sdk::v1::WorkflowTaskCompletedMetadata,
53
64
  },
54
65
  };
55
66
 
@@ -94,6 +105,9 @@ pub(crate) struct WorkflowMachines {
94
105
  /// The current workflow time if it has been established. This may differ from the WFT start
95
106
  /// time since local activities may advance the clock
96
107
  current_wf_time: Option<SystemTime>,
108
+ /// The internal flags which have been seen so far during this run's execution and thus are
109
+ /// usable during replay.
110
+ observed_internal_flags: InternalFlagsRef,
97
111
 
98
112
  all_machines: SlotMap<MachineKey, Machines>,
99
113
  /// If a machine key is in this map, that machine was created internally by core, not as a
@@ -202,25 +216,22 @@ where
202
216
  }
203
217
 
204
218
  impl WorkflowMachines {
205
- pub(crate) fn new(
206
- namespace: String,
207
- workflow_id: String,
208
- workflow_type: String,
209
- run_id: String,
210
- history: HistoryUpdate,
211
- driven_wf: DrivenWorkflow,
212
- metrics: MetricsContext,
213
- ) -> Self {
214
- let replaying = history.previous_wft_started_id > 0;
219
+ pub(crate) fn new(basics: RunBasics, driven_wf: DrivenWorkflow) -> Self {
220
+ let replaying = basics.history.previous_wft_started_id > 0;
221
+ let mut observed_internal_flags = InternalFlags::new(basics.capabilities);
222
+ // Peek ahead to determine used patches in the first WFT.
223
+ if let Some(attrs) = basics.history.peek_next_wft_completed(0) {
224
+ observed_internal_flags.add_from_complete(attrs);
225
+ };
215
226
  Self {
216
- last_history_from_server: history,
217
- namespace,
218
- workflow_id,
219
- workflow_type,
220
- run_id,
227
+ last_history_from_server: basics.history,
228
+ namespace: basics.namespace,
229
+ workflow_id: basics.workflow_id,
230
+ workflow_type: basics.workflow_type,
231
+ run_id: basics.run_id,
221
232
  drive_me: driven_wf,
222
233
  replaying,
223
- metrics,
234
+ metrics: basics.metrics,
224
235
  // In an ideal world one could say ..Default::default() here and it'd still work.
225
236
  current_started_event_id: 0,
226
237
  next_started_event_id: 0,
@@ -229,6 +240,7 @@ impl WorkflowMachines {
229
240
  workflow_end_time: None,
230
241
  wft_start_time: None,
231
242
  current_wf_time: None,
243
+ observed_internal_flags: Rc::new(RefCell::new(observed_internal_flags)),
232
244
  all_machines: Default::default(),
233
245
  machine_is_core_created: Default::default(),
234
246
  machines_by_event_id: Default::default(),
@@ -348,6 +360,12 @@ impl WorkflowMachines {
348
360
  run_id: self.run_id.clone(),
349
361
  history_length: self.last_processed_event as u32,
350
362
  jobs,
363
+ available_internal_flags: (*self.observed_internal_flags)
364
+ .borrow()
365
+ .all_lang()
366
+ .iter()
367
+ .copied()
368
+ .collect(),
351
369
  }
352
370
  }
353
371
 
@@ -362,6 +380,18 @@ impl WorkflowMachines {
362
380
  .any(|v| v.is_la_resolution)
363
381
  }
364
382
 
383
+ pub(crate) fn get_metadata_for_wft_complete(&self) -> WorkflowTaskCompletedMetadata {
384
+ (*self.observed_internal_flags)
385
+ .borrow_mut()
386
+ .gather_for_wft_complete()
387
+ }
388
+
389
+ pub(crate) fn add_lang_used_flags(&self, flags: Vec<u32>) {
390
+ (*self.observed_internal_flags)
391
+ .borrow_mut()
392
+ .add_lang_used(flags);
393
+ }
394
+
365
395
  /// Iterate the state machines, which consists of grabbing any pending outgoing commands from
366
396
  /// the workflow code, handling them, and preparing them to be sent off to the server.
367
397
  pub(crate) fn iterate_machines(&mut self) -> Result<()> {
@@ -390,29 +420,47 @@ impl WorkflowMachines {
390
420
  // then we don't need to do anything here, and in fact we need to avoid re-applying the
391
421
  // final WFT.
392
422
  if self.have_seen_terminal_event {
423
+ // Replay clearly counts as done now, since we return here and never do anything else.
424
+ self.replaying = false;
393
425
  return Ok(0);
394
426
  }
395
427
 
396
- let last_handled_wft_started_id = self.current_started_event_id;
397
- let events =
398
- match self
428
+ fn update_internal_flags(me: &mut WorkflowMachines) {
429
+ // Update observed patches with any that were used in the task
430
+ if let Some(next_complete) = me
399
431
  .last_history_from_server
400
- .take_next_wft_sequence(last_handled_wft_started_id)
432
+ .peek_next_wft_completed(me.last_processed_event)
401
433
  {
402
- NextWFT::ReplayOver => {
403
- vec![]
404
- }
405
- NextWFT::WFT(mut evts) => {
406
- // Do not re-process events we have already processed
407
- evts.retain(|e| e.event_id > self.last_processed_event);
408
- evts
409
- }
410
- NextWFT::NeedFetch => return Err(WFMachinesError::Fatal(
434
+ (*me.observed_internal_flags)
435
+ .borrow_mut()
436
+ .add_from_complete(next_complete);
437
+ }
438
+ }
439
+
440
+ // We update the internal flags before applying the current task (peeking to the completion
441
+ // of this task), and also at the end (peeking to the completion of the task that lang is
442
+ // about to generate commands for, and for which we will want those flags active).
443
+ update_internal_flags(self);
444
+
445
+ let last_handled_wft_started_id = self.current_started_event_id;
446
+ let (events, has_final_event) = match self
447
+ .last_history_from_server
448
+ .take_next_wft_sequence(last_handled_wft_started_id)
449
+ {
450
+ NextWFT::ReplayOver => (vec![], true),
451
+ NextWFT::WFT(mut evts, has_final_event) => {
452
+ // Do not re-process events we have already processed
453
+ evts.retain(|e| e.event_id > self.last_processed_event);
454
+ (evts, has_final_event)
455
+ }
456
+ NextWFT::NeedFetch => {
457
+ return Err(WFMachinesError::Fatal(
411
458
  "Need to fetch history events to continue applying workflow task, but this \
412
459
  should be prevented ahead of time! This is a Core SDK bug."
413
460
  .to_string(),
414
- )),
415
- };
461
+ ));
462
+ }
463
+ };
416
464
  let num_events_to_process = events.len();
417
465
 
418
466
  // We're caught up on reply if there are no new events to process
@@ -427,6 +475,8 @@ impl WorkflowMachines {
427
475
  }
428
476
  }
429
477
 
478
+ let mut saw_completed = false;
479
+ let mut do_handle_event = true;
430
480
  let mut history = events.into_iter().peekable();
431
481
  while let Some(event) = history.next() {
432
482
  if event.event_id != self.last_processed_event + 1 {
@@ -437,12 +487,43 @@ impl WorkflowMachines {
437
487
  }
438
488
  let next_event = history.peek();
439
489
  let eid = event.event_id;
440
- let etype = event.event_type();
441
- self.handle_event(event, next_event.is_some())?;
442
- self.last_processed_event = eid;
443
- if etype == EventType::WorkflowTaskStarted && next_event.is_none() {
444
- break;
490
+
491
+ // This definition of replaying here is that we are no longer replaying as soon as we
492
+ // see new events that have never been seen or produced by the SDK.
493
+ //
494
+ // Specifically, replay ends once we have seen the last command-event which was produced
495
+ // as a result of the last completed WFT. Thus, replay would be false for things like
496
+ // signals which were received and after the last completion, and thus generated the
497
+ // current WFT being handled.
498
+ if self.replaying && has_final_event && saw_completed && !event.is_command_event() {
499
+ // Replay is finished
500
+ self.replaying = false;
445
501
  }
502
+ if event.event_type() == EventType::WorkflowTaskCompleted {
503
+ saw_completed = true;
504
+ }
505
+
506
+ if do_handle_event {
507
+ let eho = self.handle_event(
508
+ HistEventData {
509
+ event,
510
+ replaying: self.replaying,
511
+ current_task_is_last_in_history: has_final_event,
512
+ },
513
+ next_event,
514
+ )?;
515
+ if matches!(
516
+ eho,
517
+ EventHandlingOutcome::SkipEvent {
518
+ skip_next_event: true
519
+ }
520
+ ) {
521
+ do_handle_event = false;
522
+ }
523
+ } else {
524
+ do_handle_event = true;
525
+ }
526
+ self.last_processed_event = eid;
446
527
  }
447
528
 
448
529
  // Scan through to the next WFT, searching for any patch / la markers, so that we can
@@ -492,6 +573,8 @@ impl WorkflowMachines {
492
573
  }
493
574
  }
494
575
 
576
+ update_internal_flags(self);
577
+
495
578
  if !self.replaying {
496
579
  self.metrics.wf_task_replay_latency(replay_start.elapsed());
497
580
  }
@@ -499,16 +582,20 @@ impl WorkflowMachines {
499
582
  Ok(num_events_to_process)
500
583
  }
501
584
 
502
- /// Handle a single event from the workflow history. `has_next_event` should be false if `event`
503
- /// is the last event in the history.
585
+ /// Handle a single event from the workflow history.
504
586
  ///
505
587
  /// This function will attempt to apply the event to the workflow state machines. If there is
506
588
  /// not a matching machine for the event, a nondeterminism error is returned. Otherwise, the
507
589
  /// event is applied to the machine, which may also return a nondeterminism error if the machine
508
590
  /// does not match the expected type. A fatal error may be returned if the machine is in an
509
591
  /// invalid state.
510
- #[instrument(skip(self, event), fields(event=%event))]
511
- fn handle_event(&mut self, event: HistoryEvent, has_next_event: bool) -> Result<()> {
592
+ #[instrument(skip(self, event_dat), fields(event=%event_dat))]
593
+ fn handle_event(
594
+ &mut self,
595
+ event_dat: HistEventData,
596
+ next_event: Option<&HistoryEvent>,
597
+ ) -> Result<EventHandlingOutcome> {
598
+ let event = &event_dat.event;
512
599
  if event.is_final_wf_execution_event() {
513
600
  self.have_seen_terminal_event = true;
514
601
  }
@@ -516,24 +603,18 @@ impl WorkflowMachines {
516
603
  event.event_type(),
517
604
  EventType::WorkflowExecutionTerminated | EventType::WorkflowExecutionTimedOut
518
605
  ) {
519
- return if has_next_event {
606
+ let are_more_events =
607
+ next_event.is_some() || !event_dat.current_task_is_last_in_history;
608
+ return if are_more_events {
520
609
  Err(WFMachinesError::Fatal(
521
610
  "Machines were fed a history which has an event after workflow execution was \
522
611
  terminated!"
523
612
  .to_string(),
524
613
  ))
525
614
  } else {
526
- Ok(())
615
+ Ok(EventHandlingOutcome::Normal)
527
616
  };
528
617
  }
529
- if self.replaying
530
- && self.current_started_event_id
531
- >= self.last_history_from_server.previous_wft_started_id
532
- && event.event_type() != EventType::WorkflowTaskCompleted
533
- {
534
- // Replay is finished
535
- self.replaying = false;
536
- }
537
618
  if event.event_type() == EventType::Unspecified || event.attributes.is_none() {
538
619
  return if !event.worker_may_ignore {
539
620
  Err(WFMachinesError::Fatal(format!(
@@ -541,13 +622,14 @@ impl WorkflowMachines {
541
622
  )))
542
623
  } else {
543
624
  debug!("Event is ignorable");
544
- Ok(())
625
+ Ok(EventHandlingOutcome::SkipEvent {
626
+ skip_next_event: false,
627
+ })
545
628
  };
546
629
  }
547
630
 
548
631
  if event.is_command_event() {
549
- self.handle_command_event(event)?;
550
- return Ok(());
632
+ return self.handle_command_event(event_dat, next_event);
551
633
  }
552
634
 
553
635
  if let Some(initial_cmd_id) = event.get_initial_command_event_id() {
@@ -556,7 +638,7 @@ impl WorkflowMachines {
556
638
  let maybe_machine = self.machines_by_event_id.remove(&initial_cmd_id);
557
639
  match maybe_machine {
558
640
  Some(sm) => {
559
- self.submachine_handle_event(sm, event, has_next_event)?;
641
+ self.submachine_handle_event(sm, event_dat)?;
560
642
  // Restore machine if not in it's final state
561
643
  if !self.machine(sm).is_final_state() {
562
644
  self.machines_by_event_id.insert(initial_cmd_id, sm);
@@ -570,10 +652,10 @@ impl WorkflowMachines {
570
652
  }
571
653
  }
572
654
  } else {
573
- self.handle_non_stateful_event(event, has_next_event)?;
655
+ self.handle_non_stateful_event(event_dat)?;
574
656
  }
575
657
 
576
- Ok(())
658
+ Ok(EventHandlingOutcome::Normal)
577
659
  }
578
660
 
579
661
  /// A command event is an event which is generated from a command emitted as a result of
@@ -585,7 +667,13 @@ impl WorkflowMachines {
585
667
  /// The handling consists of verifying that the next command in the commands queue is associated
586
668
  /// with a state machine, which is then notified about the event and the command is removed from
587
669
  /// the commands queue.
588
- fn handle_command_event(&mut self, event: HistoryEvent) -> Result<()> {
670
+ fn handle_command_event(
671
+ &mut self,
672
+ event_dat: HistEventData,
673
+ next_event: Option<&HistoryEvent>,
674
+ ) -> Result<EventHandlingOutcome> {
675
+ let event = &event_dat.event;
676
+
589
677
  if event.is_local_activity_marker() {
590
678
  let deets = event.extract_local_activity_marker_data().ok_or_else(|| {
591
679
  WFMachinesError::Fatal(format!("Local activity marker was unparsable: {event:?}"))
@@ -594,8 +682,8 @@ impl WorkflowMachines {
594
682
  let mkey = self.get_machine_key(cmdid)?;
595
683
  if let Machines::LocalActivityMachine(lam) = self.machine(mkey) {
596
684
  if lam.marker_should_get_special_handling()? {
597
- self.submachine_handle_event(mkey, event, false)?;
598
- return Ok(());
685
+ self.submachine_handle_event(mkey, event_dat)?;
686
+ return Ok(EventHandlingOutcome::Normal);
599
687
  }
600
688
  } else {
601
689
  return Err(WFMachinesError::Fatal(format!(
@@ -610,13 +698,13 @@ impl WorkflowMachines {
610
698
  let consumed_cmd = loop {
611
699
  if let Some(peek_machine) = self.commands.front() {
612
700
  let mach = self.machine(peek_machine.machine);
613
- match change_marker_handling(&event, mach)? {
614
- ChangeMarkerOutcome::SkipEvent => return Ok(()),
615
- ChangeMarkerOutcome::SkipCommand => {
701
+ match change_marker_handling(event, mach, next_event)? {
702
+ EventHandlingOutcome::SkipCommand => {
616
703
  self.commands.pop_front();
617
704
  continue;
618
705
  }
619
- ChangeMarkerOutcome::Normal => {}
706
+ eho @ EventHandlingOutcome::SkipEvent { .. } => return Ok(eho),
707
+ EventHandlingOutcome::Normal => {}
620
708
  }
621
709
  }
622
710
 
@@ -635,7 +723,7 @@ impl WorkflowMachines {
635
723
 
636
724
  if !canceled_before_sent {
637
725
  // Feed the machine the event
638
- self.submachine_handle_event(command.machine, event, true)?;
726
+ self.submachine_handle_event(command.machine, event_dat)?;
639
727
  break command;
640
728
  }
641
729
  };
@@ -645,26 +733,22 @@ impl WorkflowMachines {
645
733
  .insert(event_id, consumed_cmd.machine);
646
734
  }
647
735
 
648
- Ok(())
736
+ Ok(EventHandlingOutcome::Normal)
649
737
  }
650
738
 
651
- fn handle_non_stateful_event(
652
- &mut self,
653
- event: HistoryEvent,
654
- has_next_event: bool,
655
- ) -> Result<()> {
739
+ fn handle_non_stateful_event(&mut self, event_dat: HistEventData) -> Result<()> {
656
740
  trace!(
657
- event = %event,
741
+ event = %event_dat.event,
658
742
  "handling non-stateful event"
659
743
  );
660
- let event_id = event.event_id;
661
- match EventType::from_i32(event.event_type) {
744
+ let event_id = event_dat.event.event_id;
745
+ match EventType::from_i32(event_dat.event.event_type) {
662
746
  Some(EventType::WorkflowExecutionStarted) => {
663
747
  if let Some(history_event::Attributes::WorkflowExecutionStartedEventAttributes(
664
748
  attrs,
665
- )) = event.attributes
749
+ )) = event_dat.event.attributes
666
750
  {
667
- if let Some(st) = event.event_time.clone() {
751
+ if let Some(st) = event_dat.event.event_time.clone() {
668
752
  let as_systime: SystemTime = st.try_into()?;
669
753
  self.workflow_start_time = Some(as_systime);
670
754
  // Set the workflow time to be the event time of the first event, so that
@@ -676,25 +760,25 @@ impl WorkflowMachines {
676
760
  self.drive_me.start(
677
761
  self.workflow_id.clone(),
678
762
  str_to_randomness_seed(&attrs.original_execution_run_id),
679
- event.event_time.unwrap_or_default(),
763
+ event_dat.event.event_time.unwrap_or_default(),
680
764
  attrs,
681
765
  );
682
766
  } else {
683
767
  return Err(WFMachinesError::Fatal(format!(
684
- "WorkflowExecutionStarted event did not have appropriate attributes: {event}"
768
+ "WorkflowExecutionStarted event did not have appropriate attributes: {event_dat}"
685
769
  )));
686
770
  }
687
771
  }
688
772
  Some(EventType::WorkflowTaskScheduled) => {
689
773
  let wf_task_sm = WorkflowTaskMachine::new(self.next_started_event_id);
690
774
  let key = self.all_machines.insert(wf_task_sm.into());
691
- self.submachine_handle_event(key, event, has_next_event)?;
775
+ self.submachine_handle_event(key, event_dat)?;
692
776
  self.machines_by_event_id.insert(event_id, key);
693
777
  }
694
778
  Some(EventType::WorkflowExecutionSignaled) => {
695
779
  if let Some(history_event::Attributes::WorkflowExecutionSignaledEventAttributes(
696
780
  attrs,
697
- )) = event.attributes
781
+ )) = event_dat.event.attributes
698
782
  {
699
783
  self.drive_me
700
784
  .send_job(workflow_activation::SignalWorkflow::from(attrs).into());
@@ -707,7 +791,7 @@ impl WorkflowMachines {
707
791
  history_event::Attributes::WorkflowExecutionCancelRequestedEventAttributes(
708
792
  attrs,
709
793
  ),
710
- ) = event.attributes
794
+ ) = event_dat.event.attributes
711
795
  {
712
796
  self.drive_me
713
797
  .send_job(workflow_activation::CancelWorkflow::from(attrs).into());
@@ -717,7 +801,7 @@ impl WorkflowMachines {
717
801
  }
718
802
  _ => {
719
803
  return Err(WFMachinesError::Fatal(format!(
720
- "The event is not a non-stateful event, but we tried to handle it as one: {event}"
804
+ "The event is not a non-stateful event, but we tried to handle it as one: {event_dat}"
721
805
  )));
722
806
  }
723
807
  }
@@ -734,13 +818,8 @@ impl WorkflowMachines {
734
818
 
735
819
  /// Wrapper for calling [TemporalStateMachine::handle_event] which appropriately takes action
736
820
  /// on the returned machine responses
737
- fn submachine_handle_event(
738
- &mut self,
739
- sm: MachineKey,
740
- event: HistoryEvent,
741
- has_next_event: bool,
742
- ) -> Result<()> {
743
- let machine_responses = self.machine_mut(sm).handle_event(event, has_next_event)?;
821
+ fn submachine_handle_event(&mut self, sm: MachineKey, event: HistEventData) -> Result<()> {
822
+ let machine_responses = self.machine_mut(sm).handle_event(event)?;
744
823
  self.process_machine_responses(sm, machine_responses)?;
745
824
  Ok(())
746
825
  }
@@ -843,6 +922,12 @@ impl WorkflowMachines {
843
922
  CommandIdKind::CoreInternal,
844
923
  );
845
924
  }
925
+ ProtoCmdAttrs::UpsertWorkflowSearchAttributesCommandAttributes(attrs) => {
926
+ self.add_cmd_to_wf_task(
927
+ upsert_search_attrs_internal(attrs),
928
+ CommandIdKind::NeverResolves,
929
+ );
930
+ }
846
931
  c => {
847
932
  return Err(WFMachinesError::Fatal(format!(
848
933
  "A machine requested to create a new command of an unsupported type: {c:?}"
@@ -949,7 +1034,11 @@ impl WorkflowMachines {
949
1034
  }
950
1035
  WFCommand::UpsertSearchAttributes(attrs) => {
951
1036
  self.add_cmd_to_wf_task(
952
- upsert_search_attrs(attrs),
1037
+ upsert_search_attrs(
1038
+ attrs,
1039
+ self.observed_internal_flags.clone(),
1040
+ self.replaying,
1041
+ ),
953
1042
  CommandIdKind::NeverResolves,
954
1043
  );
955
1044
  }
@@ -958,7 +1047,10 @@ impl WorkflowMachines {
958
1047
  }
959
1048
  WFCommand::AddActivity(attrs) => {
960
1049
  let seq = attrs.seq;
961
- self.add_cmd_to_wf_task(new_activity(attrs), CommandID::Activity(seq).into());
1050
+ self.add_cmd_to_wf_task(
1051
+ ActivityMachine::new_scheduled(attrs, self.observed_internal_flags.clone()),
1052
+ CommandID::Activity(seq).into(),
1053
+ );
962
1054
  }
963
1055
  WFCommand::AddLocalActivity(attrs) => {
964
1056
  let seq = attrs.seq;
@@ -978,6 +1070,7 @@ impl WorkflowMachines {
978
1070
  self.replaying,
979
1071
  self.local_activity_data.take_preresolution(seq),
980
1072
  self.current_wf_time,
1073
+ self.observed_internal_flags.clone(),
981
1074
  )?;
982
1075
  let machkey = self.all_machines.insert(la.into());
983
1076
  self.id_to_machine
@@ -1010,13 +1103,21 @@ impl WorkflowMachines {
1010
1103
  WFCommand::SetPatchMarker(attrs) => {
1011
1104
  // Do not create commands for change IDs that we have already created commands
1012
1105
  // for.
1013
- if !matches!(self.encountered_change_markers.get(&attrs.patch_id),
1106
+ let encountered_entry = self.encountered_change_markers.get(&attrs.patch_id);
1107
+ if !matches!(encountered_entry,
1014
1108
  Some(ChangeInfo {created_command}) if *created_command)
1015
1109
  {
1016
- self.add_cmd_to_wf_task(
1017
- has_change(attrs.patch_id.clone(), self.replaying, attrs.deprecated),
1018
- CommandIdKind::NeverResolves,
1019
- );
1110
+ let (patch_machine, other_cmds) = has_change(
1111
+ attrs.patch_id.clone(),
1112
+ self.replaying,
1113
+ attrs.deprecated,
1114
+ encountered_entry.is_some(),
1115
+ self.encountered_change_markers.keys().map(|s| s.as_str()),
1116
+ self.observed_internal_flags.clone(),
1117
+ )?;
1118
+ let mkey =
1119
+ self.add_cmd_to_wf_task(patch_machine, CommandIdKind::NeverResolves);
1120
+ self.process_machine_responses(mkey, other_cmds)?;
1020
1121
 
1021
1122
  if let Some(ci) = self.encountered_change_markers.get_mut(&attrs.patch_id) {
1022
1123
  ci.created_command = true;
@@ -1033,7 +1134,10 @@ impl WorkflowMachines {
1033
1134
  WFCommand::AddChildWorkflow(attrs) => {
1034
1135
  let seq = attrs.seq;
1035
1136
  self.add_cmd_to_wf_task(
1036
- new_child_workflow(attrs),
1137
+ ChildWorkflowMachine::new_scheduled(
1138
+ attrs,
1139
+ self.observed_internal_flags.clone(),
1140
+ ),
1037
1141
  CommandID::ChildWorkflowStart(seq).into(),
1038
1142
  );
1039
1143
  }
@@ -1118,15 +1222,21 @@ impl WorkflowMachines {
1118
1222
  }
1119
1223
 
1120
1224
  /// Add a new command/machines for that command to the current workflow task
1121
- fn add_cmd_to_wf_task(&mut self, machine: NewMachineWithCommand, id: CommandIdKind) {
1225
+ fn add_cmd_to_wf_task(
1226
+ &mut self,
1227
+ machine: NewMachineWithCommand,
1228
+ id: CommandIdKind,
1229
+ ) -> MachineKey {
1122
1230
  let mach = self.add_new_command_machine(machine);
1231
+ let key = mach.machine;
1123
1232
  if let CommandIdKind::LangIssued(id) = id {
1124
- self.id_to_machine.insert(id, mach.machine);
1233
+ self.id_to_machine.insert(id, key);
1125
1234
  }
1126
1235
  if matches!(id, CommandIdKind::CoreInternal) {
1127
- self.machine_is_core_created.insert(mach.machine, ());
1236
+ self.machine_is_core_created.insert(key, ());
1128
1237
  }
1129
1238
  self.current_wf_task_commands.push_back(mach);
1239
+ key
1130
1240
  }
1131
1241
 
1132
1242
  fn add_new_command_machine(&mut self, machine: NewMachineWithCommand) -> CommandAndMachine {
@@ -1186,15 +1296,20 @@ fn str_to_randomness_seed(run_id: &str) -> u64 {
1186
1296
  s.finish()
1187
1297
  }
1188
1298
 
1189
- enum ChangeMarkerOutcome {
1190
- SkipEvent,
1299
+ #[must_use]
1300
+ enum EventHandlingOutcome {
1301
+ SkipEvent { skip_next_event: bool },
1191
1302
  SkipCommand,
1192
1303
  Normal,
1193
1304
  }
1194
1305
 
1195
1306
  /// Special handling for patch markers, when handling command events as in
1196
1307
  /// [WorkflowMachines::handle_command_event]
1197
- fn change_marker_handling(event: &HistoryEvent, mach: &Machines) -> Result<ChangeMarkerOutcome> {
1308
+ fn change_marker_handling(
1309
+ event: &HistoryEvent,
1310
+ mach: &Machines,
1311
+ next_event: Option<&HistoryEvent>,
1312
+ ) -> Result<EventHandlingOutcome> {
1198
1313
  if !mach.matches_event(event) {
1199
1314
  // Version markers can be skipped in the event they are deprecated
1200
1315
  if let Some((patch_name, deprecated)) = event.get_patch_marker_details() {
@@ -1202,7 +1317,18 @@ fn change_marker_handling(event: &HistoryEvent, mach: &Machines) -> Result<Chang
1202
1317
  // markers are allowed without matching changed calls.
1203
1318
  if deprecated {
1204
1319
  debug!("Deprecated patch marker tried against wrong machine, skipping.");
1205
- return Ok(ChangeMarkerOutcome::SkipEvent);
1320
+
1321
+ // Also ignore the subsequent upsert event if present
1322
+ let mut skip_next_event = false;
1323
+ if let Some(Attributes::UpsertWorkflowSearchAttributesEventAttributes(atts)) =
1324
+ next_event.and_then(|ne| ne.attributes.as_ref())
1325
+ {
1326
+ if let Some(ref sa) = atts.search_attributes {
1327
+ skip_next_event = sa.indexed_fields.contains_key(VERSION_SEARCH_ATTR_KEY);
1328
+ }
1329
+ }
1330
+
1331
+ return Ok(EventHandlingOutcome::SkipEvent { skip_next_event });
1206
1332
  }
1207
1333
  return Err(WFMachinesError::Nondeterminism(format!(
1208
1334
  "Non-deprecated patch marker encountered for change {patch_name}, \
@@ -1214,10 +1340,10 @@ fn change_marker_handling(event: &HistoryEvent, mach: &Machines) -> Result<Chang
1214
1340
  // worker.
1215
1341
  if matches!(mach, Machines::PatchMachine(_)) {
1216
1342
  debug!("Skipping non-matching event against patch machine");
1217
- return Ok(ChangeMarkerOutcome::SkipCommand);
1343
+ return Ok(EventHandlingOutcome::SkipCommand);
1218
1344
  }
1219
1345
  }
1220
- Ok(ChangeMarkerOutcome::Normal)
1346
+ Ok(EventHandlingOutcome::Normal)
1221
1347
  }
1222
1348
 
1223
1349
  #[derive(derive_more::From)]