@temporalio/core-bridge 1.13.0 → 1.13.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 (181) hide show
  1. package/Cargo.lock +239 -382
  2. package/Cargo.toml +11 -11
  3. package/lib/native.d.ts +10 -3
  4. package/package.json +3 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.cargo/config.toml +71 -11
  11. package/sdk-core/.clippy.toml +1 -0
  12. package/sdk-core/.github/workflows/heavy.yml +2 -0
  13. package/sdk-core/.github/workflows/per-pr.yml +50 -18
  14. package/sdk-core/ARCHITECTURE.md +44 -48
  15. package/sdk-core/Cargo.toml +26 -7
  16. package/sdk-core/README.md +4 -0
  17. package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
  18. package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
  19. package/sdk-core/arch_docs/sdks_intro.md +299 -0
  20. package/sdk-core/client/Cargo.toml +8 -7
  21. package/sdk-core/client/src/callback_based.rs +1 -2
  22. package/sdk-core/client/src/lib.rs +485 -299
  23. package/sdk-core/client/src/metrics.rs +32 -8
  24. package/sdk-core/client/src/proxy.rs +124 -5
  25. package/sdk-core/client/src/raw.rs +598 -307
  26. package/sdk-core/client/src/replaceable.rs +253 -0
  27. package/sdk-core/client/src/retry.rs +9 -6
  28. package/sdk-core/client/src/worker_registry/mod.rs +19 -3
  29. package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
  30. package/sdk-core/core/Cargo.toml +100 -31
  31. package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
  32. package/sdk-core/core/src/core_tests/mod.rs +2 -8
  33. package/sdk-core/core/src/core_tests/queries.rs +3 -5
  34. package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
  35. package/sdk-core/core/src/core_tests/updates.rs +4 -5
  36. package/sdk-core/core/src/core_tests/workers.rs +4 -3
  37. package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
  38. package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
  39. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
  40. package/sdk-core/core/src/internal_flags.rs +11 -1
  41. package/sdk-core/core/src/lib.rs +50 -36
  42. package/sdk-core/core/src/pollers/mod.rs +5 -5
  43. package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
  44. package/sdk-core/core/src/protosext/mod.rs +13 -5
  45. package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
  46. package/sdk-core/core/src/retry_logic.rs +256 -108
  47. package/sdk-core/core/src/telemetry/metrics.rs +1 -0
  48. package/sdk-core/core/src/telemetry/mod.rs +8 -2
  49. package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
  50. package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
  51. package/sdk-core/core/src/test_help/mod.rs +10 -1100
  52. package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
  53. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
  54. package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
  55. package/sdk-core/core/src/worker/activities.rs +10 -3
  56. package/sdk-core/core/src/worker/client/mocks.rs +3 -3
  57. package/sdk-core/core/src/worker/client.rs +130 -93
  58. package/sdk-core/core/src/worker/heartbeat.rs +12 -13
  59. package/sdk-core/core/src/worker/mod.rs +31 -21
  60. package/sdk-core/core/src/worker/nexus.rs +14 -3
  61. package/sdk-core/core/src/worker/slot_provider.rs +9 -0
  62. package/sdk-core/core/src/worker/tuner.rs +159 -0
  63. package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
  64. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
  65. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
  66. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
  67. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
  68. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
  69. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
  70. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
  71. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
  72. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
  73. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
  74. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
  75. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
  76. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
  77. package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
  78. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
  79. package/sdk-core/core-api/Cargo.toml +4 -4
  80. package/sdk-core/core-api/src/envconfig.rs +153 -54
  81. package/sdk-core/core-api/src/lib.rs +68 -0
  82. package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
  83. package/sdk-core/core-api/src/telemetry.rs +13 -0
  84. package/sdk-core/core-c-bridge/Cargo.toml +13 -8
  85. package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
  86. package/sdk-core/core-c-bridge/src/client.rs +462 -184
  87. package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
  88. package/sdk-core/core-c-bridge/src/lib.rs +1 -0
  89. package/sdk-core/core-c-bridge/src/random.rs +4 -4
  90. package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
  91. package/sdk-core/core-c-bridge/src/testing.rs +1 -4
  92. package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
  93. package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
  94. package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
  95. package/sdk-core/core-c-bridge/src/worker.rs +319 -66
  96. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
  97. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
  98. package/sdk-core/sdk/Cargo.toml +8 -2
  99. package/sdk-core/sdk/src/activity_context.rs +1 -1
  100. package/sdk-core/sdk/src/app_data.rs +1 -1
  101. package/sdk-core/sdk/src/interceptors.rs +1 -4
  102. package/sdk-core/sdk/src/lib.rs +1 -5
  103. package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
  104. package/sdk-core/sdk/src/workflow_future.rs +1 -1
  105. package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
  106. package/sdk-core/sdk-core-protos/build.rs +10 -23
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
  115. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  116. package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
  117. package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
  118. package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
  119. package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
  120. package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
  121. package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
  122. package/sdk-core/tests/cloud_tests.rs +10 -8
  123. package/sdk-core/tests/common/http_proxy.rs +134 -0
  124. package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
  125. package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
  126. package/sdk-core/tests/fuzzy_workflow.rs +1 -1
  127. package/sdk-core/tests/global_metric_tests.rs +8 -7
  128. package/sdk-core/tests/heavy_tests.rs +7 -3
  129. package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
  130. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
  131. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
  132. package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
  133. package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
  134. package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
  135. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
  136. package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
  137. package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
  138. package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
  139. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
  140. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
  141. package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
  142. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
  143. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
  144. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
  145. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
  146. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
  147. package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
  148. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
  149. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
  150. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
  151. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
  152. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
  153. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
  154. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
  155. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
  156. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
  157. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
  158. package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
  159. package/sdk-core/tests/main.rs +26 -17
  160. package/sdk-core/tests/manual_tests.rs +5 -1
  161. package/sdk-core/tests/runner.rs +22 -40
  162. package/sdk-core/tests/shared_tests/mod.rs +1 -1
  163. package/sdk-core/tests/shared_tests/priority.rs +1 -1
  164. package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
  165. package/src/client.rs +97 -20
  166. package/src/helpers/callbacks.rs +4 -4
  167. package/src/helpers/errors.rs +7 -1
  168. package/src/helpers/handles.rs +1 -0
  169. package/src/helpers/try_from_js.rs +4 -3
  170. package/src/lib.rs +3 -2
  171. package/src/metrics.rs +3 -0
  172. package/src/runtime.rs +5 -2
  173. package/src/worker.rs +9 -12
  174. package/ts/native.ts +13 -3
  175. package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
  176. package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
  177. package/sdk-core/core/src/core_tests/determinism.rs +0 -318
  178. package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
  179. package/sdk-core/test-utils/Cargo.toml +0 -38
  180. package/sdk-core/test-utils/src/histfetch.rs +0 -28
  181. package/sdk-core/test-utils/src/interceptors.rs +0 -46
@@ -1,35 +1,67 @@
1
- use crate::shared_tests;
1
+ use crate::{
2
+ common::{CoreWfStarter, get_integ_server_options, get_integ_telem_options, mock_sdk_cfg},
3
+ shared_tests,
4
+ };
2
5
  use assert_matches::assert_matches;
6
+ use futures_util::FutureExt;
3
7
  use std::{
4
8
  cell::Cell,
5
9
  sync::{
6
- Arc,
10
+ Arc, Mutex,
7
11
  atomic::{AtomicBool, Ordering::Relaxed},
8
12
  },
9
13
  time::Duration,
10
14
  };
11
15
  use temporal_client::WorkflowOptions;
12
- use temporal_sdk::{WfContext, interceptors::WorkerInterceptor};
13
- use temporal_sdk_core::{CoreRuntime, ResourceBasedTuner, ResourceSlotOptions, init_worker};
16
+ use temporal_sdk::{
17
+ ActivityOptions, LocalActivityOptions, WfContext, interceptors::WorkerInterceptor,
18
+ };
19
+ use temporal_sdk_core::{
20
+ CoreRuntime, ResourceBasedTuner, ResourceSlotOptions, TunerBuilder, init_worker,
21
+ test_help::{
22
+ FakeWfResponses, MockPollCfg, ResponseType, TEST_Q, build_mock_pollers,
23
+ drain_pollers_and_shutdown, hist_to_poll_resp, mock_worker, mock_worker_client,
24
+ },
25
+ };
14
26
  use temporal_sdk_core_api::{
15
27
  Worker,
16
28
  errors::WorkerValidationError,
17
- worker::{PollerBehavior, WorkerConfigBuilder, WorkerVersioningStrategy},
29
+ worker::{
30
+ ActivitySlotKind, LocalActivitySlotKind, PollerBehavior, SlotInfo, SlotInfoTrait,
31
+ SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext, SlotSupplier,
32
+ SlotSupplierPermit, WorkerConfigBuilder, WorkerVersioningStrategy, WorkflowSlotKind,
33
+ },
18
34
  };
19
35
  use temporal_sdk_core_protos::{
20
- coresdk::workflow_completion::{
21
- Failure, WorkflowActivationCompletion, workflow_activation_completion::Status,
36
+ DEFAULT_WORKFLOW_TYPE, TestHistoryBuilder, canned_histories,
37
+ coresdk::{
38
+ ActivityTaskCompletion,
39
+ activity_result::ActivityExecutionResult,
40
+ workflow_completion::{
41
+ Failure, WorkflowActivationCompletion, workflow_activation_completion::Status,
42
+ },
22
43
  },
23
44
  temporal::api::{
24
- enums::v1::{EventType, WorkflowTaskFailedCause::GrpcMessageTooLarge},
45
+ command::v1::command::Attributes,
46
+ common::v1::WorkerVersionStamp,
47
+ enums::v1::{
48
+ EventType, WorkflowTaskFailedCause, WorkflowTaskFailedCause::GrpcMessageTooLarge,
49
+ },
25
50
  failure::v1::Failure as InnerFailure,
26
- history::v1::history_event::Attributes::WorkflowTaskFailedEventAttributes,
51
+ history::v1::{
52
+ ActivityTaskScheduledEventAttributes, history_event,
53
+ history_event::Attributes::{
54
+ self as EventAttributes, WorkflowTaskFailedEventAttributes,
55
+ },
56
+ },
57
+ workflowservice::v1::{
58
+ GetWorkflowExecutionHistoryResponse, PollActivityTaskQueueResponse,
59
+ RespondActivityTaskCompletedResponse,
60
+ },
27
61
  },
28
62
  };
29
- use temporal_sdk_core_test_utils::{
30
- CoreWfStarter, drain_pollers_and_shutdown, get_integ_server_options, get_integ_telem_options,
31
- };
32
- use tokio::sync::Notify;
63
+ use tokio::sync::{Barrier, Notify, Semaphore};
64
+ use tokio_util::sync::CancellationToken;
33
65
  use uuid::Uuid;
34
66
 
35
67
  #[tokio::test]
@@ -201,3 +233,626 @@ async fn oversize_grpc_message() {
201
233
  async fn grpc_message_too_large_test() {
202
234
  shared_tests::grpc_message_too_large().await
203
235
  }
236
+
237
+ #[tokio::test]
238
+ async fn activity_tasks_from_completion_reserve_slots() {
239
+ let wf_id = "fake_wf_id";
240
+ let mut t = TestHistoryBuilder::default();
241
+ t.add_by_type(EventType::WorkflowExecutionStarted);
242
+ t.add_full_wf_task();
243
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
244
+ ActivityTaskScheduledEventAttributes {
245
+ activity_id: "1".to_string(),
246
+ activity_type: Some("act1".into()),
247
+ ..Default::default()
248
+ },
249
+ ));
250
+ let startid = t.add_activity_task_started(schedid);
251
+ t.add_activity_task_completed(schedid, startid, b"hi".into());
252
+ t.add_full_wf_task();
253
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
254
+ ActivityTaskScheduledEventAttributes {
255
+ activity_id: "2".to_string(),
256
+ activity_type: Some("act2".into()),
257
+ ..Default::default()
258
+ },
259
+ ));
260
+ let startid = t.add_activity_task_started(schedid);
261
+ t.add_activity_task_completed(schedid, startid, b"hi".into());
262
+ t.add_full_wf_task();
263
+ t.add_workflow_execution_completed();
264
+
265
+ let mut mock = mock_worker_client();
266
+ // Set up two tasks to be returned via normal activity polling
267
+ let act_tasks = vec![
268
+ PollActivityTaskQueueResponse {
269
+ task_token: vec![1],
270
+ activity_id: "act1".to_string(),
271
+ ..Default::default()
272
+ }
273
+ .into(),
274
+ PollActivityTaskQueueResponse {
275
+ task_token: vec![2],
276
+ activity_id: "act2".to_string(),
277
+ ..Default::default()
278
+ }
279
+ .into(),
280
+ ];
281
+ mock.expect_complete_activity_task()
282
+ .times(2)
283
+ .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
284
+ let barr: &'static Barrier = Box::leak(Box::new(Barrier::new(2)));
285
+ let mut mh = MockPollCfg::from_resp_batches(
286
+ wf_id,
287
+ t,
288
+ [
289
+ ResponseType::ToTaskNum(1),
290
+ // We don't want the second task to be delivered until *after* the activity tasks
291
+ // have been completed, so that the second activity schedule will have slots available
292
+ ResponseType::UntilResolved(
293
+ async {
294
+ barr.wait().await;
295
+ barr.wait().await;
296
+ }
297
+ .boxed(),
298
+ 2,
299
+ ),
300
+ ResponseType::AllHistory,
301
+ ],
302
+ mock,
303
+ );
304
+ mh.completion_mock_fn = Some(Box::new(|wftc| {
305
+ // Make sure when we see the completion with the schedule act command that it does
306
+ // not have the eager execution flag set the first time, and does the second.
307
+ if let Some(Attributes::ScheduleActivityTaskCommandAttributes(attrs)) = wftc
308
+ .commands
309
+ .first()
310
+ .and_then(|cmd| cmd.attributes.as_ref())
311
+ {
312
+ if attrs.activity_id == "1" {
313
+ assert!(!attrs.request_eager_execution);
314
+ } else {
315
+ assert!(attrs.request_eager_execution);
316
+ }
317
+ }
318
+ Ok(Default::default())
319
+ }));
320
+ mh.activity_responses = Some(act_tasks);
321
+ let mut mock = build_mock_pollers(mh);
322
+ mock.worker_cfg(|cfg| {
323
+ cfg.max_cached_workflows = 2;
324
+ cfg.max_outstanding_activities = Some(2);
325
+ });
326
+ let core = Arc::new(mock_worker(mock));
327
+ let mut worker = crate::common::TestWorker::new(core.clone(), TEST_Q.to_string());
328
+
329
+ // First poll for activities twice, occupying both slots
330
+ let at1 = core.poll_activity_task().await.unwrap();
331
+ let at2 = core.poll_activity_task().await.unwrap();
332
+ let workflow_complete_token = CancellationToken::new();
333
+ let workflow_complete_token_clone = workflow_complete_token.clone();
334
+
335
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| {
336
+ let complete_token = workflow_complete_token.clone();
337
+ async move {
338
+ ctx.activity(ActivityOptions {
339
+ activity_type: "act1".to_string(),
340
+ ..Default::default()
341
+ })
342
+ .await;
343
+ ctx.activity(ActivityOptions {
344
+ activity_type: "act2".to_string(),
345
+ ..Default::default()
346
+ })
347
+ .await;
348
+ complete_token.cancel();
349
+ Ok(().into())
350
+ }
351
+ });
352
+
353
+ worker
354
+ .submit_wf(
355
+ wf_id.to_owned(),
356
+ DEFAULT_WORKFLOW_TYPE,
357
+ vec![],
358
+ WorkflowOptions::default(),
359
+ )
360
+ .await
361
+ .unwrap();
362
+ let act_completer = async {
363
+ barr.wait().await;
364
+ core.complete_activity_task(ActivityTaskCompletion {
365
+ task_token: at1.task_token,
366
+ result: Some(ActivityExecutionResult::ok("hi".into())),
367
+ })
368
+ .await
369
+ .unwrap();
370
+ core.complete_activity_task(ActivityTaskCompletion {
371
+ task_token: at2.task_token,
372
+ result: Some(ActivityExecutionResult::ok("hi".into())),
373
+ })
374
+ .await
375
+ .unwrap();
376
+ barr.wait().await;
377
+ // Wait for workflow to complete in order for all eager activities to be requested before shutting down.
378
+ // After shutdown, no eager activities slots can be allocated.
379
+ workflow_complete_token_clone.cancelled().await;
380
+ core.initiate_shutdown();
381
+ // Even though this test requests eager activity tasks, none are returned in poll responses.
382
+ let err = core.poll_activity_task().await.unwrap_err();
383
+ assert_matches!(err, temporal_sdk_core_api::errors::PollError::ShutDown);
384
+ };
385
+ // This wf poll should *not* set the flag that it wants tasks back since both slots are
386
+ // occupied
387
+ let run_fut = async { worker.run_until_done().await.unwrap() };
388
+ tokio::join!(run_fut, act_completer);
389
+ }
390
+
391
+ #[tokio::test]
392
+ async fn max_wft_respected() {
393
+ let total_wfs = 100;
394
+ let wf_ids: Vec<_> = (0..total_wfs).map(|i| format!("fake-wf-{i}")).collect();
395
+ let hists = wf_ids.iter().map(|wf_id| {
396
+ let hist = canned_histories::single_timer("1");
397
+ FakeWfResponses {
398
+ wf_id: wf_id.to_string(),
399
+ hist,
400
+ response_batches: vec![1.into(), 2.into()],
401
+ }
402
+ });
403
+ let mh = MockPollCfg::new(hists.into_iter().collect(), true, 0);
404
+ let mut worker = mock_sdk_cfg(mh, |cfg| {
405
+ cfg.max_cached_workflows = total_wfs as usize;
406
+ cfg.max_outstanding_workflow_tasks = Some(1);
407
+ });
408
+ let active_count: &'static _ = Box::leak(Box::new(Semaphore::new(1)));
409
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
410
+ drop(
411
+ active_count
412
+ .try_acquire()
413
+ .expect("No multiple concurrent workflow tasks!"),
414
+ );
415
+ ctx.timer(Duration::from_secs(1)).await;
416
+ Ok(().into())
417
+ });
418
+
419
+ for wf_id in wf_ids {
420
+ worker
421
+ .submit_wf(wf_id, DEFAULT_WORKFLOW_TYPE, vec![], Default::default())
422
+ .await
423
+ .unwrap();
424
+ }
425
+ worker.run_until_done().await.unwrap();
426
+ }
427
+
428
+ #[rstest]
429
+ #[tokio::test]
430
+ async fn history_length_with_fail_and_timeout(
431
+ #[values(true, false)] use_cache: bool,
432
+ #[values(1, 2, 3)] history_responses_case: u8,
433
+ ) {
434
+ if !use_cache && history_responses_case == 3 {
435
+ eprintln!(
436
+ "Skipping history_length_with_fail_and_timeout::use_cache_2_false::history_responses_case_3_3 due to flaky hang"
437
+ );
438
+ return;
439
+ }
440
+ let wfid = "fake_wf_id";
441
+ let mut t = TestHistoryBuilder::default();
442
+ t.add_by_type(EventType::WorkflowExecutionStarted);
443
+ t.add_full_wf_task();
444
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
445
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
446
+ t.add_workflow_task_scheduled_and_started();
447
+ t.add_workflow_task_failed_with_failure(WorkflowTaskFailedCause::Unspecified, "ahh".into());
448
+ t.add_workflow_task_scheduled_and_started();
449
+ t.add_workflow_task_timed_out();
450
+ t.add_full_wf_task();
451
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
452
+ t.add_timer_fired(timer_started_event_id, "2".to_string());
453
+ t.add_full_wf_task();
454
+ t.add_workflow_execution_completed();
455
+
456
+ let mut mock_client = mock_worker_client();
457
+ let history_responses = match history_responses_case {
458
+ 1 => vec![ResponseType::AllHistory],
459
+ 2 => vec![
460
+ ResponseType::ToTaskNum(1),
461
+ ResponseType::ToTaskNum(2),
462
+ ResponseType::AllHistory,
463
+ ],
464
+ 3 => {
465
+ let mut needs_fetch = hist_to_poll_resp(&t, wfid, ResponseType::ToTaskNum(2)).resp;
466
+ needs_fetch.next_page_token = vec![1];
467
+ // Truncate the history a bit in order to force incomplete WFT
468
+ needs_fetch.history.as_mut().unwrap().events.truncate(6);
469
+ let needs_fetch_resp = ResponseType::Raw(needs_fetch);
470
+ let mut empty_fetch_resp: GetWorkflowExecutionHistoryResponse =
471
+ t.get_history_info(1).unwrap().into();
472
+ empty_fetch_resp.history.as_mut().unwrap().events = vec![];
473
+ mock_client
474
+ .expect_get_workflow_execution_history()
475
+ .returning(move |_, _, _| Ok(empty_fetch_resp.clone()))
476
+ .times(1);
477
+ vec![
478
+ ResponseType::ToTaskNum(1),
479
+ needs_fetch_resp,
480
+ ResponseType::ToTaskNum(2),
481
+ ResponseType::AllHistory,
482
+ ]
483
+ }
484
+ _ => unreachable!(),
485
+ };
486
+
487
+ let mut mh = MockPollCfg::from_resp_batches(wfid, t, history_responses, mock_client);
488
+ if history_responses_case == 3 {
489
+ // Expect the failed pagination fetch
490
+ mh.num_expected_fails = 1;
491
+ }
492
+ let mut worker = mock_sdk_cfg(mh, |wc| {
493
+ if use_cache {
494
+ wc.max_cached_workflows = 1;
495
+ }
496
+ });
497
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, |ctx: WfContext| async move {
498
+ assert_eq!(ctx.history_length(), 3);
499
+ ctx.timer(Duration::from_secs(1)).await;
500
+ assert_eq!(ctx.history_length(), 14);
501
+ ctx.timer(Duration::from_secs(1)).await;
502
+ assert_eq!(ctx.history_length(), 19);
503
+ Ok(().into())
504
+ });
505
+ worker
506
+ .submit_wf(
507
+ wfid.to_owned(),
508
+ DEFAULT_WORKFLOW_TYPE.to_owned(),
509
+ vec![],
510
+ WorkflowOptions::default(),
511
+ )
512
+ .await
513
+ .unwrap();
514
+ worker.run_until_done().await.unwrap();
515
+ }
516
+
517
+ #[allow(deprecated)]
518
+ #[tokio::test]
519
+ async fn sets_build_id_from_wft_complete() {
520
+ let wfid = "fake_wf_id";
521
+
522
+ let mut t = TestHistoryBuilder::default();
523
+ t.add_by_type(EventType::WorkflowExecutionStarted);
524
+ t.add_full_wf_task();
525
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
526
+ t.add_timer_fired(timer_started_event_id, "1".to_string());
527
+ t.add_full_wf_task();
528
+ t.modify_event(t.current_event_id(), |he| {
529
+ if let history_event::Attributes::WorkflowTaskCompletedEventAttributes(a) =
530
+ he.attributes.as_mut().unwrap()
531
+ {
532
+ a.worker_version = Some(WorkerVersionStamp {
533
+ build_id: "enchi-cat".to_string(),
534
+ ..Default::default()
535
+ });
536
+ }
537
+ });
538
+ let timer_started_event_id = t.add_by_type(EventType::TimerStarted);
539
+ t.add_timer_fired(timer_started_event_id, "2".to_string());
540
+ t.add_workflow_task_scheduled_and_started();
541
+
542
+ let mock = mock_worker_client();
543
+ let mut worker = mock_sdk_cfg(
544
+ MockPollCfg::from_resp_batches(wfid, t, [ResponseType::AllHistory], mock),
545
+ |cfg| {
546
+ cfg.versioning_strategy = WorkerVersioningStrategy::None {
547
+ build_id: "fierce-predator".to_string(),
548
+ };
549
+ cfg.max_cached_workflows = 1;
550
+ },
551
+ );
552
+
553
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, |ctx: WfContext| async move {
554
+ // First task, it should be empty, since replaying and nothing in first WFT completed
555
+ assert_eq!(ctx.current_deployment_version(), None);
556
+ ctx.timer(Duration::from_secs(1)).await;
557
+ assert_eq!(
558
+ ctx.current_deployment_version().unwrap().build_id,
559
+ "enchi-cat"
560
+ );
561
+ ctx.timer(Duration::from_secs(1)).await;
562
+ // Not replaying at this point, so we should see the worker's build id
563
+ assert_eq!(
564
+ ctx.current_deployment_version().unwrap().build_id,
565
+ "fierce-predator"
566
+ );
567
+ ctx.timer(Duration::from_secs(1)).await;
568
+ assert_eq!(
569
+ ctx.current_deployment_version().unwrap().build_id,
570
+ "fierce-predator"
571
+ );
572
+ Ok(().into())
573
+ });
574
+ worker
575
+ .submit_wf(wfid, DEFAULT_WORKFLOW_TYPE, vec![], Default::default())
576
+ .await
577
+ .unwrap();
578
+ worker.run_until_done().await.unwrap();
579
+ }
580
+
581
+ #[derive(Debug, Clone)]
582
+ enum SlotEvent {
583
+ ReserveSlot {
584
+ slot_type: &'static str,
585
+ },
586
+ TryReserveSlot {
587
+ slot_type: &'static str,
588
+ },
589
+ MarkSlotUsed {
590
+ slot_type: &'static str,
591
+ is_sticky: bool,
592
+ workflow_type: Option<String>,
593
+ activity_type: Option<String>,
594
+ },
595
+ ReleaseSlot {
596
+ slot_type: &'static str,
597
+ },
598
+ }
599
+
600
+ struct TrackingSlotSupplier<SK> {
601
+ events: Arc<Mutex<Vec<SlotEvent>>>,
602
+ slot_type: &'static str,
603
+ _phantom: std::marker::PhantomData<SK>,
604
+ }
605
+
606
+ impl<SK> TrackingSlotSupplier<SK> {
607
+ fn new(slot_type: &'static str) -> Self {
608
+ Self {
609
+ events: Arc::new(Mutex::new(Vec::new())),
610
+ slot_type,
611
+ _phantom: std::marker::PhantomData,
612
+ }
613
+ }
614
+
615
+ fn get_events(&self) -> Vec<SlotEvent> {
616
+ self.events.lock().unwrap().clone()
617
+ }
618
+
619
+ fn add_event(&self, event: SlotEvent) {
620
+ self.events.lock().unwrap().push(event);
621
+ }
622
+
623
+ fn extract_slot_info(info: &dyn SlotInfoTrait) -> (bool, Option<String>, Option<String>) {
624
+ match info.downcast() {
625
+ SlotInfo::Workflow(w) => (w.is_sticky, Some(w.workflow_type.clone()), None),
626
+ SlotInfo::Activity(a) => (false, None, Some(a.activity_type.clone())),
627
+ SlotInfo::LocalActivity(a) => (false, None, Some(a.activity_type.clone())),
628
+ SlotInfo::Nexus(_) => (false, None, None),
629
+ }
630
+ }
631
+ }
632
+
633
+ #[async_trait::async_trait]
634
+ impl<SK> SlotSupplier for TrackingSlotSupplier<SK>
635
+ where
636
+ SK: temporal_sdk_core_api::worker::SlotKind + Send + Sync,
637
+ SK::Info: SlotInfoTrait,
638
+ {
639
+ type SlotKind = SK;
640
+
641
+ async fn reserve_slot(&self, _ctx: &dyn SlotReservationContext) -> SlotSupplierPermit {
642
+ self.add_event(SlotEvent::ReserveSlot {
643
+ slot_type: self.slot_type,
644
+ });
645
+ SlotSupplierPermit::with_user_data(())
646
+ }
647
+
648
+ fn try_reserve_slot(&self, _ctx: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
649
+ self.add_event(SlotEvent::TryReserveSlot {
650
+ slot_type: self.slot_type,
651
+ });
652
+ Some(SlotSupplierPermit::with_user_data(()))
653
+ }
654
+
655
+ fn mark_slot_used(&self, ctx: &dyn SlotMarkUsedContext<SlotKind = Self::SlotKind>) {
656
+ let (is_sticky, workflow_type, activity_type) = Self::extract_slot_info(ctx.info());
657
+ self.add_event(SlotEvent::MarkSlotUsed {
658
+ slot_type: self.slot_type,
659
+ is_sticky,
660
+ workflow_type,
661
+ activity_type,
662
+ });
663
+ }
664
+
665
+ fn release_slot(&self, _ctx: &dyn SlotReleaseContext<SlotKind = Self::SlotKind>) {
666
+ self.add_event(SlotEvent::ReleaseSlot {
667
+ slot_type: self.slot_type,
668
+ });
669
+ }
670
+ }
671
+
672
+ #[tokio::test]
673
+ async fn test_custom_slot_supplier_simple() {
674
+ let wf_supplier = Arc::new(TrackingSlotSupplier::<WorkflowSlotKind>::new("workflow"));
675
+ let activity_supplier = Arc::new(TrackingSlotSupplier::<ActivitySlotKind>::new("activity"));
676
+ let local_activity_supplier = Arc::new(TrackingSlotSupplier::<LocalActivitySlotKind>::new(
677
+ "local_activity",
678
+ ));
679
+
680
+ let mut starter = CoreWfStarter::new("test_custom_slot_supplier_simple");
681
+ starter.worker_config.clear_max_outstanding_opts();
682
+
683
+ let mut tb = TunerBuilder::default();
684
+ tb.workflow_slot_supplier(wf_supplier.clone());
685
+ tb.activity_slot_supplier(activity_supplier.clone());
686
+ tb.local_activity_slot_supplier(local_activity_supplier.clone());
687
+ starter.worker_config.tuner(Arc::new(tb.build()));
688
+
689
+ let mut worker = starter.worker().await;
690
+
691
+ worker.register_activity(
692
+ "SlotSupplierActivity",
693
+ |_: temporal_sdk::ActContext, _: ()| async move { Ok(()) },
694
+ );
695
+ worker.register_wf(
696
+ "SlotSupplierWorkflow".to_owned(),
697
+ |ctx: WfContext| async move {
698
+ let _result = ctx
699
+ .activity(ActivityOptions {
700
+ activity_type: "SlotSupplierActivity".to_string(),
701
+ start_to_close_timeout: Some(Duration::from_secs(10)),
702
+ ..Default::default()
703
+ })
704
+ .await;
705
+ let _result = ctx
706
+ .local_activity(LocalActivityOptions {
707
+ activity_type: "SlotSupplierActivity".to_string(),
708
+ start_to_close_timeout: Some(Duration::from_secs(10)),
709
+ ..Default::default()
710
+ })
711
+ .await;
712
+ Ok(().into())
713
+ },
714
+ );
715
+
716
+ worker
717
+ .submit_wf(
718
+ "test-wf".to_owned(),
719
+ "SlotSupplierWorkflow".to_owned(),
720
+ vec![],
721
+ Default::default(),
722
+ )
723
+ .await
724
+ .unwrap();
725
+
726
+ worker.run_until_done().await.unwrap();
727
+
728
+ // Collect all events
729
+ let wf_events = wf_supplier.get_events();
730
+ let activity_events = activity_supplier.get_events();
731
+ let local_activity_events = local_activity_supplier.get_events();
732
+
733
+ // Verify workflow slot events - should have reserve, mark used, and release events
734
+ assert!(wf_events.iter().any(
735
+ |e| matches!(e, SlotEvent::ReserveSlot { slot_type, .. } if *slot_type == "workflow")
736
+ ));
737
+ assert!(wf_events.iter().any(
738
+ |e| matches!(e, SlotEvent::MarkSlotUsed { slot_type, .. } if *slot_type == "workflow")
739
+ ));
740
+ assert!(
741
+ wf_events
742
+ .iter()
743
+ .any(|e| matches!(e, SlotEvent::ReleaseSlot { slot_type } if *slot_type == "workflow"))
744
+ );
745
+
746
+ // Verify activity slot events - should have reserve, try_reserve (for eager execution), mark
747
+ // used, and release
748
+ assert!(activity_events.iter().any(
749
+ |e| matches!(e, SlotEvent::ReserveSlot { slot_type, .. } if *slot_type == "activity")
750
+ ));
751
+ assert!(
752
+ activity_events.iter().any(
753
+ |e| matches!(e, SlotEvent::TryReserveSlot { slot_type } if *slot_type == "activity")
754
+ )
755
+ );
756
+ assert!(activity_events.iter().any(
757
+ |e| matches!(e, SlotEvent::MarkSlotUsed { slot_type, .. } if *slot_type == "activity")
758
+ ));
759
+ assert!(
760
+ activity_events
761
+ .iter()
762
+ .any(|e| matches!(e, SlotEvent::ReleaseSlot { slot_type } if *slot_type == "activity"))
763
+ );
764
+
765
+ // Verify local activity slot events
766
+ assert!(local_activity_events.iter().any(
767
+ |e| matches!(e, SlotEvent::ReserveSlot { slot_type, .. } if *slot_type == "local_activity")
768
+ ));
769
+ assert!(local_activity_events.iter().any(
770
+ |e| matches!(e, SlotEvent::MarkSlotUsed { slot_type, .. } if *slot_type == "local_activity")
771
+ ));
772
+ assert!(local_activity_events.iter().any(
773
+ |e| matches!(e, SlotEvent::ReleaseSlot { slot_type } if *slot_type == "local_activity")
774
+ ));
775
+
776
+ assert!(
777
+ wf_events
778
+ .iter()
779
+ .any(|e| matches!(e, SlotEvent::MarkSlotUsed {
780
+ slot_type: "workflow",
781
+ workflow_type: Some(wf_type),
782
+ ..
783
+ } if wf_type == "SlotSupplierWorkflow"))
784
+ );
785
+ assert!(
786
+ activity_events
787
+ .iter()
788
+ .any(|e| matches!(e, SlotEvent::MarkSlotUsed {
789
+ slot_type: "activity",
790
+ activity_type: Some(act_type),
791
+ ..
792
+ } if act_type == "SlotSupplierActivity"))
793
+ );
794
+ assert!(
795
+ local_activity_events
796
+ .iter()
797
+ .any(|e| matches!(e, SlotEvent::MarkSlotUsed {
798
+ slot_type: "local_activity",
799
+ activity_type: Some(act_type),
800
+ ..
801
+ } if act_type == "SlotSupplierActivity"))
802
+ );
803
+ assert!(wf_events.iter().any(|e| matches!(
804
+ e,
805
+ SlotEvent::MarkSlotUsed {
806
+ slot_type: "workflow",
807
+ is_sticky: false,
808
+ ..
809
+ }
810
+ )));
811
+
812
+ // Verify that the number of reserve/try_reserve events matches the number of release events
813
+ let total_reserves = wf_events
814
+ .iter()
815
+ .filter(|e| {
816
+ matches!(
817
+ e,
818
+ SlotEvent::ReserveSlot { .. } | SlotEvent::TryReserveSlot { .. }
819
+ )
820
+ })
821
+ .count()
822
+ + activity_events
823
+ .iter()
824
+ .filter(|e| {
825
+ matches!(
826
+ e,
827
+ SlotEvent::ReserveSlot { .. } | SlotEvent::TryReserveSlot { .. }
828
+ )
829
+ })
830
+ .count()
831
+ + local_activity_events
832
+ .iter()
833
+ .filter(|e| {
834
+ matches!(
835
+ e,
836
+ SlotEvent::ReserveSlot { .. } | SlotEvent::TryReserveSlot { .. }
837
+ )
838
+ })
839
+ .count();
840
+
841
+ let total_releases = wf_events
842
+ .iter()
843
+ .filter(|e| matches!(e, SlotEvent::ReleaseSlot { .. }))
844
+ .count()
845
+ + activity_events
846
+ .iter()
847
+ .filter(|e| matches!(e, SlotEvent::ReleaseSlot { .. }))
848
+ .count()
849
+ + local_activity_events
850
+ .iter()
851
+ .filter(|e| matches!(e, SlotEvent::ReleaseSlot { .. }))
852
+ .count();
853
+
854
+ assert_eq!(
855
+ total_reserves, total_releases,
856
+ "Number of reserves should equal number of releases"
857
+ );
858
+ }