@temporalio/core-bridge 1.7.0 → 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/Cargo.lock +500 -400
  2. package/package.json +3 -3
  3. package/releases/aarch64-apple-darwin/index.node +0 -0
  4. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  5. package/releases/x86_64-apple-darwin/index.node +0 -0
  6. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  7. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  8. package/sdk-core/client/src/lib.rs +23 -6
  9. package/sdk-core/client/src/raw.rs +15 -6
  10. package/sdk-core/core/Cargo.toml +1 -0
  11. package/sdk-core/core/src/core_tests/activity_tasks.rs +13 -5
  12. package/sdk-core/core/src/core_tests/determinism.rs +49 -2
  13. package/sdk-core/core/src/core_tests/workflow_tasks.rs +21 -39
  14. package/sdk-core/core/src/internal_flags.rs +132 -60
  15. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +10 -7
  16. package/sdk-core/core/src/worker/activities.rs +152 -142
  17. package/sdk-core/core/src/worker/client.rs +12 -8
  18. package/sdk-core/core/src/worker/mod.rs +8 -5
  19. package/sdk-core/core/src/worker/workflow/history_update.rs +86 -2
  20. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +4 -1
  21. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +23 -88
  22. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -6
  23. package/sdk-core/core/src/worker/workflow/managed_run.rs +9 -2
  24. package/sdk-core/core/src/worker/workflow/mod.rs +22 -8
  25. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +29 -27
  26. package/sdk-core/protos/api_upstream/.github/workflows/publish-docs.yml +23 -0
  27. package/sdk-core/protos/api_upstream/Makefile +1 -1
  28. package/sdk-core/protos/api_upstream/buf.yaml +5 -0
  29. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +17 -0
  30. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  31. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +6 -3
  32. package/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +1 -1
  33. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +12 -22
  34. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +2 -2
  35. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -0
  36. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +145 -48
  37. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +19 -8
  38. package/sdk-core/test-utils/src/lib.rs +29 -7
  39. package/sdk-core/tests/integ_tests/activity_functions.rs +5 -0
  40. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +2 -4
  41. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +0 -1
  42. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +5 -7
  43. package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -7
  44. package/sdk-core/tests/main.rs +16 -24
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temporalio/core-bridge",
3
- "version": "1.7.0",
3
+ "version": "1.7.2",
4
4
  "description": "Temporal.io SDK Core<>Node bridge",
5
5
  "main": "index.js",
6
6
  "types": "lib/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
25
  "@opentelemetry/api": "^1.3.0",
26
- "@temporalio/common": "1.7.0",
26
+ "@temporalio/common": "1.7.2",
27
27
  "arg": "^5.0.2",
28
28
  "cargo-cp-artifact": "^0.1.6",
29
29
  "which": "^2.0.2"
@@ -53,5 +53,5 @@
53
53
  "publishConfig": {
54
54
  "access": "public"
55
55
  },
56
- "gitHead": "2b32bac62f879b35238b487d3aaed093a1e449a7"
56
+ "gitHead": "779561124eecdec8396e658c0a1305d343dfaff7"
57
57
  }
@@ -33,7 +33,7 @@ use crate::{
33
33
  workflow_handle::UntypedWorkflowHandle,
34
34
  };
35
35
  use backoff::{exponential, ExponentialBackoff, SystemClock};
36
- use http::uri::InvalidUri;
36
+ use http::{uri::InvalidUri, Uri};
37
37
  use once_cell::sync::OnceCell;
38
38
  use parking_lot::RwLock;
39
39
  use std::{
@@ -114,6 +114,14 @@ pub struct ClientOptions {
114
114
  /// Retry configuration for the server client. Default is [RetryConfig::default]
115
115
  #[builder(default)]
116
116
  pub retry_config: RetryConfig,
117
+
118
+ /// If set, override the origin used when connecting. May be useful in rare situations where tls
119
+ /// verification needs to use a different name from what should be set as the `:authority`
120
+ /// header. If [TlsConfig::domain] is set, and this is not, this will be set to
121
+ /// `https://<domain>`, effectively making the `:authority` header consistent with the domain
122
+ /// override.
123
+ #[builder(default)]
124
+ pub override_origin: Option<Uri>,
117
125
  }
118
126
 
119
127
  /// Configuration options for TLS
@@ -310,6 +318,11 @@ impl ClientOptions {
310
318
  {
311
319
  let channel = Channel::from_shared(self.target_url.to_string())?;
312
320
  let channel = self.add_tls_to_channel(channel).await?;
321
+ let channel = if let Some(origin) = self.override_origin.clone() {
322
+ channel.origin(origin)
323
+ } else {
324
+ channel
325
+ };
313
326
  let channel = channel.connect().await?;
314
327
  let service = ServiceBuilder::new()
315
328
  .layer_fn(|channel| GrpcMetricSvc {
@@ -347,10 +360,7 @@ impl ClientOptions {
347
360
 
348
361
  /// If TLS is configured, set the appropriate options on the provided channel and return it.
349
362
  /// Passes it through if TLS options not set.
350
- async fn add_tls_to_channel(
351
- &self,
352
- channel: Endpoint,
353
- ) -> Result<Endpoint, tonic::transport::Error> {
363
+ async fn add_tls_to_channel(&self, mut channel: Endpoint) -> Result<Endpoint, ClientInitError> {
354
364
  if let Some(tls_cfg) = &self.tls_cfg {
355
365
  let mut tls = tonic::transport::ClientTlsConfig::new();
356
366
 
@@ -361,6 +371,13 @@ impl ClientOptions {
361
371
 
362
372
  if let Some(domain) = &tls_cfg.domain {
363
373
  tls = tls.domain_name(domain);
374
+
375
+ // This song and dance ultimately is just to make sure the `:authority` header ends
376
+ // up correct on requests while we use TLS. Setting the header directly in our
377
+ // interceptor doesn't work since seemingly it is overridden at some point by
378
+ // something lower level.
379
+ let uri: Uri = format!("https://{}", domain).parse()?;
380
+ channel = channel.origin(uri);
364
381
  }
365
382
 
366
383
  if let Some(client_opts) = &tls_cfg.client_tls_config {
@@ -369,7 +386,7 @@ impl ClientOptions {
369
386
  tls = tls.identity(client_identity);
370
387
  }
371
388
 
372
- return channel.tls_config(tls);
389
+ return channel.tls_config(tls).map_err(Into::into);
373
390
  }
374
391
  Ok(channel)
375
392
  }
@@ -740,9 +740,9 @@ proxier! {
740
740
  }
741
741
  );
742
742
  (
743
- update_worker_build_id_ordering,
744
- UpdateWorkerBuildIdOrderingRequest,
745
- UpdateWorkerBuildIdOrderingResponse,
743
+ update_worker_build_id_compatibility,
744
+ UpdateWorkerBuildIdCompatibilityRequest,
745
+ UpdateWorkerBuildIdCompatibilityResponse,
746
746
  |r| {
747
747
  let mut labels = AttachMetricLabels::namespace(r.get_ref().namespace.clone());
748
748
  labels.task_q_str(r.get_ref().task_queue.clone());
@@ -750,9 +750,9 @@ proxier! {
750
750
  }
751
751
  );
752
752
  (
753
- get_worker_build_id_ordering,
754
- GetWorkerBuildIdOrderingRequest,
755
- GetWorkerBuildIdOrderingResponse,
753
+ get_worker_build_id_compatibility,
754
+ GetWorkerBuildIdCompatibilityRequest,
755
+ GetWorkerBuildIdCompatibilityResponse,
756
756
  |r| {
757
757
  let mut labels = AttachMetricLabels::namespace(r.get_ref().namespace.clone());
758
758
  labels.task_q_str(r.get_ref().task_queue.clone());
@@ -768,6 +768,15 @@ proxier! {
768
768
  r.extensions_mut().insert(labels);
769
769
  }
770
770
  );
771
+ (
772
+ poll_workflow_execution_update,
773
+ PollWorkflowExecutionUpdateRequest,
774
+ PollWorkflowExecutionUpdateResponse,
775
+ |r| {
776
+ let labels = AttachMetricLabels::namespace(r.get_ref().namespace.clone());
777
+ r.extensions_mut().insert(labels);
778
+ }
779
+ );
771
780
  (
772
781
  start_batch_operation,
773
782
  StartBatchOperationRequest,
@@ -28,6 +28,7 @@ dashmap = "5.0"
28
28
  derive_builder = "0.12"
29
29
  derive_more = "0.99"
30
30
  enum_dispatch = "0.3"
31
+ enum-iterator = "1.4"
31
32
  flate2 = "1.0"
32
33
  futures = "0.3"
33
34
  futures-util = "0.3"
@@ -58,7 +58,7 @@ use temporal_sdk_core_protos::{
58
58
  TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
59
59
  };
60
60
  use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd, TestWorker};
61
- use tokio::{sync::Barrier, time::sleep};
61
+ use tokio::{join, sync::Barrier, time::sleep};
62
62
  use tokio_util::sync::CancellationToken;
63
63
 
64
64
  fn three_tasks() -> VecDeque<PollActivityTaskQueueResponse> {
@@ -288,7 +288,7 @@ async fn activity_cancel_interrupts_poll() {
288
288
  // Perform first poll to get the activity registered
289
289
  let act = core.poll_activity_task().await.unwrap();
290
290
  // Poll should block until heartbeat is sent, issuing the cancel, and interrupting the poll
291
- tokio::join! {
291
+ join! {
292
292
  async {
293
293
  core.record_activity_heartbeat(ActivityHeartbeat {
294
294
  task_token: act.task_token,
@@ -984,7 +984,7 @@ async fn activity_tasks_from_completion_reserve_slots() {
984
984
  // This wf poll should *not* set the flag that it wants tasks back since both slots are
985
985
  // occupied
986
986
  let run_fut = async { worker.run_until_done().await.unwrap() };
987
- tokio::join!(run_fut, act_completer);
987
+ join!(run_fut, act_completer);
988
988
  }
989
989
 
990
990
  #[tokio::test]
@@ -1052,9 +1052,11 @@ async fn cant_complete_activity_with_unset_result_payload() {
1052
1052
  )
1053
1053
  }
1054
1054
 
1055
+ #[rstest::rstest]
1055
1056
  #[tokio::test]
1056
- async fn graceful_shutdown() {
1057
+ async fn graceful_shutdown(#[values(true, false)] at_max_outstanding: bool) {
1057
1058
  let _task_q = "q";
1059
+ let grace_period = Duration::from_millis(200);
1058
1060
  let mut tasks = three_tasks();
1059
1061
  let mut mock_client = mock_workflow_client();
1060
1062
  mock_client
@@ -1067,15 +1069,21 @@ async fn graceful_shutdown() {
1067
1069
  .times(3)
1068
1070
  .returning(|_, _| Ok(Default::default()));
1069
1071
 
1072
+ let max_outstanding = if at_max_outstanding { 3_usize } else { 100 };
1070
1073
  let worker = Worker::new_test(
1071
1074
  test_worker_cfg()
1072
- .graceful_shutdown_period(Duration::from_millis(500))
1075
+ .graceful_shutdown_period(grace_period)
1076
+ .max_outstanding_activities(max_outstanding)
1073
1077
  .build()
1074
1078
  .unwrap(),
1075
1079
  mock_client,
1076
1080
  );
1077
1081
 
1078
1082
  let _1 = worker.poll_activity_task().await.unwrap();
1083
+
1084
+ // Wait at least the grace period after one poll - ensuring it doesn't trigger prematurely
1085
+ tokio::time::sleep(grace_period.mul_f32(1.1)).await;
1086
+
1079
1087
  let _2 = worker.poll_activity_task().await.unwrap();
1080
1088
  let _3 = worker.poll_activity_task().await.unwrap();
1081
1089
 
@@ -13,8 +13,11 @@ use temporal_sdk::{
13
13
  ActivityOptions, ChildWorkflowOptions, LocalActivityOptions, WfContext, WorkflowResult,
14
14
  };
15
15
  use temporal_sdk_core_protos::{
16
- temporal::api::{enums::v1::WorkflowTaskFailedCause, failure::v1::Failure},
17
- DEFAULT_ACTIVITY_TYPE,
16
+ temporal::api::{
17
+ enums::v1::{EventType, WorkflowTaskFailedCause},
18
+ failure::v1::Failure,
19
+ },
20
+ TestHistoryBuilder, DEFAULT_ACTIVITY_TYPE,
18
21
  };
19
22
 
20
23
  static DID_FAIL: AtomicBool = AtomicBool::new(false);
@@ -268,3 +271,47 @@ async fn child_wf_id_or_type_change_is_nondeterministic(
268
271
  .unwrap();
269
272
  worker.run_until_done().await.unwrap();
270
273
  }
274
+
275
+ /// Repros a situation where if, upon completing a task there is some internal error which causes
276
+ /// us to want to auto-fail the workflow task while there is also an outstanding eviction, the wf
277
+ /// would get evicted but then try to send some info down the completion channel afterward, causing
278
+ /// a panic.
279
+ #[tokio::test]
280
+ async fn repro_channel_missing_because_nondeterminism() {
281
+ for _ in 1..50 {
282
+ let wf_id = "fakeid";
283
+ let wf_type = DEFAULT_WORKFLOW_TYPE;
284
+ let mut t = TestHistoryBuilder::default();
285
+ t.add_by_type(EventType::WorkflowExecutionStarted);
286
+ t.add_full_wf_task();
287
+ t.add_has_change_marker("patch-1", false);
288
+ let _ts = t.add_by_type(EventType::TimerStarted);
289
+ t.add_workflow_task_scheduled_and_started();
290
+
291
+ let mock = mock_workflow_client();
292
+ let mut mh =
293
+ MockPollCfg::from_resp_batches(wf_id, t, [1.into(), ResponseType::AllHistory], mock);
294
+ mh.num_expected_fails = 1;
295
+ let mut worker = mock_sdk_cfg(mh, |cfg| {
296
+ cfg.max_cached_workflows = 2;
297
+ cfg.ignore_evicts_on_shutdown = false;
298
+ });
299
+
300
+ worker.register_wf(wf_type.to_owned(), move |ctx: WfContext| async move {
301
+ ctx.patched("wrongid");
302
+ ctx.timer(Duration::from_secs(1)).await;
303
+ Ok(().into())
304
+ });
305
+
306
+ worker
307
+ .submit_wf(
308
+ wf_id.to_owned(),
309
+ wf_type.to_owned(),
310
+ vec![],
311
+ WorkflowOptions::default(),
312
+ )
313
+ .await
314
+ .unwrap();
315
+ worker.run_until_done().await.unwrap();
316
+ }
317
+ }
@@ -1,5 +1,7 @@
1
1
  use crate::{
2
- advance_fut, job_assert,
2
+ advance_fut,
3
+ internal_flags::CoreInternalFlags,
4
+ job_assert,
3
5
  replay::TestHistoryBuilder,
4
6
  test_help::{
5
7
  build_fake_worker, build_mock_pollers, build_multihist_mock_sg, canned_histories,
@@ -14,9 +16,9 @@ use crate::{
14
16
  use futures::{stream, FutureExt};
15
17
  use rstest::{fixture, rstest};
16
18
  use std::{
17
- collections::{HashMap, VecDeque},
19
+ collections::{HashMap, HashSet, VecDeque},
18
20
  sync::{
19
- atomic::{AtomicBool, AtomicU64, Ordering},
21
+ atomic::{AtomicU64, Ordering},
20
22
  Arc,
21
23
  },
22
24
  time::Duration,
@@ -54,9 +56,7 @@ use temporal_sdk_core_protos::{
54
56
  },
55
57
  DEFAULT_ACTIVITY_TYPE, DEFAULT_WORKFLOW_TYPE,
56
58
  };
57
- use temporal_sdk_core_test_utils::{
58
- fanout_tasks, schedule_activity_cmd, start_timer_cmd, WorkerTestHelpers,
59
- };
59
+ use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd, WorkerTestHelpers};
60
60
  use tokio::{
61
61
  join,
62
62
  sync::{Barrier, Semaphore},
@@ -2297,7 +2297,7 @@ async fn ensure_fetching_fail_during_complete_sends_task_failure() {
2297
2297
  mock.expect_get_workflow_execution_history()
2298
2298
  .returning(move |_, _, _| {
2299
2299
  error!("Called fetch second time!");
2300
- Ok(really_empty_fetch_resp.clone())
2300
+ Err(tonic::Status::not_found("Ahh broken"))
2301
2301
  })
2302
2302
  .times(1);
2303
2303
  mock.expect_fail_workflow_task()
@@ -2380,54 +2380,36 @@ async fn lang_internal_flags() {
2380
2380
  core.shutdown().await;
2381
2381
  }
2382
2382
 
2383
- // Verify we send flags to server when they're used
2383
+ // Verify we send all core internal flags on the first non-replay WFT
2384
2384
  #[tokio::test]
2385
2385
  async fn core_internal_flags() {
2386
2386
  let mut t = TestHistoryBuilder::default();
2387
2387
  t.add_by_type(EventType::WorkflowExecutionStarted);
2388
- t.add_full_wf_task();
2389
- let act_scheduled_event_id = t.add_activity_task_scheduled("act-id");
2390
- let act_started_event_id = t.add_activity_task_started(act_scheduled_event_id);
2391
- t.add_activity_task_completed(
2392
- act_scheduled_event_id,
2393
- act_started_event_id,
2394
- Default::default(),
2395
- );
2396
- t.add_full_wf_task();
2397
- t.add_workflow_execution_completed();
2388
+ t.add_workflow_task_scheduled_and_started();
2398
2389
 
2399
2390
  let mut mh = MockPollCfg::from_resp_batches(
2400
2391
  "fake_wf_id",
2401
2392
  t,
2402
- [ResponseType::ToTaskNum(1), ResponseType::ToTaskNum(2)],
2393
+ [ResponseType::ToTaskNum(1)],
2403
2394
  mock_workflow_client(),
2404
2395
  );
2405
- let first_poll = AtomicBool::new(true);
2406
2396
  mh.completion_asserts = Some(Box::new(move |c| {
2407
- if !first_poll.load(Ordering::Acquire) {
2408
- assert_matches!(c.sdk_metadata.core_used_flags.as_slice(), &[1]);
2409
- }
2410
- first_poll.store(false, Ordering::Release);
2397
+ assert_eq!(
2398
+ c.sdk_metadata
2399
+ .core_used_flags
2400
+ .iter()
2401
+ .copied()
2402
+ .collect::<HashSet<_>>(),
2403
+ CoreInternalFlags::all_except_too_high()
2404
+ .into_iter()
2405
+ .map(|f| f as u32)
2406
+ .collect()
2407
+ );
2411
2408
  }));
2412
2409
  let mut mock = build_mock_pollers(mh);
2413
2410
  mock.worker_cfg(|wc| wc.max_cached_workflows = 1);
2414
2411
  let core = mock_worker(mock);
2415
2412
 
2416
- let act = core.poll_workflow_activation().await.unwrap();
2417
- core.complete_workflow_activation(WorkflowActivationCompletion::from_cmd(
2418
- act.run_id,
2419
- schedule_activity_cmd(
2420
- 1,
2421
- "whatever",
2422
- "act-id",
2423
- ActivityCancellationType::TryCancel,
2424
- Duration::from_secs(60),
2425
- Duration::from_secs(60),
2426
- ),
2427
- ))
2428
- .await
2429
- .unwrap();
2430
-
2431
2413
  let act = core.poll_workflow_activation().await.unwrap();
2432
2414
  core.complete_execution(&act.run_id).await;
2433
2415
  core.shutdown().await;
@@ -1,7 +1,11 @@
1
1
  //! Utilities for and tracking of internal versions which alter history in incompatible ways
2
2
  //! so that we can use older code paths for workflows executed on older core versions.
3
3
 
4
- use std::collections::{BTreeSet, HashSet};
4
+ use itertools::Either;
5
+ use std::{
6
+ collections::{BTreeSet, HashSet},
7
+ iter,
8
+ };
5
9
  use temporal_sdk_core_protos::temporal::api::{
6
10
  history::v1::WorkflowTaskCompletedEventAttributes, sdk::v1::WorkflowTaskCompletedMetadata,
7
11
  workflowservice::v1::get_system_info_response,
@@ -15,7 +19,7 @@ use temporal_sdk_core_protos::temporal::api::{
15
19
  /// that removing older variants does not create any change in existing values. Removed flag
16
20
  /// variants must be reserved forever (a-la protobuf), and should be called out in a comment.
17
21
  #[repr(u32)]
18
- #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug)]
22
+ #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone, Debug, enum_iterator::Sequence)]
19
23
  pub(crate) enum CoreInternalFlags {
20
24
  /// In this flag additional checks were added to a number of state machines to ensure that
21
25
  /// the ID and type of activities, local activities, and child workflows match during replay.
@@ -28,78 +32,85 @@ pub(crate) enum CoreInternalFlags {
28
32
  }
29
33
 
30
34
  #[derive(Debug, Clone, PartialEq, Eq)]
31
- pub(crate) struct InternalFlags {
32
- enabled: bool,
33
- core: BTreeSet<CoreInternalFlags>,
34
- lang: BTreeSet<u32>,
35
- core_since_last_complete: HashSet<CoreInternalFlags>,
36
- lang_since_last_complete: HashSet<u32>,
35
+ pub(crate) enum InternalFlags {
36
+ Enabled {
37
+ core: BTreeSet<CoreInternalFlags>,
38
+ lang: BTreeSet<u32>,
39
+ core_since_last_complete: HashSet<CoreInternalFlags>,
40
+ lang_since_last_complete: HashSet<u32>,
41
+ },
42
+ Disabled,
37
43
  }
38
44
 
39
45
  impl InternalFlags {
40
46
  pub fn new(server_capabilities: &get_system_info_response::Capabilities) -> Self {
41
- Self {
42
- enabled: server_capabilities.sdk_metadata,
43
- core: Default::default(),
44
- lang: Default::default(),
45
- core_since_last_complete: Default::default(),
46
- lang_since_last_complete: Default::default(),
47
- }
48
- }
49
-
50
- #[cfg(test)]
51
- pub fn all_core_enabled() -> Self {
52
- Self {
53
- enabled: true,
54
- core: BTreeSet::from([
55
- CoreInternalFlags::IdAndTypeDeterminismChecks,
56
- CoreInternalFlags::UpsertSearchAttributeOnPatch,
57
- ]),
58
- lang: Default::default(),
59
- core_since_last_complete: Default::default(),
60
- lang_since_last_complete: Default::default(),
47
+ match server_capabilities.sdk_metadata {
48
+ true => Self::Enabled {
49
+ core: Default::default(),
50
+ lang: Default::default(),
51
+ core_since_last_complete: Default::default(),
52
+ lang_since_last_complete: Default::default(),
53
+ },
54
+ false => Self::Disabled,
61
55
  }
62
56
  }
63
57
 
64
58
  pub fn add_from_complete(&mut self, e: &WorkflowTaskCompletedEventAttributes) {
65
- if !self.enabled {
66
- return;
67
- }
68
-
69
- if let Some(metadata) = e.sdk_metadata.as_ref() {
70
- self.core.extend(
71
- metadata
72
- .core_used_flags
73
- .iter()
74
- .map(|u| CoreInternalFlags::from_u32(*u)),
75
- );
76
- self.lang.extend(metadata.lang_used_flags.iter());
59
+ if let Self::Enabled { core, lang, .. } = self {
60
+ if let Some(metadata) = e.sdk_metadata.as_ref() {
61
+ core.extend(
62
+ metadata
63
+ .core_used_flags
64
+ .iter()
65
+ .map(|u| CoreInternalFlags::from_u32(*u)),
66
+ );
67
+ lang.extend(metadata.lang_used_flags.iter());
68
+ }
77
69
  }
78
70
  }
79
71
 
80
72
  pub fn add_lang_used(&mut self, flags: impl IntoIterator<Item = u32>) {
81
- if !self.enabled {
82
- return;
73
+ if let Self::Enabled {
74
+ lang_since_last_complete,
75
+ ..
76
+ } = self
77
+ {
78
+ lang_since_last_complete.extend(flags.into_iter());
83
79
  }
84
-
85
- self.lang_since_last_complete.extend(flags.into_iter());
86
80
  }
87
81
 
88
82
  /// Returns true if this flag may currently be used. If `should_record` is true, always returns
89
83
  /// true and records the flag as being used, for taking later via
90
84
  /// [Self::gather_for_wft_complete].
91
85
  pub fn try_use(&mut self, core_patch: CoreInternalFlags, should_record: bool) -> bool {
92
- if !self.enabled {
86
+ match self {
87
+ Self::Enabled {
88
+ core,
89
+ core_since_last_complete,
90
+ ..
91
+ } => {
92
+ if should_record {
93
+ core_since_last_complete.insert(core_patch);
94
+ true
95
+ } else {
96
+ core.contains(&core_patch)
97
+ }
98
+ }
93
99
  // If the server does not support the metadata field, we must assume we can never use
94
100
  // any internal flags since they can't be recorded for future use
95
- return false;
101
+ Self::Disabled => false,
96
102
  }
103
+ }
97
104
 
98
- if should_record {
99
- self.core_since_last_complete.insert(core_patch);
100
- true
101
- } else {
102
- self.core.contains(&core_patch)
105
+ /// Writes all known core flags to the set which should be recorded in the current WFT if not
106
+ /// already known. Must only be called if not replaying.
107
+ pub fn write_all_known(&mut self) {
108
+ if let Self::Enabled {
109
+ core_since_last_complete,
110
+ ..
111
+ } = self
112
+ {
113
+ core_since_last_complete.extend(CoreInternalFlags::all_except_too_high());
103
114
  }
104
115
  }
105
116
 
@@ -107,18 +118,39 @@ impl InternalFlags {
107
118
  /// sdk metadata message that can be combined with any existing data before sending the WFT
108
119
  /// complete
109
120
  pub fn gather_for_wft_complete(&mut self) -> WorkflowTaskCompletedMetadata {
110
- WorkflowTaskCompletedMetadata {
111
- core_used_flags: self
112
- .core_since_last_complete
113
- .drain()
114
- .map(|p| p as u32)
115
- .collect(),
116
- lang_used_flags: self.lang_since_last_complete.drain().collect(),
121
+ match self {
122
+ Self::Enabled {
123
+ core_since_last_complete,
124
+ lang_since_last_complete,
125
+ core,
126
+ lang,
127
+ } => {
128
+ let core_newly_used: Vec<_> = core_since_last_complete
129
+ .iter()
130
+ .filter(|f| !core.contains(f))
131
+ .map(|p| *p as u32)
132
+ .collect();
133
+ let lang_newly_used: Vec<_> = lang_since_last_complete
134
+ .iter()
135
+ .filter(|f| !lang.contains(f))
136
+ .copied()
137
+ .collect();
138
+ core.extend(core_since_last_complete.iter());
139
+ lang.extend(lang_since_last_complete.iter());
140
+ WorkflowTaskCompletedMetadata {
141
+ core_used_flags: core_newly_used,
142
+ lang_used_flags: lang_newly_used,
143
+ }
144
+ }
145
+ Self::Disabled => WorkflowTaskCompletedMetadata::default(),
117
146
  }
118
147
  }
119
148
 
120
- pub fn all_lang(&self) -> &BTreeSet<u32> {
121
- &self.lang
149
+ pub fn all_lang(&self) -> impl Iterator<Item = u32> + '_ {
150
+ match self {
151
+ Self::Enabled { lang, .. } => Either::Left(lang.iter().copied()),
152
+ Self::Disabled => Either::Right(iter::empty()),
153
+ }
122
154
  }
123
155
  }
124
156
 
@@ -130,6 +162,11 @@ impl CoreInternalFlags {
130
162
  _ => Self::TooHigh,
131
163
  }
132
164
  }
165
+
166
+ pub fn all_except_too_high() -> impl Iterator<Item = CoreInternalFlags> {
167
+ enum_iterator::all::<CoreInternalFlags>()
168
+ .filter(|f| !matches!(f, CoreInternalFlags::TooHigh))
169
+ }
133
170
  }
134
171
 
135
172
  #[cfg(test)]
@@ -152,4 +189,39 @@ mod tests {
152
189
  assert_matches!(gathered.core_used_flags.as_slice(), &[]);
153
190
  assert_matches!(gathered.lang_used_flags.as_slice(), &[]);
154
191
  }
192
+
193
+ #[test]
194
+ fn all_have_u32_from_impl() {
195
+ let all_known = CoreInternalFlags::all_except_too_high();
196
+ for flag in all_known {
197
+ let as_u32 = flag as u32;
198
+ assert_eq!(CoreInternalFlags::from_u32(as_u32), flag);
199
+ }
200
+ }
201
+
202
+ #[test]
203
+ fn only_writes_new_flags() {
204
+ let mut f = InternalFlags::new(&Capabilities {
205
+ sdk_metadata: true,
206
+ ..Default::default()
207
+ });
208
+ f.add_lang_used([1]);
209
+ f.try_use(CoreInternalFlags::IdAndTypeDeterminismChecks, true);
210
+ let gathered = f.gather_for_wft_complete();
211
+ assert_matches!(gathered.core_used_flags.as_slice(), &[1]);
212
+ assert_matches!(gathered.lang_used_flags.as_slice(), &[1]);
213
+
214
+ f.add_from_complete(&WorkflowTaskCompletedEventAttributes {
215
+ sdk_metadata: Some(WorkflowTaskCompletedMetadata {
216
+ core_used_flags: vec![2],
217
+ lang_used_flags: vec![2],
218
+ }),
219
+ ..Default::default()
220
+ });
221
+ f.add_lang_used([2]);
222
+ f.try_use(CoreInternalFlags::UpsertSearchAttributeOnPatch, true);
223
+ let gathered = f.gather_for_wft_complete();
224
+ assert_matches!(gathered.core_used_flags.as_slice(), &[]);
225
+ assert_matches!(gathered.lang_used_flags.as_slice(), &[]);
226
+ }
155
227
  }
@@ -1,11 +1,14 @@
1
- use crate::abstractions::MeteredSemaphore;
2
- use crate::worker::activities::PermittedTqResp;
3
- use crate::{pollers::BoxedActPoller, MetricsContext};
1
+ use crate::{
2
+ abstractions::MeteredSemaphore, pollers::BoxedActPoller, worker::activities::PermittedTqResp,
3
+ MetricsContext,
4
+ };
4
5
  use futures::{stream, Stream};
5
- use governor::clock::DefaultClock;
6
- use governor::middleware::NoOpMiddleware;
7
- use governor::state::{InMemoryState, NotKeyed};
8
- use governor::RateLimiter;
6
+ use governor::{
7
+ clock::DefaultClock,
8
+ middleware::NoOpMiddleware,
9
+ state::{InMemoryState, NotKeyed},
10
+ RateLimiter,
11
+ };
9
12
  use std::sync::Arc;
10
13
  use temporal_sdk_core_protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse;
11
14
  use tokio::select;