@temporalio/core-bridge 1.7.4 → 1.8.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 (90) hide show
  1. package/Cargo.lock +245 -247
  2. package/Cargo.toml +1 -1
  3. package/lib/errors.d.ts +9 -0
  4. package/lib/errors.js +13 -0
  5. package/lib/errors.js.map +1 -1
  6. package/lib/index.d.ts +19 -3
  7. package/lib/index.js.map +1 -1
  8. package/package.json +3 -3
  9. package/releases/aarch64-apple-darwin/index.node +0 -0
  10. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  11. package/releases/x86_64-apple-darwin/index.node +0 -0
  12. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  13. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  14. package/sdk-core/.github/workflows/heavy.yml +1 -1
  15. package/sdk-core/.github/workflows/semgrep.yml +25 -0
  16. package/sdk-core/README.md +2 -0
  17. package/sdk-core/cargo-tokio-console.sh +5 -0
  18. package/sdk-core/client/src/lib.rs +6 -41
  19. package/sdk-core/client/src/raw.rs +9 -0
  20. package/sdk-core/client/src/retry.rs +0 -16
  21. package/sdk-core/core/Cargo.toml +9 -5
  22. package/sdk-core/core/src/abstractions.rs +7 -75
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +16 -8
  24. package/sdk-core/core/src/core_tests/local_activities.rs +97 -5
  25. package/sdk-core/core/src/core_tests/mod.rs +1 -1
  26. package/sdk-core/core/src/core_tests/workers.rs +16 -16
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +247 -28
  28. package/sdk-core/core/src/lib.rs +2 -3
  29. package/sdk-core/core/src/pollers/mod.rs +30 -3
  30. package/sdk-core/core/src/pollers/poll_buffer.rs +166 -77
  31. package/sdk-core/core/src/protosext/mod.rs +4 -8
  32. package/sdk-core/core/src/replay/mod.rs +1 -1
  33. package/sdk-core/core/src/telemetry/metrics.rs +9 -0
  34. package/sdk-core/core/src/telemetry/mod.rs +3 -0
  35. package/sdk-core/core/src/test_help/mod.rs +9 -16
  36. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +6 -31
  37. package/sdk-core/core/src/worker/activities/local_activities.rs +214 -110
  38. package/sdk-core/core/src/worker/activities.rs +72 -47
  39. package/sdk-core/core/src/worker/client/mocks.rs +1 -1
  40. package/sdk-core/core/src/worker/client.rs +45 -32
  41. package/sdk-core/core/src/worker/mod.rs +170 -122
  42. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +0 -4
  43. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +9 -2
  44. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +9 -2
  45. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -3
  46. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +74 -22
  47. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +3 -2
  48. package/sdk-core/core/src/worker/workflow/managed_run.rs +16 -3
  49. package/sdk-core/core/src/worker/workflow/mod.rs +13 -22
  50. package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -0
  51. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +4 -7
  52. package/sdk-core/core/src/worker/workflow/wft_poller.rs +38 -8
  53. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +1 -0
  54. package/sdk-core/core-api/src/worker.rs +43 -2
  55. package/sdk-core/protos/api_upstream/Makefile +1 -1
  56. package/sdk-core/protos/api_upstream/buf.yaml +1 -6
  57. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +12 -0
  58. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +11 -0
  59. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
  60. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +1 -0
  61. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  62. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +9 -0
  63. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +19 -0
  64. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +5 -0
  65. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +36 -4
  66. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +24 -7
  67. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +4 -0
  68. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +76 -44
  69. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +23 -1
  70. package/sdk-core/protos/google/rpc/status.proto +52 -0
  71. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +16 -0
  72. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +4 -0
  73. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +6 -0
  74. package/sdk-core/sdk/src/lib.rs +31 -10
  75. package/sdk-core/sdk/src/workflow_future.rs +7 -5
  76. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -0
  77. package/sdk-core/sdk-core-protos/src/history_info.rs +1 -0
  78. package/sdk-core/sdk-core-protos/src/lib.rs +82 -73
  79. package/sdk-core/test-utils/Cargo.toml +1 -1
  80. package/sdk-core/test-utils/src/lib.rs +50 -37
  81. package/sdk-core/tests/integ_tests/metrics_tests.rs +143 -10
  82. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +26 -15
  83. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -1
  84. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +2 -2
  85. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +5 -1
  86. package/sdk-core/tests/integ_tests/workflow_tests.rs +1 -0
  87. package/src/conversions.rs +9 -2
  88. package/src/runtime.rs +5 -7
  89. package/ts/errors.ts +15 -0
  90. package/ts/index.ts +22 -4
@@ -47,7 +47,7 @@ use std::{
47
47
  };
48
48
  use temporal_sdk_core_protos::{
49
49
  coresdk::{
50
- common::NamespacedWorkflowExecution,
50
+ common::{NamespacedWorkflowExecution, VersioningIntent},
51
51
  workflow_activation,
52
52
  workflow_activation::{
53
53
  workflow_activation_job, NotifyHasPatch, UpdateRandomSeed, WorkflowActivation,
@@ -95,6 +95,11 @@ pub(crate) struct WorkflowMachines {
95
95
  pub workflow_type: String,
96
96
  /// Identifies the current run
97
97
  pub run_id: String,
98
+ /// The task queue this workflow is operating within
99
+ pub task_queue: String,
100
+ /// Is set to true once we've seen the final event in workflow history, to avoid accidentally
101
+ /// re-applying the final workflow task.
102
+ pub have_seen_terminal_event: bool,
98
103
  /// The time the workflow execution began, as told by the WEStarted event
99
104
  workflow_start_time: Option<SystemTime>,
100
105
  /// The time the workflow execution finished, as determined by when the machines handled
@@ -108,6 +113,10 @@ pub(crate) struct WorkflowMachines {
108
113
  /// The internal flags which have been seen so far during this run's execution and thus are
109
114
  /// usable during replay.
110
115
  observed_internal_flags: InternalFlagsRef,
116
+ /// Set on each WFT started event, the most recent size of history in bytes
117
+ history_size_bytes: u64,
118
+ /// Set on each WFT started event
119
+ continue_as_new_suggested: bool,
111
120
 
112
121
  all_machines: SlotMap<MachineKey, Machines>,
113
122
  /// If a machine key is in this map, that machine was created internally by core, not as a
@@ -137,10 +146,6 @@ pub(crate) struct WorkflowMachines {
137
146
  /// The workflow that is being driven by this instance of the machines
138
147
  drive_me: DrivenWorkflow,
139
148
 
140
- /// Is set to true once we've seen the final event in workflow history, to avoid accidentally
141
- /// re-applying the final workflow task.
142
- pub have_seen_terminal_event: bool,
143
-
144
149
  /// Metrics context
145
150
  pub metrics: MetricsContext,
146
151
  }
@@ -229,6 +234,7 @@ impl WorkflowMachines {
229
234
  workflow_id: basics.workflow_id,
230
235
  workflow_type: basics.workflow_type,
231
236
  run_id: basics.run_id,
237
+ task_queue: basics.task_queue,
232
238
  drive_me: driven_wf,
233
239
  replaying,
234
240
  metrics: basics.metrics,
@@ -241,6 +247,8 @@ impl WorkflowMachines {
241
247
  wft_start_time: None,
242
248
  current_wf_time: None,
243
249
  observed_internal_flags: Rc::new(RefCell::new(observed_internal_flags)),
250
+ history_size_bytes: 0,
251
+ continue_as_new_suggested: false,
244
252
  all_machines: Default::default(),
245
253
  machine_is_core_created: Default::default(),
246
254
  machines_by_event_id: Default::default(),
@@ -369,6 +377,8 @@ impl WorkflowMachines {
369
377
  .borrow()
370
378
  .all_lang()
371
379
  .collect(),
380
+ history_size_bytes: self.history_size_bytes,
381
+ continue_as_new_suggested: self.continue_as_new_suggested,
372
382
  }
373
383
  }
374
384
 
@@ -635,6 +645,13 @@ impl WorkflowMachines {
635
645
  return self.handle_command_event(event_dat, next_event);
636
646
  }
637
647
 
648
+ if let Some(history_event::Attributes::WorkflowTaskStartedEventAttributes(ref attrs)) =
649
+ event.attributes
650
+ {
651
+ self.history_size_bytes = u64::try_from(attrs.history_size_bytes).unwrap_or_default();
652
+ self.continue_as_new_suggested = attrs.suggest_continue_as_new;
653
+ }
654
+
638
655
  if let Some(initial_cmd_id) = event.get_initial_command_event_id() {
639
656
  // We remove the machine while we it handles events, then return it, to avoid
640
657
  // borrowing from ourself mutably.
@@ -1050,24 +1067,27 @@ impl WorkflowMachines {
1050
1067
  }
1051
1068
  WFCommand::AddActivity(attrs) => {
1052
1069
  let seq = attrs.seq;
1070
+ let use_compat = self.determine_use_compatible_flag(
1071
+ attrs.versioning_intent(),
1072
+ &attrs.task_queue,
1073
+ );
1053
1074
  self.add_cmd_to_wf_task(
1054
- ActivityMachine::new_scheduled(attrs, self.observed_internal_flags.clone()),
1075
+ ActivityMachine::new_scheduled(
1076
+ attrs,
1077
+ self.observed_internal_flags.clone(),
1078
+ use_compat,
1079
+ ),
1055
1080
  CommandID::Activity(seq).into(),
1056
1081
  );
1057
1082
  }
1058
1083
  WFCommand::AddLocalActivity(attrs) => {
1059
1084
  let seq = attrs.seq;
1060
- let attrs: ValidScheduleLA = ValidScheduleLA::from_schedule_la(
1061
- attrs,
1062
- self.get_started_info()
1063
- .as_ref()
1064
- .and_then(|x| x.workflow_execution_timeout),
1065
- )
1066
- .map_err(|e| {
1067
- WFMachinesError::Fatal(format!(
1068
- "Invalid schedule local activity request (seq {seq}): {e}"
1069
- ))
1070
- })?;
1085
+ let attrs: ValidScheduleLA =
1086
+ ValidScheduleLA::from_schedule_la(attrs).map_err(|e| {
1087
+ WFMachinesError::Fatal(format!(
1088
+ "Invalid schedule local activity request (seq {seq}): {e}"
1089
+ ))
1090
+ })?;
1071
1091
  let (la, mach_resp) = new_local_activity(
1072
1092
  attrs,
1073
1093
  self.replaying,
@@ -1087,20 +1107,32 @@ impl WorkflowMachines {
1087
1107
  self.process_cancellation(CommandID::LocalActivity(attrs.seq))?;
1088
1108
  }
1089
1109
  WFCommand::CompleteWorkflow(attrs) => {
1090
- self.metrics.wf_completed();
1110
+ if !self.replaying {
1111
+ self.metrics.wf_completed();
1112
+ }
1091
1113
  self.add_terminal_command(complete_workflow(attrs));
1092
1114
  }
1093
1115
  WFCommand::FailWorkflow(attrs) => {
1094
- self.metrics.wf_failed();
1116
+ if !self.replaying {
1117
+ self.metrics.wf_failed();
1118
+ }
1095
1119
  self.add_terminal_command(fail_workflow(attrs));
1096
1120
  }
1097
1121
  WFCommand::ContinueAsNew(attrs) => {
1098
- self.metrics.wf_continued_as_new();
1122
+ if !self.replaying {
1123
+ self.metrics.wf_continued_as_new();
1124
+ }
1099
1125
  let attrs = self.augment_continue_as_new_with_current_values(attrs);
1100
- self.add_terminal_command(continue_as_new(attrs));
1126
+ let use_compat = self.determine_use_compatible_flag(
1127
+ attrs.versioning_intent(),
1128
+ &attrs.task_queue,
1129
+ );
1130
+ self.add_terminal_command(continue_as_new(attrs, use_compat));
1101
1131
  }
1102
1132
  WFCommand::CancelWorkflow(attrs) => {
1103
- self.metrics.wf_canceled();
1133
+ if !self.replaying {
1134
+ self.metrics.wf_canceled();
1135
+ }
1104
1136
  self.add_terminal_command(cancel_workflow(attrs));
1105
1137
  }
1106
1138
  WFCommand::SetPatchMarker(attrs) => {
@@ -1136,10 +1168,15 @@ impl WorkflowMachines {
1136
1168
  }
1137
1169
  WFCommand::AddChildWorkflow(attrs) => {
1138
1170
  let seq = attrs.seq;
1171
+ let use_compat = self.determine_use_compatible_flag(
1172
+ attrs.versioning_intent(),
1173
+ &attrs.task_queue,
1174
+ );
1139
1175
  self.add_cmd_to_wf_task(
1140
1176
  ChildWorkflowMachine::new_scheduled(
1141
1177
  attrs,
1142
1178
  self.observed_internal_flags.clone(),
1179
+ use_compat,
1143
1180
  ),
1144
1181
  CommandID::ChildWorkflowStart(seq).into(),
1145
1182
  );
@@ -1289,6 +1326,21 @@ impl WorkflowMachines {
1289
1326
  }
1290
1327
  attrs
1291
1328
  }
1329
+
1330
+ /// Given a user's versioning intent for a command and that command's target task queue,
1331
+ /// returns whether or not the command should set the flag for attempting to stick within the
1332
+ /// compatible version set
1333
+ fn determine_use_compatible_flag(&self, intent: VersioningIntent, target_tq: &str) -> bool {
1334
+ match intent {
1335
+ VersioningIntent::Compatible => true,
1336
+ VersioningIntent::Default => false,
1337
+ VersioningIntent::Unspecified => {
1338
+ // If the target TQ is empty, that means use same TQ.
1339
+ // When TQs match, use compat by default
1340
+ target_tq.is_empty() || target_tq == self.task_queue
1341
+ }
1342
+ }
1343
+ }
1292
1344
  }
1293
1345
 
1294
1346
  fn str_to_randomness_seed(run_id: &str) -> u64 {
@@ -61,7 +61,7 @@ pub struct ManagedWFFunc {
61
61
  activation_tx: UnboundedSender<WorkflowActivation>,
62
62
  completions_rx: UnboundedReceiver<WorkflowActivationCompletion>,
63
63
  completions_sync_tx: crossbeam::channel::Sender<WorkflowActivationCompletion>,
64
- future_handle: Option<JoinHandle<WorkflowResult<()>>>,
64
+ future_handle: Option<JoinHandle<WorkflowResult<Payload>>>,
65
65
  was_shutdown: bool,
66
66
  }
67
67
 
@@ -93,6 +93,7 @@ impl ManagedWFFunc {
93
93
  workflow_id: "wfid".to_string(),
94
94
  workflow_type: "wftype".to_string(),
95
95
  run_id: "runid".to_string(),
96
+ task_queue: TEST_Q.to_string(),
96
97
  history: hist,
97
98
  metrics: MetricsContext::no_op(),
98
99
  capabilities: DEFAULT_TEST_CAPABILITIES,
@@ -175,7 +176,7 @@ impl ManagedWFFunc {
175
176
  Ok(last_act)
176
177
  }
177
178
 
178
- pub async fn shutdown(&mut self) -> WorkflowResult<()> {
179
+ pub async fn shutdown(&mut self) -> WorkflowResult<Payload> {
179
180
  self.was_shutdown = true;
180
181
  // Send an eviction to ensure wf exits if it has not finished (ex: feeding partial hist)
181
182
  let _ = self.activation_tx.send(create_evict_activation(
@@ -1039,13 +1039,26 @@ impl ManagedRun {
1039
1039
  }
1040
1040
 
1041
1041
  fn reply_to_complete(
1042
- &self,
1042
+ &mut self,
1043
1043
  outcome: ActivationCompleteOutcome,
1044
1044
  chan: Option<oneshot::Sender<ActivationCompleteResult>>,
1045
1045
  ) {
1046
1046
  if let Some(chan) = chan {
1047
- chan.send(self.build_activation_complete_result(outcome))
1048
- .expect("Rcv half of activation reply not dropped");
1047
+ if chan
1048
+ .send(self.build_activation_complete_result(outcome))
1049
+ .is_err()
1050
+ {
1051
+ let warnstr = "The workflow task completer went missing! This likely indicates an \
1052
+ SDK bug, please report."
1053
+ .to_string();
1054
+ warn!(run_id=%self.run_id(), "{}", warnstr);
1055
+ self.request_eviction(RequestEvictMsg {
1056
+ run_id: self.run_id().to_string(),
1057
+ message: warnstr,
1058
+ reason: EvictionReason::Fatal,
1059
+ auto_reply_fail_tt: None,
1060
+ });
1061
+ }
1049
1062
  }
1050
1063
  }
1051
1064
 
@@ -23,22 +23,19 @@ pub(crate) use managed_run::ManagedWFFunc;
23
23
 
24
24
  use crate::{
25
25
  abstractions::{
26
- dbg_panic, stream_when_allowed, take_cell::TakeCell, MeteredSemaphore,
27
- TrackedOwnedMeteredSemPermit, UsedMeteredSemPermit,
26
+ dbg_panic, take_cell::TakeCell, MeteredSemaphore, TrackedOwnedMeteredSemPermit,
27
+ UsedMeteredSemPermit,
28
28
  },
29
29
  internal_flags::InternalFlags,
30
- protosext::{legacy_query_failure, ValidPollWFTQResponse},
31
- telemetry::{
32
- metrics::workflow_worker_type, set_trace_subscriber_for_current_thread, TelemetryInstance,
33
- VecDisplayer,
34
- },
30
+ protosext::legacy_query_failure,
31
+ telemetry::{set_trace_subscriber_for_current_thread, TelemetryInstance, VecDisplayer},
35
32
  worker::{
36
33
  activities::{ActivitiesFromWFTsHandle, LocalActivityManager, TrackedPermittedTqResp},
37
34
  client::{WorkerClient, WorkflowTaskCompletion},
38
35
  workflow::{
39
36
  history_update::HistoryPaginator,
40
37
  managed_run::RunUpdateAct,
41
- wft_extraction::{HistoryFetchReq, WFTExtractor},
38
+ wft_extraction::{HistoryFetchReq, WFTExtractor, WFTStreamIn},
42
39
  wft_poller::validate_wft,
43
40
  workflow_stream::{LocalInput, LocalInputs, WFStream},
44
41
  },
@@ -127,14 +124,13 @@ pub(crate) struct Workflows {
127
124
  /// If set, can be used to reserve activity task slots for eager-return of new activity tasks.
128
125
  activity_tasks_handle: Option<ActivitiesFromWFTsHandle>,
129
126
  /// Ensures we stay at or below this worker's maximum concurrent workflow task limit
130
- wft_semaphore: MeteredSemaphore,
127
+ wft_semaphore: Arc<MeteredSemaphore>,
131
128
  local_act_mgr: Arc<LocalActivityManager>,
132
129
  ever_polled: AtomicBool,
133
130
  }
134
131
 
135
132
  pub(crate) struct WorkflowBasics {
136
133
  pub max_cached_workflows: usize,
137
- pub max_outstanding_wfts: usize,
138
134
  pub shutdown_token: CancellationToken,
139
135
  pub metrics: MetricsContext,
140
136
  pub namespace: String,
@@ -151,6 +147,7 @@ pub(crate) struct RunBasics<'a> {
151
147
  pub workflow_id: String,
152
148
  pub workflow_type: String,
153
149
  pub run_id: String,
150
+ pub task_queue: String,
154
151
  pub history: HistoryUpdate,
155
152
  pub metrics: MetricsContext,
156
153
  pub capabilities: &'a get_system_info_response::Capabilities,
@@ -162,7 +159,8 @@ impl Workflows {
162
159
  basics: WorkflowBasics,
163
160
  sticky_attrs: Option<StickyExecutionAttributes>,
164
161
  client: Arc<dyn WorkerClient>,
165
- wft_stream: impl Stream<Item = Result<ValidPollWFTQResponse, tonic::Status>> + Send + 'static,
162
+ wft_semaphore: Arc<MeteredSemaphore>,
163
+ wft_stream: impl Stream<Item = WFTStreamIn> + Send + 'static,
166
164
  local_activity_request_sink: impl LocalActivityRequestSink,
167
165
  local_act_mgr: Arc<LocalActivityManager>,
168
166
  heartbeat_timeout_rx: UnboundedReceiver<HeartbeatTimeoutMsg>,
@@ -173,16 +171,6 @@ impl Workflows {
173
171
  let (fetch_tx, fetch_rx) = unbounded_channel();
174
172
  let shutdown_tok = basics.shutdown_token.clone();
175
173
  let task_queue = basics.task_queue.clone();
176
- let wft_semaphore = MeteredSemaphore::new(
177
- basics.max_outstanding_wfts,
178
- basics.metrics.with_new_attrs([workflow_worker_type()]),
179
- MetricsContext::available_task_slots,
180
- );
181
- // Only allow polling of the new WFT stream if there are available task slots
182
- let proceeder = stream::unfold(wft_semaphore.clone(), |sem| async move {
183
- Some((sem.acquire_owned().await.unwrap(), sem))
184
- });
185
- let wft_stream = stream_when_allowed(wft_stream, proceeder);
186
174
  let extracted_wft_stream = WFTExtractor::build(
187
175
  client.clone(),
188
176
  basics.fetching_concurrency,
@@ -515,6 +503,10 @@ impl Workflows {
515
503
  pub(super) fn available_wft_permits(&self) -> usize {
516
504
  self.wft_semaphore.available_permits()
517
505
  }
506
+ #[cfg(test)]
507
+ pub(super) fn unused_wft_permits(&self) -> usize {
508
+ self.wft_semaphore.unused_permits()
509
+ }
518
510
 
519
511
  pub(super) async fn shutdown(&self) -> Result<(), anyhow::Error> {
520
512
  if let Some(jh) = self.processing_task.take_once() {
@@ -1235,7 +1227,6 @@ enum CommandID {
1235
1227
  #[derive(Debug, Clone)]
1236
1228
  pub struct WorkflowStartedInfo {
1237
1229
  workflow_task_timeout: Option<Duration>,
1238
- workflow_execution_timeout: Option<Duration>,
1239
1230
  memo: Option<Memo>,
1240
1231
  search_attrs: Option<SearchAttributes>,
1241
1232
  retry_policy: Option<RetryPolicy>,
@@ -13,6 +13,7 @@ use temporal_sdk_core_protos::temporal::api::workflowservice::v1::get_system_inf
13
13
  pub(super) struct RunCache {
14
14
  max: usize,
15
15
  namespace: String,
16
+ task_queue: String,
16
17
  server_capabilities: get_system_info_response::Capabilities,
17
18
  /// Run id -> Data
18
19
  runs: LruCache<String, ManagedRun>,
@@ -25,6 +26,7 @@ impl RunCache {
25
26
  pub fn new(
26
27
  max_cache_size: usize,
27
28
  namespace: String,
29
+ task_queue: String,
28
30
  server_capabilities: get_system_info_response::Capabilities,
29
31
  local_activity_request_sink: impl LocalActivityRequestSink,
30
32
  metrics: MetricsContext,
@@ -39,6 +41,7 @@ impl RunCache {
39
41
  Self {
40
42
  max: max_cache_size,
41
43
  namespace,
44
+ task_queue,
42
45
  server_capabilities,
43
46
  runs: LruCache::new(
44
47
  NonZeroUsize::new(lru_size).expect("LRU size is guaranteed positive"),
@@ -72,6 +75,7 @@ impl RunCache {
72
75
  workflow_id: pwft.work.execution.workflow_id.clone(),
73
76
  workflow_type: pwft.work.workflow_type.clone(),
74
77
  run_id: pwft.work.execution.run_id.clone(),
78
+ task_queue: self.task_queue.clone(),
75
79
  history: history_update,
76
80
  metrics,
77
81
  capabilities: &self.server_capabilities,
@@ -89,6 +93,7 @@ impl RunCache {
89
93
  pub fn remove(&mut self, k: &str) -> Option<ManagedRun> {
90
94
  let r = self.runs.pop(k);
91
95
  self.metrics.cache_size(self.len() as u64);
96
+ self.metrics.cache_eviction();
92
97
  r
93
98
  }
94
99
 
@@ -36,10 +36,7 @@ pub(super) enum WFTExtractorOutput {
36
36
  PollerDead,
37
37
  }
38
38
 
39
- type WFTStreamIn = (
40
- Result<ValidPollWFTQResponse, tonic::Status>,
41
- OwnedMeteredSemPermit,
42
- );
39
+ pub(crate) type WFTStreamIn = Result<(ValidPollWFTQResponse, OwnedMeteredSemPermit), tonic::Status>;
43
40
  #[derive(derive_more::From, Debug)]
44
41
  pub(super) enum HistoryFetchReq {
45
42
  Full(CacheMissFetchReq, Arc<HistfetchRC>),
@@ -58,11 +55,11 @@ impl WFTExtractor {
58
55
  ) -> impl Stream<Item = Result<WFTExtractorOutput, tonic::Status>> + Send + 'static {
59
56
  let fetch_client = client.clone();
60
57
  let wft_stream = wft_stream
61
- .map(move |(wft, permit)| {
58
+ .map(move |stream_in| {
62
59
  let client = client.clone();
63
60
  async move {
64
- match wft {
65
- Ok(wft) => {
61
+ match stream_in {
62
+ Ok((wft, permit)) => {
66
63
  let run_id = wft.workflow_execution.run_id.clone();
67
64
  let tt = wft.task_token.clone();
68
65
  Ok(match HistoryPaginator::from_poll(wft, client).await {
@@ -1,15 +1,20 @@
1
- use crate::{pollers::BoxedWFPoller, protosext::ValidPollWFTQResponse, MetricsContext};
1
+ use crate::{
2
+ abstractions::OwnedMeteredSemPermit,
3
+ pollers::{BoxedWFPoller, Poller},
4
+ protosext::ValidPollWFTQResponse,
5
+ MetricsContext,
6
+ };
2
7
  use futures::{stream, Stream};
3
8
  use temporal_sdk_core_protos::temporal::api::workflowservice::v1::PollWorkflowTaskQueueResponse;
4
9
 
5
10
  pub(crate) fn new_wft_poller(
6
11
  poller: BoxedWFPoller,
7
12
  metrics: MetricsContext,
8
- ) -> impl Stream<Item = Result<ValidPollWFTQResponse, tonic::Status>> {
13
+ ) -> impl Stream<Item = Result<(ValidPollWFTQResponse, OwnedMeteredSemPermit), tonic::Status>> {
9
14
  stream::unfold((poller, metrics), |(poller, metrics)| async move {
10
15
  loop {
11
16
  return match poller.poll().await {
12
- Some(Ok(wft)) => {
17
+ Some(Ok((wft, permit))) => {
13
18
  if wft == PollWorkflowTaskQueueResponse::default() {
14
19
  // We get the default proto in the event that the long poll times out.
15
20
  debug!("Poll wft timeout");
@@ -27,7 +32,7 @@ pub(crate) fn new_wft_poller(
27
32
  }
28
33
  };
29
34
  metrics.wf_tq_poll_ok();
30
- Some((Ok(work), (poller, metrics)))
35
+ Some((Ok((work, permit)), (poller, metrics)))
31
36
  }
32
37
  Some(Err(e)) => {
33
38
  warn!(error=?e, "Error while polling for workflow tasks");
@@ -35,7 +40,12 @@ pub(crate) fn new_wft_poller(
35
40
  }
36
41
  // If poller returns None, it's dead, thus we also return None to terminate this
37
42
  // stream.
38
- None => None,
43
+ None => {
44
+ // Make sure we call the actual shutdown function here to propagate any panics
45
+ // inside the polling tasks as errors.
46
+ poller.shutdown_box().await;
47
+ None
48
+ }
39
49
  };
40
50
  }
41
51
  })
@@ -55,8 +65,11 @@ pub(crate) fn validate_wft(
55
65
  #[cfg(test)]
56
66
  mod tests {
57
67
  use super::*;
58
- use crate::test_help::mock_poller;
68
+ use crate::{
69
+ abstractions::MeteredSemaphore, pollers::MockPermittedPollBuffer, test_help::mock_poller,
70
+ };
59
71
  use futures::{pin_mut, StreamExt};
72
+ use std::sync::Arc;
60
73
 
61
74
  #[tokio::test]
62
75
  async fn poll_timeouts_do_not_produce_responses() {
@@ -66,7 +79,16 @@ mod tests {
66
79
  .times(1)
67
80
  .returning(|| Some(Ok(PollWorkflowTaskQueueResponse::default())));
68
81
  mock_poller.expect_poll().times(1).returning(|| None);
69
- let stream = new_wft_poller(Box::new(mock_poller), MetricsContext::no_op());
82
+ mock_poller.expect_shutdown().times(1).returning(|| ());
83
+ let sem = Arc::new(MeteredSemaphore::new(
84
+ 10,
85
+ MetricsContext::no_op(),
86
+ MetricsContext::available_task_slots,
87
+ ));
88
+ let stream = new_wft_poller(
89
+ Box::new(MockPermittedPollBuffer::new(sem, mock_poller)),
90
+ MetricsContext::no_op(),
91
+ );
70
92
  pin_mut!(stream);
71
93
  assert_matches!(stream.next().await, None);
72
94
  }
@@ -78,7 +100,15 @@ mod tests {
78
100
  .expect_poll()
79
101
  .times(1)
80
102
  .returning(|| Some(Err(tonic::Status::internal("ahhh"))));
81
- let stream = new_wft_poller(Box::new(mock_poller), MetricsContext::no_op());
103
+ let sem = Arc::new(MeteredSemaphore::new(
104
+ 10,
105
+ MetricsContext::no_op(),
106
+ MetricsContext::available_task_slots,
107
+ ));
108
+ let stream = new_wft_poller(
109
+ Box::new(MockPermittedPollBuffer::new(sem, mock_poller)),
110
+ MetricsContext::no_op(),
111
+ );
82
112
  pin_mut!(stream);
83
113
  assert_matches!(stream.next().await, Some(Err(_)));
84
114
  }
@@ -97,6 +97,7 @@ impl WFStream {
97
97
  runs: RunCache::new(
98
98
  basics.max_cached_workflows,
99
99
  basics.namespace.clone(),
100
+ basics.task_queue.clone(),
100
101
  basics.server_capabilities.clone(),
101
102
  local_activity_request_sink,
102
103
  basics.metrics.clone(),
@@ -1,6 +1,9 @@
1
1
  use std::time::Duration;
2
2
  use tokio::sync::mpsc::UnboundedSender;
3
3
 
4
+ const MAX_OUTSTANDING_WFT_DEFAULT: usize = 100;
5
+ const MAX_CONCURRENT_WFT_POLLS_DEFAULT: usize = 5;
6
+
4
7
  /// Defines per-worker configuration options
5
8
  #[derive(Debug, Clone, derive_builder::Builder, serde::Serialize, serde::Deserialize)]
6
9
  #[builder(setter(into), build_fn(validate = "Self::validate"))]
@@ -31,7 +34,7 @@ pub struct WorkerConfig {
31
34
  /// "outstanding" until all activations it requires have been completed.
32
35
  ///
33
36
  /// Cannot be larger than `max_cached_workflows`.
34
- #[builder(default = "100")]
37
+ #[builder(default = "MAX_OUTSTANDING_WFT_DEFAULT")]
35
38
  pub max_outstanding_workflow_tasks: usize,
36
39
  /// The maximum number of activity tasks that will ever be given to this worker concurrently
37
40
  #[builder(default = "100")]
@@ -43,7 +46,7 @@ pub struct WorkerConfig {
43
46
  /// Maximum number of concurrent poll workflow task requests we will perform at a time on this
44
47
  /// worker's task queue. See also [WorkerConfig::nonsticky_to_sticky_poll_ratio]. Must be at
45
48
  /// least 1.
46
- #[builder(default = "5")]
49
+ #[builder(default = "MAX_CONCURRENT_WFT_POLLS_DEFAULT")]
47
50
  pub max_concurrent_wft_polls: usize,
48
51
  /// [WorkerConfig::max_concurrent_wft_polls] * this number = the number of max pollers that will
49
52
  /// be allowed for the nonsticky queue when sticky tasks are enabled. If both defaults are used,
@@ -161,6 +164,44 @@ impl WorkerConfigBuilder {
161
164
  );
162
165
  }
163
166
  }
167
+ if matches!(self.max_concurrent_wft_polls, Some(1))
168
+ && self.max_cached_workflows > Some(0)
169
+ && self
170
+ .max_outstanding_workflow_tasks
171
+ .unwrap_or(MAX_OUTSTANDING_WFT_DEFAULT)
172
+ <= 1
173
+ {
174
+ return Err(
175
+ "`max_outstanding_workflow_tasks` must be at at least 2 when \
176
+ `max_cached_workflows` is nonzero"
177
+ .to_owned(),
178
+ );
179
+ }
180
+ if self
181
+ .max_concurrent_wft_polls
182
+ .unwrap_or(MAX_CONCURRENT_WFT_POLLS_DEFAULT)
183
+ > self
184
+ .max_outstanding_workflow_tasks
185
+ .unwrap_or(MAX_OUTSTANDING_WFT_DEFAULT)
186
+ {
187
+ return Err(
188
+ "`max_concurrent_wft_polls` cannot exceed `max_outstanding_workflow_tasks`"
189
+ .to_owned(),
190
+ );
191
+ }
192
+
193
+ if self.use_worker_versioning.unwrap_or_default()
194
+ && self
195
+ .worker_build_id
196
+ .as_ref()
197
+ .map(|s| s.is_empty())
198
+ .unwrap_or_default()
199
+ {
200
+ return Err(
201
+ "`worker_build_id` must be non-empty when `use_worker_versioning` is true"
202
+ .to_owned(),
203
+ );
204
+ }
164
205
  Ok(())
165
206
  }
166
207
  }
@@ -29,7 +29,7 @@ $(PROTO_OUT):
29
29
  mkdir $(PROTO_OUT)
30
30
 
31
31
  ##### Compile proto files for go #####
32
- grpc: buf-lint api-linter gogo-grpc fix-path
32
+ grpc: buf-lint api-linter buf-breaking gogo-grpc fix-path
33
33
 
34
34
  go-grpc: clean $(PROTO_OUT)
35
35
  printf $(COLOR) "Compile for go-gRPC..."
@@ -1,12 +1,7 @@
1
1
  version: v1
2
2
  breaking:
3
- ignore:
4
- # TODO: Remove after PR 237
5
- - temporal/api/taskqueue/v1
6
- - temporal/api/workflowservice/v1
7
- - temporal/api/history/v1
8
3
  use:
9
- - PACKAGE
4
+ - WIRE_JSON
10
5
  lint:
11
6
  use:
12
7
  - DEFAULT
@@ -37,6 +37,7 @@ import "google/protobuf/timestamp.proto";
37
37
 
38
38
  import "temporal/api/common/v1/message.proto";
39
39
  import "temporal/api/enums/v1/batch_operation.proto";
40
+ import "temporal/api/enums/v1/reset.proto";
40
41
 
41
42
  message BatchOperationInfo {
42
43
  // Batch job ID
@@ -86,4 +87,15 @@ message BatchOperationCancellation {
86
87
  message BatchOperationDeletion {
87
88
  // The identity of the worker/client
88
89
  string identity = 1;
90
+ }
91
+
92
+ // BatchOperationReset sends reset requests to batch workflows.
93
+ // Keep the parameter in sync with temporal.api.workflowservice.v1.ResetWorkflowExecutionRequest.
94
+ message BatchOperationReset {
95
+ // Reset type.
96
+ temporal.api.enums.v1.ResetType reset_type = 1;
97
+ // History event reapply options.
98
+ temporal.api.enums.v1.ResetReapplyType reset_reapply_type = 2;
99
+ // The identity of the worker/client.
100
+ string identity = 3;
89
101
  }
@@ -83,6 +83,10 @@ message ScheduleActivityTaskCommandAttributes {
83
83
  // Request to start the activity directly bypassing matching service and worker polling
84
84
  // The slot for executing the activity should be reserved when setting this field to true.
85
85
  bool request_eager_execution = 12;
86
+ // If this is set, the workflow executing this command wishes to start the activity using
87
+ // a version compatible with the version that this workflow most recently ran on, if such
88
+ // behavior is possible.
89
+ bool use_compatible_version = 13;
86
90
  }
87
91
 
88
92
  message RequestCancelActivityTaskCommandAttributes {
@@ -191,6 +195,9 @@ message ContinueAsNewWorkflowExecutionCommandAttributes {
191
195
  temporal.api.common.v1.Header header = 12;
192
196
  temporal.api.common.v1.Memo memo = 13;
193
197
  temporal.api.common.v1.SearchAttributes search_attributes = 14;
198
+ // If this is set, the workflow executing this command wishes to continue as new using a version
199
+ // compatible with the version that this workflow most recently ran on.
200
+ bool use_compatible_version = 15;
194
201
 
195
202
  // `workflow_execution_timeout` is omitted as it shouldn't be overridden from within a workflow.
196
203
  }
@@ -218,6 +225,10 @@ message StartChildWorkflowExecutionCommandAttributes {
218
225
  temporal.api.common.v1.Header header = 14;
219
226
  temporal.api.common.v1.Memo memo = 15;
220
227
  temporal.api.common.v1.SearchAttributes search_attributes = 16;
228
+ // If this is set, the workflow executing this command wishes to start the child workflow using
229
+ // a version compatible with the version that this workflow most recently ran on, if such
230
+ // behavior is possible.
231
+ bool use_compatible_version = 17;
221
232
  }
222
233
 
223
234
  message ProtocolMessageCommandAttributes {