@temporalio/core-bridge 1.14.2-canary-release-testing.0 → 1.16.0
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.
- package/Cargo.lock +794 -650
- package/bridge-macros/src/derive_tryintojs.rs +40 -0
- package/lib/native.d.ts +24 -3
- package/package.json +4 -4
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.github/workflows/per-pr.yml +6 -6
- package/sdk-core/AGENTS.md +42 -31
- package/sdk-core/Cargo.toml +4 -1
- package/sdk-core/README.md +19 -13
- package/sdk-core/crates/client/Cargo.toml +4 -0
- package/sdk-core/crates/client/README.md +139 -0
- package/sdk-core/crates/client/src/async_activity_handle.rs +297 -0
- package/sdk-core/crates/client/src/callback_based.rs +7 -0
- package/sdk-core/crates/client/src/errors.rs +294 -0
- package/sdk-core/crates/client/src/{raw.rs → grpc.rs} +370 -159
- package/sdk-core/crates/client/src/lib.rs +920 -1326
- package/sdk-core/crates/client/src/metrics.rs +24 -33
- package/sdk-core/crates/client/src/options_structs.rs +457 -0
- package/sdk-core/crates/client/src/replaceable.rs +5 -4
- package/sdk-core/crates/client/src/request_extensions.rs +8 -9
- package/sdk-core/crates/client/src/retry.rs +99 -54
- package/sdk-core/crates/client/src/{worker/mod.rs → worker.rs} +104 -29
- package/sdk-core/crates/client/src/workflow_handle.rs +826 -0
- package/sdk-core/crates/common/Cargo.toml +62 -3
- package/sdk-core/crates/common/build.rs +742 -12
- package/sdk-core/crates/common/protos/api_upstream/.github/workflows/ci.yml +2 -0
- package/sdk-core/crates/common/protos/api_upstream/.github/workflows/create-release.yml +0 -5
- package/sdk-core/crates/common/protos/api_upstream/Makefile +2 -1
- package/sdk-core/crates/common/protos/api_upstream/README.md +8 -0
- package/sdk-core/crates/common/protos/api_upstream/cmd/check-path-conflicts/main.go +137 -0
- package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv2.json +3329 -2647
- package/sdk-core/crates/common/protos/api_upstream/openapi/openapiv3.yaml +2734 -708
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/activity/v1/message.proto +155 -3
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/common/v1/message.proto +8 -1
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/deployment/v1/message.proto +27 -1
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/activity.proto +81 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +4 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +15 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/enums/v1/workflow.proto +63 -15
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/errordetails/v1/message.proto +8 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/history/v1/message.proto +111 -17
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/namespace/v1/message.proto +21 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/nexus/v1/message.proto +20 -1
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +4 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -0
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/worker/v1/message.proto +4 -7
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflow/v1/message.proto +80 -22
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +347 -23
- package/sdk-core/crates/common/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +242 -43
- package/sdk-core/crates/common/protos/local/temporal/sdk/core/core_interface.proto +15 -0
- package/sdk-core/crates/common/protos/local/temporal/sdk/core/nexus/nexus.proto +9 -2
- package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +8 -0
- package/sdk-core/crates/common/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +22 -5
- package/sdk-core/crates/common/src/activity_definition.rs +20 -0
- package/sdk-core/crates/common/src/data_converters.rs +770 -0
- package/sdk-core/crates/common/src/envconfig.rs +5 -0
- package/sdk-core/crates/common/src/lib.rs +15 -211
- package/sdk-core/crates/common/src/payload_visitor.rs +648 -0
- package/sdk-core/crates/common/src/priority.rs +110 -0
- package/sdk-core/crates/common/src/protos/canned_histories.rs +19 -0
- package/sdk-core/crates/common/src/protos/history_builder.rs +45 -0
- package/sdk-core/crates/common/src/protos/history_info.rs +2 -0
- package/sdk-core/crates/common/src/protos/mod.rs +134 -27
- package/sdk-core/crates/common/src/protos/task_token.rs +3 -3
- package/sdk-core/crates/common/src/protos/utilities.rs +11 -0
- package/sdk-core/crates/{sdk-core → common}/src/telemetry/log_export.rs +11 -16
- package/sdk-core/crates/common/src/telemetry/metrics/core.rs +125 -0
- package/sdk-core/crates/common/src/telemetry/metrics.rs +272 -225
- package/sdk-core/crates/{sdk-core → common}/src/telemetry/otel.rs +8 -13
- package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_meter.rs +49 -50
- package/sdk-core/crates/{sdk-core → common}/src/telemetry/prometheus_server.rs +2 -3
- package/sdk-core/crates/common/src/telemetry.rs +278 -19
- package/sdk-core/crates/common/src/worker.rs +68 -636
- package/sdk-core/crates/common/src/workflow_definition.rs +60 -0
- package/sdk-core/crates/macros/Cargo.toml +5 -1
- package/sdk-core/crates/macros/src/activities_definitions.rs +585 -0
- package/sdk-core/crates/macros/src/fsm_impl.rs +507 -0
- package/sdk-core/crates/macros/src/lib.rs +138 -512
- package/sdk-core/crates/macros/src/macro_utils.rs +106 -0
- package/sdk-core/crates/macros/src/workflow_definitions.rs +1224 -0
- package/sdk-core/crates/sdk/Cargo.toml +19 -6
- package/sdk-core/crates/sdk/README.md +415 -0
- package/sdk-core/crates/sdk/src/activities.rs +417 -0
- package/sdk-core/crates/sdk/src/interceptors.rs +1 -1
- package/sdk-core/crates/sdk/src/lib.rs +759 -442
- package/sdk-core/crates/sdk/src/workflow_context/options.rs +64 -35
- package/sdk-core/crates/sdk/src/workflow_context.rs +1033 -289
- package/sdk-core/crates/sdk/src/workflow_future.rs +277 -213
- package/sdk-core/crates/sdk/src/workflows.rs +711 -0
- package/sdk-core/crates/sdk-core/Cargo.toml +59 -65
- package/sdk-core/crates/sdk-core/benches/workflow_replay_bench.rs +45 -54
- package/sdk-core/crates/sdk-core/machine_coverage/ActivityMachine_Coverage.puml +1 -1
- package/sdk-core/crates/sdk-core/src/abstractions.rs +6 -10
- package/sdk-core/crates/sdk-core/src/core_tests/activity_tasks.rs +6 -5
- package/sdk-core/crates/sdk-core/src/core_tests/mod.rs +22 -21
- package/sdk-core/crates/sdk-core/src/core_tests/queries.rs +21 -25
- package/sdk-core/crates/sdk-core/src/core_tests/replay_flag.rs +7 -10
- package/sdk-core/crates/sdk-core/src/core_tests/updates.rs +14 -17
- package/sdk-core/crates/sdk-core/src/core_tests/workers.rs +647 -27
- package/sdk-core/crates/sdk-core/src/core_tests/workflow_tasks.rs +46 -41
- package/sdk-core/crates/sdk-core/src/ephemeral_server/mod.rs +13 -16
- package/sdk-core/crates/sdk-core/src/histfetch.rs +20 -10
- package/sdk-core/crates/sdk-core/src/lib.rs +60 -123
- package/sdk-core/crates/sdk-core/src/pollers/mod.rs +4 -9
- package/sdk-core/crates/sdk-core/src/pollers/poll_buffer.rs +411 -32
- package/sdk-core/crates/sdk-core/src/protosext/mod.rs +2 -2
- package/sdk-core/crates/sdk-core/src/replay/mod.rs +14 -5
- package/sdk-core/crates/sdk-core/src/telemetry/metrics.rs +183 -198
- package/sdk-core/crates/sdk-core/src/telemetry/mod.rs +3 -281
- package/sdk-core/crates/sdk-core/src/test_help/integ_helpers.rs +35 -16
- package/sdk-core/crates/sdk-core/src/test_help/unit_helpers.rs +3 -6
- package/sdk-core/crates/sdk-core/src/worker/activities/activity_heartbeat_manager.rs +1 -0
- package/sdk-core/crates/sdk-core/src/worker/activities/local_activities.rs +11 -14
- package/sdk-core/crates/sdk-core/src/worker/activities.rs +16 -19
- package/sdk-core/crates/sdk-core/src/worker/client/mocks.rs +11 -5
- package/sdk-core/crates/sdk-core/src/worker/client.rs +104 -86
- package/sdk-core/crates/sdk-core/src/worker/heartbeat.rs +10 -14
- package/sdk-core/crates/sdk-core/src/worker/mod.rs +1175 -241
- package/sdk-core/crates/sdk-core/src/worker/nexus.rs +150 -23
- package/sdk-core/crates/sdk-core/src/worker/slot_provider.rs +2 -2
- package/sdk-core/crates/sdk-core/src/worker/tuner/fixed_size.rs +2 -2
- package/sdk-core/crates/sdk-core/src/worker/tuner/resource_based.rs +25 -27
- package/sdk-core/crates/sdk-core/src/worker/tuner.rs +64 -44
- package/sdk-core/crates/sdk-core/src/worker/workflow/driven_workflow.rs +9 -3
- package/sdk-core/crates/sdk-core/src/worker/workflow/machines/patch_state_machine.rs +5 -8
- package/sdk-core/crates/sdk-core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +21 -22
- package/sdk-core/crates/sdk-core/src/worker/workflow/machines/workflow_machines.rs +28 -4
- package/sdk-core/crates/sdk-core/src/worker/workflow/managed_run.rs +20 -41
- package/sdk-core/crates/sdk-core/src/worker/workflow/mod.rs +50 -9
- package/sdk-core/crates/sdk-core/src/worker/workflow/run_cache.rs +4 -7
- package/sdk-core/crates/sdk-core/src/worker/workflow/wft_extraction.rs +2 -4
- package/sdk-core/crates/sdk-core/src/worker/workflow/wft_poller.rs +8 -9
- package/sdk-core/crates/sdk-core/src/worker/workflow/workflow_stream.rs +1 -3
- package/sdk-core/crates/sdk-core/tests/activities_procmacro.rs +6 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/basic_pass.rs +54 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.rs +18 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/invalid_self_type_fail.stderr +5 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.rs +14 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/missing_context_fail.stderr +5 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/multi_arg_pass.rs +48 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_input_pass.rs +14 -0
- package/sdk-core/crates/sdk-core/tests/activities_trybuild/no_return_type_pass.rs +19 -0
- package/sdk-core/crates/sdk-core/tests/cloud_tests.rs +14 -5
- package/sdk-core/crates/sdk-core/tests/common/activity_functions.rs +55 -0
- package/sdk-core/crates/sdk-core/tests/common/mod.rs +281 -236
- package/sdk-core/crates/sdk-core/tests/common/workflows.rs +41 -28
- package/sdk-core/crates/sdk-core/tests/global_metric_tests.rs +9 -14
- package/sdk-core/crates/sdk-core/tests/heavy_tests/fuzzy_workflow.rs +73 -66
- package/sdk-core/crates/sdk-core/tests/heavy_tests.rs +306 -268
- package/sdk-core/crates/sdk-core/tests/integ_tests/async_activity_client_tests.rs +230 -0
- package/sdk-core/crates/sdk-core/tests/integ_tests/client_tests.rs +94 -57
- package/sdk-core/crates/sdk-core/tests/integ_tests/data_converter_tests.rs +381 -0
- package/sdk-core/crates/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +37 -38
- package/sdk-core/crates/sdk-core/tests/integ_tests/heartbeat_tests.rs +49 -40
- package/sdk-core/crates/sdk-core/tests/integ_tests/metrics_tests.rs +447 -300
- package/sdk-core/crates/sdk-core/tests/integ_tests/pagination_tests.rs +50 -45
- package/sdk-core/crates/sdk-core/tests/integ_tests/polling_tests.rs +157 -157
- package/sdk-core/crates/sdk-core/tests/integ_tests/queries_tests.rs +103 -89
- package/sdk-core/crates/sdk-core/tests/integ_tests/update_tests.rs +609 -463
- package/sdk-core/crates/sdk-core/tests/integ_tests/visibility_tests.rs +80 -62
- package/sdk-core/crates/sdk-core/tests/integ_tests/worker_heartbeat_tests.rs +389 -265
- package/sdk-core/crates/sdk-core/tests/integ_tests/worker_tests.rs +250 -185
- package/sdk-core/crates/sdk-core/tests/integ_tests/worker_versioning_tests.rs +52 -49
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_client_tests.rs +180 -0
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/activities.rs +437 -327
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +82 -58
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +56 -30
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +364 -251
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/client_interactions.rs +552 -0
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +110 -46
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +243 -149
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/eager.rs +98 -32
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1475 -1040
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +73 -43
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +402 -245
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/patches.rs +343 -207
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/queries.rs +415 -0
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/replay.rs +96 -36
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/resets.rs +155 -140
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/signals.rs +183 -113
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +85 -44
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/timers.rs +142 -48
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +73 -56
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests.rs +365 -242
- package/sdk-core/crates/sdk-core/tests/main.rs +22 -16
- package/sdk-core/crates/sdk-core/tests/manual_tests.rs +233 -187
- package/sdk-core/crates/sdk-core/tests/runner.rs +4 -6
- package/sdk-core/crates/sdk-core/tests/shared_tests/mod.rs +73 -27
- package/sdk-core/crates/sdk-core/tests/shared_tests/priority.rs +107 -84
- package/sdk-core/crates/sdk-core/tests/workflows_procmacro.rs +6 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.rs +26 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/async_query_fail.stderr +5 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/basic_pass.rs +49 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/minimal_pass.rs +21 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.rs +26 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/mut_query_fail.stderr +5 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.rs +21 -0
- package/sdk-core/crates/sdk-core/tests/workflows_trybuild/sync_run_fail.stderr +5 -0
- package/sdk-core/crates/sdk-core-c-bridge/Cargo.toml +8 -1
- package/sdk-core/crates/sdk-core-c-bridge/include/temporal-sdk-core-c-bridge.h +37 -26
- package/sdk-core/crates/sdk-core-c-bridge/src/client.rs +180 -87
- package/sdk-core/crates/sdk-core-c-bridge/src/lib.rs +89 -5
- package/sdk-core/crates/sdk-core-c-bridge/src/metric.rs +10 -16
- package/sdk-core/crates/sdk-core-c-bridge/src/runtime.rs +59 -67
- package/sdk-core/crates/sdk-core-c-bridge/src/testing.rs +10 -10
- package/sdk-core/crates/sdk-core-c-bridge/src/tests/context.rs +57 -22
- package/sdk-core/crates/sdk-core-c-bridge/src/tests/mod.rs +108 -12
- package/sdk-core/crates/sdk-core-c-bridge/src/tests/utils.rs +9 -52
- package/sdk-core/crates/sdk-core-c-bridge/src/worker.rs +74 -91
- package/sdk-core/rustfmt.toml +2 -1
- package/src/client.rs +206 -289
- package/src/helpers/try_into_js.rs +88 -2
- package/src/metrics.rs +277 -35
- package/src/runtime.rs +94 -45
- package/src/testing.rs +9 -16
- package/src/worker.rs +86 -68
- package/ts/native.ts +39 -3
- package/sdk-core/crates/client/src/workflow_handle/mod.rs +0 -212
- package/sdk-core/crates/common/src/errors.rs +0 -85
- package/sdk-core/crates/common/tests/worker_task_types_test.rs +0 -129
- package/sdk-core/crates/macros/LICENSE.txt +0 -21
- package/sdk-core/crates/sdk/src/activity_context.rs +0 -238
- package/sdk-core/crates/sdk/src/app_data.rs +0 -37
- package/sdk-core/crates/sdk-core/tests/integ_tests/activity_functions.rs +0 -5
- package/sdk-core/crates/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +0 -61
|
@@ -2,10 +2,12 @@ use crate::{
|
|
|
2
2
|
abstractions::{ActiveCounter, MeteredPermitDealer, OwnedMeteredSemPermit, dbg_panic},
|
|
3
3
|
pollers::{self, Poller},
|
|
4
4
|
worker::{
|
|
5
|
-
WFTPollerShared,
|
|
5
|
+
ActivitySlotKind, NexusSlotKind, PollerBehavior, SlotKind, WFTPollerShared,
|
|
6
|
+
WorkflowSlotKind,
|
|
6
7
|
client::{PollActivityOptions, PollOptions, PollWorkflowOptions, WorkerClient},
|
|
7
8
|
},
|
|
8
9
|
};
|
|
10
|
+
use backoff::{SystemClock, backoff::Backoff, exponential::ExponentialBackoff};
|
|
9
11
|
use crossbeam_utils::atomic::AtomicCell;
|
|
10
12
|
use futures_util::{FutureExt, StreamExt, future::BoxFuture};
|
|
11
13
|
use governor::{Quota, RateLimiter};
|
|
@@ -22,15 +24,11 @@ use std::{
|
|
|
22
24
|
use temporalio_client::{
|
|
23
25
|
ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT, request_extensions::NoRetryOnMatching,
|
|
24
26
|
};
|
|
25
|
-
use temporalio_common::{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
PollActivityTaskQueueResponse, PollNexusTaskQueueResponse,
|
|
30
|
-
PollWorkflowTaskQueueResponse,
|
|
31
|
-
},
|
|
27
|
+
use temporalio_common::protos::temporal::api::{
|
|
28
|
+
taskqueue::v1::PollerScalingDecision,
|
|
29
|
+
workflowservice::v1::{
|
|
30
|
+
PollActivityTaskQueueResponse, PollNexusTaskQueueResponse, PollWorkflowTaskQueueResponse,
|
|
32
31
|
},
|
|
33
|
-
worker::{ActivitySlotKind, NexusSlotKind, PollerBehavior, SlotKind, WorkflowSlotKind},
|
|
34
32
|
};
|
|
35
33
|
use tokio::{
|
|
36
34
|
sync::{
|
|
@@ -79,6 +77,7 @@ impl LongPollBuffer<PollWorkflowTaskQueueResponse, WorkflowSlotKind> {
|
|
|
79
77
|
num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
|
|
80
78
|
options: WorkflowTaskOptions,
|
|
81
79
|
last_successful_poll_time: Arc<AtomicCell<Option<SystemTime>>>,
|
|
80
|
+
graceful_poll_shutdown: Arc<AtomicBool>,
|
|
82
81
|
) -> Self {
|
|
83
82
|
let is_sticky = sticky_queue.is_some();
|
|
84
83
|
let poll_scaler = PollScaler::new(
|
|
@@ -141,6 +140,7 @@ impl LongPollBuffer<PollWorkflowTaskQueueResponse, WorkflowSlotKind> {
|
|
|
141
140
|
poll_scaler,
|
|
142
141
|
pre_permit_delay,
|
|
143
142
|
post_poll_fn,
|
|
143
|
+
graceful_poll_shutdown,
|
|
144
144
|
)
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -156,6 +156,7 @@ impl LongPollBuffer<PollActivityTaskQueueResponse, ActivitySlotKind> {
|
|
|
156
156
|
num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
|
|
157
157
|
options: ActivityTaskOptions,
|
|
158
158
|
last_successful_poll_time: Arc<AtomicCell<Option<SystemTime>>>,
|
|
159
|
+
graceful_poll_shutdown: Arc<AtomicBool>,
|
|
159
160
|
) -> Self {
|
|
160
161
|
let pre_permit_delay = options
|
|
161
162
|
.max_worker_acts_per_second
|
|
@@ -208,6 +209,7 @@ impl LongPollBuffer<PollActivityTaskQueueResponse, ActivitySlotKind> {
|
|
|
208
209
|
poll_scaler,
|
|
209
210
|
pre_permit_delay,
|
|
210
211
|
None::<fn(&PollActivityTaskQueueResponse)>,
|
|
212
|
+
graceful_poll_shutdown,
|
|
211
213
|
)
|
|
212
214
|
}
|
|
213
215
|
}
|
|
@@ -223,6 +225,7 @@ impl LongPollBuffer<PollNexusTaskQueueResponse, NexusSlotKind> {
|
|
|
223
225
|
num_pollers_handler: Option<impl Fn(usize) + Send + Sync + 'static>,
|
|
224
226
|
last_successful_poll_time: Arc<AtomicCell<Option<SystemTime>>>,
|
|
225
227
|
send_heartbeat: bool,
|
|
228
|
+
graceful_poll_shutdown: Arc<AtomicBool>,
|
|
226
229
|
) -> Self {
|
|
227
230
|
let no_retry = if matches!(poller_behavior, PollerBehavior::Autoscaling { .. }) {
|
|
228
231
|
Some(NoRetryOnMatching {
|
|
@@ -259,10 +262,20 @@ impl LongPollBuffer<PollNexusTaskQueueResponse, NexusSlotKind> {
|
|
|
259
262
|
),
|
|
260
263
|
None::<fn() -> BoxFuture<'static, ()>>,
|
|
261
264
|
None::<fn(&PollNexusTaskQueueResponse)>,
|
|
265
|
+
graceful_poll_shutdown,
|
|
262
266
|
)
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
269
|
|
|
270
|
+
// Simple way to test this w/o plumbing options through. This will be removed after
|
|
271
|
+
// the proper server implementation for terminating polls on worker shutdown exists.
|
|
272
|
+
#[cfg(test)]
|
|
273
|
+
use std::cell::RefCell;
|
|
274
|
+
#[cfg(test)]
|
|
275
|
+
thread_local! {
|
|
276
|
+
static POLL_SHUTDOWN_INTERRUPT: RefCell<Option<Duration>> = RefCell::default();
|
|
277
|
+
}
|
|
278
|
+
|
|
266
279
|
impl<T, SK> LongPollBuffer<T, SK>
|
|
267
280
|
where
|
|
268
281
|
T: TaskPollerResult + Send + Debug + 'static,
|
|
@@ -275,6 +288,7 @@ where
|
|
|
275
288
|
mut poll_scaler: PollScaler<F>,
|
|
276
289
|
pre_permit_delay: Option<impl Fn() -> DelayFut + Send + Sync + 'static>,
|
|
277
290
|
post_poll_fn: Option<impl Fn(&T) + Send + Sync + 'static>,
|
|
291
|
+
graceful_shutdown: Arc<AtomicBool>,
|
|
278
292
|
) -> Self
|
|
279
293
|
where
|
|
280
294
|
FT: Future<Output = pollers::Result<T>> + Send,
|
|
@@ -282,11 +296,18 @@ where
|
|
|
282
296
|
F: Fn(usize) + Send + Sync + 'static,
|
|
283
297
|
{
|
|
284
298
|
let (tx, rx) = unbounded_channel();
|
|
285
|
-
let (starter, wait_for_start) = broadcast::channel(1);
|
|
299
|
+
let (starter, mut wait_for_start) = broadcast::channel(1);
|
|
286
300
|
let pf = Arc::new(poll_fn);
|
|
287
301
|
let post_pf = Arc::new(post_poll_fn);
|
|
288
|
-
let mut wait_for_start = wait_for_start.resubscribe();
|
|
289
302
|
let shutdown_clone = shutdown.clone();
|
|
303
|
+
#[cfg(test)]
|
|
304
|
+
let poll_shutdown_interrupt_wait = POLL_SHUTDOWN_INTERRUPT.with(|v| *v.borrow());
|
|
305
|
+
#[cfg(not(test))]
|
|
306
|
+
let poll_shutdown_interrupt_wait =
|
|
307
|
+
std::env::var("TEMPORAL_POLL_SHUTDOWN_INTERRUPT_WAIT_MS")
|
|
308
|
+
.ok()
|
|
309
|
+
.and_then(|s| s.parse::<u64>().ok())
|
|
310
|
+
.map(Duration::from_millis);
|
|
290
311
|
|
|
291
312
|
let poller_task = tokio::spawn(
|
|
292
313
|
async move {
|
|
@@ -338,18 +359,38 @@ where
|
|
|
338
359
|
} else {
|
|
339
360
|
None
|
|
340
361
|
};
|
|
362
|
+
let graceful_shutdown = graceful_shutdown.clone();
|
|
341
363
|
let poll_task = tokio::spawn(async move {
|
|
342
|
-
let
|
|
343
|
-
|
|
344
|
-
|
|
364
|
+
let shutdown_clone = shutdown.clone();
|
|
365
|
+
|
|
366
|
+
let r = if graceful_shutdown.load(Ordering::Relaxed) {
|
|
367
|
+
pf(timeout_override).await
|
|
368
|
+
} else {
|
|
369
|
+
let poll_interruptor = shutdown.cancelled().then(|_| async move {
|
|
370
|
+
if let Some(w) = poll_shutdown_interrupt_wait {
|
|
371
|
+
tokio::time::sleep(w).await;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
tokio::select! {
|
|
375
|
+
r = pf(timeout_override) => r,
|
|
376
|
+
_ = poll_interruptor => return,
|
|
377
|
+
}
|
|
345
378
|
};
|
|
346
|
-
drop(active_guard);
|
|
347
379
|
if let Ok(r) = &r
|
|
348
380
|
&& let Some(ppf) = post_pf.as_ref()
|
|
349
381
|
{
|
|
350
382
|
ppf(r);
|
|
351
383
|
}
|
|
352
|
-
|
|
384
|
+
let (should_forward, backoff_duration) = report_handle.poll_result(&r);
|
|
385
|
+
if let Some(duration) = backoff_duration {
|
|
386
|
+
// Apply backoff BEFORE dropping active_guard to prevent next poll from starting
|
|
387
|
+
tokio::select! {
|
|
388
|
+
_ = tokio::time::sleep(duration) => return,
|
|
389
|
+
_ = shutdown_clone.cancelled() => (),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
drop(active_guard);
|
|
393
|
+
if should_forward {
|
|
353
394
|
let _ = tx.send(r.map(|r| (r, permit)));
|
|
354
395
|
}
|
|
355
396
|
});
|
|
@@ -463,6 +504,28 @@ where
|
|
|
463
504
|
ingested_last_period: Default::default(),
|
|
464
505
|
scale_up_allowed: AtomicBool::new(true),
|
|
465
506
|
last_successful_poll_time,
|
|
507
|
+
exponential_backoff: parking_lot::Mutex::new(ExponentialBackoff {
|
|
508
|
+
// Copied from RetryOptions::task_poll_retry_policy()
|
|
509
|
+
current_interval: Duration::from_millis(200),
|
|
510
|
+
initial_interval: Duration::from_millis(200),
|
|
511
|
+
randomization_factor: 0.2,
|
|
512
|
+
multiplier: 2.0,
|
|
513
|
+
max_interval: Duration::from_secs(10),
|
|
514
|
+
max_elapsed_time: None,
|
|
515
|
+
clock: SystemClock::default(),
|
|
516
|
+
start_time: std::time::Instant::now(),
|
|
517
|
+
}),
|
|
518
|
+
resource_exhausted_backoff: parking_lot::Mutex::new(ExponentialBackoff {
|
|
519
|
+
// Copied from RetryOptions::throttle_retry_policy()
|
|
520
|
+
current_interval: Duration::from_secs(1),
|
|
521
|
+
initial_interval: Duration::from_secs(1),
|
|
522
|
+
randomization_factor: 0.2,
|
|
523
|
+
multiplier: 2.0,
|
|
524
|
+
max_interval: Duration::from_secs(10),
|
|
525
|
+
max_elapsed_time: None,
|
|
526
|
+
clock: SystemClock::default(),
|
|
527
|
+
start_time: std::time::Instant::now(),
|
|
528
|
+
}),
|
|
466
529
|
});
|
|
467
530
|
let rhc = report_handle.clone();
|
|
468
531
|
let ingestor_task = if behavior.is_autoscaling() {
|
|
@@ -526,18 +589,32 @@ struct PollScalerReportHandle {
|
|
|
526
589
|
ingested_last_period: AtomicUsize,
|
|
527
590
|
scale_up_allowed: AtomicBool,
|
|
528
591
|
last_successful_poll_time: Arc<AtomicCell<Option<SystemTime>>>,
|
|
592
|
+
|
|
593
|
+
// Exponential backoff for normal errors and resource exhausted errors
|
|
594
|
+
exponential_backoff: parking_lot::Mutex<ExponentialBackoff<SystemClock>>,
|
|
595
|
+
resource_exhausted_backoff: parking_lot::Mutex<ExponentialBackoff<SystemClock>>,
|
|
529
596
|
}
|
|
530
597
|
|
|
531
598
|
impl PollScalerReportHandle {
|
|
532
|
-
/// Returns
|
|
533
|
-
|
|
599
|
+
/// Returns (should_forward, backoff_duration)
|
|
600
|
+
/// - should_forward: true if the response should be passed on, false if it should be swallowed
|
|
601
|
+
/// - backoff_duration: Some(duration) if we should sleep before the next poll
|
|
602
|
+
fn poll_result(
|
|
603
|
+
&self,
|
|
604
|
+
res: &Result<impl TaskPollerResult, tonic::Status>,
|
|
605
|
+
) -> (bool, Option<Duration>) {
|
|
534
606
|
match res {
|
|
535
607
|
Ok(res) => {
|
|
536
608
|
self.last_successful_poll_time
|
|
537
609
|
.store(Some(SystemTime::now()));
|
|
610
|
+
|
|
611
|
+
// Reset backoff on successful poll
|
|
612
|
+
self.exponential_backoff.lock().reset();
|
|
613
|
+
self.resource_exhausted_backoff.lock().reset();
|
|
614
|
+
|
|
538
615
|
if let PollerBehavior::SimpleMaximum(_) = self.behavior {
|
|
539
616
|
// We don't do auto-scaling with the simple max
|
|
540
|
-
return true;
|
|
617
|
+
return (true, None);
|
|
541
618
|
}
|
|
542
619
|
if !res.is_empty() {
|
|
543
620
|
self.ingested_this_period.fetch_add(1, Ordering::Relaxed);
|
|
@@ -572,30 +649,36 @@ impl PollScalerReportHandle {
|
|
|
572
649
|
}
|
|
573
650
|
Err(e) => {
|
|
574
651
|
if matches!(self.behavior, PollerBehavior::Autoscaling { .. }) {
|
|
575
|
-
//
|
|
576
|
-
|
|
652
|
+
// Follow the same backoff logic as the retry client
|
|
653
|
+
let mut backoff_duration = self.exponential_backoff.lock().next_backoff();
|
|
654
|
+
if e.code() == Code::ResourceExhausted {
|
|
655
|
+
backoff_duration = self.resource_exhausted_backoff.lock().next_backoff();
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
// Only propagate errors out if they weren't because of the short-circuiting
|
|
659
|
+
// logic. IE: We don't want to fail callers because we said we wanted to know
|
|
660
|
+
// about ResourceExhausted errors, but we haven't seen a scaling decision yet,
|
|
661
|
+
// so we're not reacting to errors, only propagating them.
|
|
662
|
+
let should_forward = !e
|
|
663
|
+
.metadata()
|
|
664
|
+
.contains_key(ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT);
|
|
665
|
+
|
|
666
|
+
// We should only react to errors in autoscaling mode if we saw a scaling decision
|
|
577
667
|
if self.ever_saw_scaling_decision.load(Ordering::Relaxed) {
|
|
578
668
|
debug!("Got error from server while polling: {:?}", e);
|
|
579
669
|
if e.code() == Code::ResourceExhausted {
|
|
580
670
|
// Scale down significantly for resource exhaustion
|
|
581
671
|
self.change_target(usize::saturating_div, 2);
|
|
582
672
|
} else {
|
|
583
|
-
// Other codes that would normally have made us back off briefly can
|
|
584
|
-
// reclaim this poller
|
|
673
|
+
// Other codes that would normally have made us back off briefly can reclaim this poller
|
|
585
674
|
self.change_target(usize::saturating_sub, 1);
|
|
586
675
|
}
|
|
587
676
|
}
|
|
588
|
-
|
|
589
|
-
// logic. IE: We don't want to fail callers because we said we wanted to know
|
|
590
|
-
// about ResourceExhausted errors, but we haven't seen a scaling decision yet,
|
|
591
|
-
// so we're not reacting to errors, only propagating them.
|
|
592
|
-
return !e
|
|
593
|
-
.metadata()
|
|
594
|
-
.contains_key(ERROR_RETURNED_DUE_TO_SHORT_CIRCUIT);
|
|
677
|
+
return (should_forward, backoff_duration);
|
|
595
678
|
}
|
|
596
679
|
}
|
|
597
680
|
}
|
|
598
|
-
true
|
|
681
|
+
(true, None)
|
|
599
682
|
}
|
|
600
683
|
|
|
601
684
|
#[inline]
|
|
@@ -740,8 +823,9 @@ mod tests {
|
|
|
740
823
|
worker::client::mocks::mock_manual_worker_client,
|
|
741
824
|
};
|
|
742
825
|
use futures_util::FutureExt;
|
|
826
|
+
use rstest::rstest;
|
|
743
827
|
use std::time::Duration;
|
|
744
|
-
use tokio::select;
|
|
828
|
+
use tokio::{select, sync::Notify};
|
|
745
829
|
|
|
746
830
|
#[tokio::test]
|
|
747
831
|
async fn only_polls_once_with_1_poller() {
|
|
@@ -769,6 +853,7 @@ mod tests {
|
|
|
769
853
|
wft_poller_shared: Some(Arc::new(WFTPollerShared::new(Some(10)))),
|
|
770
854
|
},
|
|
771
855
|
Arc::new(AtomicCell::new(None)),
|
|
856
|
+
Arc::new(AtomicBool::new(false)),
|
|
772
857
|
);
|
|
773
858
|
|
|
774
859
|
// Poll a bunch of times, "interrupting" it each time, we should only actually have polled
|
|
@@ -825,10 +910,304 @@ mod tests {
|
|
|
825
910
|
wft_poller_shared: Some(Arc::new(WFTPollerShared::new(Some(1)))),
|
|
826
911
|
},
|
|
827
912
|
Arc::new(AtomicCell::new(None)),
|
|
913
|
+
Arc::new(AtomicBool::new(false)),
|
|
828
914
|
);
|
|
829
915
|
|
|
830
916
|
// Should not see error, unwraps should get empty response
|
|
831
917
|
pb.poll().await.unwrap().unwrap();
|
|
832
918
|
pb.shutdown().await;
|
|
833
919
|
}
|
|
920
|
+
|
|
921
|
+
#[tokio::test]
|
|
922
|
+
async fn poll_shutdown_waits_interrupt_period_before_cancelling() {
|
|
923
|
+
POLL_SHUTDOWN_INTERRUPT.with(|v| {
|
|
924
|
+
*v.borrow_mut() = Some(Duration::from_millis(200));
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
let mut mock_client = mock_manual_worker_client();
|
|
928
|
+
let call_count = Arc::new(AtomicUsize::new(0));
|
|
929
|
+
let call_count_clone = call_count.clone();
|
|
930
|
+
|
|
931
|
+
let second_task_complete = Arc::new(Notify::new());
|
|
932
|
+
let second_task_complete_clone = second_task_complete.clone();
|
|
933
|
+
let third_task_complete = Arc::new(Notify::new());
|
|
934
|
+
let third_task_complete_clone = third_task_complete.clone();
|
|
935
|
+
|
|
936
|
+
let second_started = Arc::new(Notify::new());
|
|
937
|
+
let second_started_clone = second_started.clone();
|
|
938
|
+
let third_started = Arc::new(Notify::new());
|
|
939
|
+
let third_started_clone = third_started.clone();
|
|
940
|
+
|
|
941
|
+
mock_client
|
|
942
|
+
.expect_poll_workflow_task()
|
|
943
|
+
.returning(move |_, _| {
|
|
944
|
+
let count = call_count_clone.fetch_add(1, Ordering::SeqCst);
|
|
945
|
+
let second_complete = second_task_complete_clone.clone();
|
|
946
|
+
let third_complete = third_task_complete_clone.clone();
|
|
947
|
+
let second_started = second_started_clone.clone();
|
|
948
|
+
let third_started = third_started_clone.clone();
|
|
949
|
+
|
|
950
|
+
async move {
|
|
951
|
+
match count {
|
|
952
|
+
0 => Ok(PollWorkflowTaskQueueResponse {
|
|
953
|
+
task_token: vec![1],
|
|
954
|
+
..Default::default()
|
|
955
|
+
}),
|
|
956
|
+
1 => {
|
|
957
|
+
second_started.notify_one();
|
|
958
|
+
second_complete.notified().await;
|
|
959
|
+
Ok(PollWorkflowTaskQueueResponse {
|
|
960
|
+
task_token: vec![2],
|
|
961
|
+
..Default::default()
|
|
962
|
+
})
|
|
963
|
+
}
|
|
964
|
+
_ => {
|
|
965
|
+
third_started.notify_one();
|
|
966
|
+
third_complete.notified().await;
|
|
967
|
+
Ok(PollWorkflowTaskQueueResponse {
|
|
968
|
+
task_token: vec![3],
|
|
969
|
+
..Default::default()
|
|
970
|
+
})
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
.boxed()
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
let shutdown_token = CancellationToken::new();
|
|
978
|
+
let pb = LongPollBuffer::new_workflow_task(
|
|
979
|
+
Arc::new(mock_client),
|
|
980
|
+
"sometq".to_string(),
|
|
981
|
+
None,
|
|
982
|
+
PollerBehavior::SimpleMaximum(3),
|
|
983
|
+
fixed_size_permit_dealer(10),
|
|
984
|
+
shutdown_token.clone(),
|
|
985
|
+
None::<fn(usize)>,
|
|
986
|
+
WorkflowTaskOptions {
|
|
987
|
+
wft_poller_shared: Some(Arc::new(WFTPollerShared::new(Some(10)))),
|
|
988
|
+
},
|
|
989
|
+
Arc::new(AtomicCell::new(None)),
|
|
990
|
+
Arc::new(AtomicBool::new(false)),
|
|
991
|
+
);
|
|
992
|
+
|
|
993
|
+
let first_task = pb.poll().await.expect("Should get first task");
|
|
994
|
+
assert!(first_task.is_ok());
|
|
995
|
+
assert_eq!(first_task.unwrap().0.task_token, vec![1]);
|
|
996
|
+
|
|
997
|
+
// Wait for both second and third polls to start
|
|
998
|
+
second_started.notified().await;
|
|
999
|
+
third_started.notified().await;
|
|
1000
|
+
|
|
1001
|
+
// Now both polls 2 and 3 are in progress. Trigger shutdown.
|
|
1002
|
+
let shutdown_time = std::time::Instant::now();
|
|
1003
|
+
shutdown_token.cancel();
|
|
1004
|
+
|
|
1005
|
+
// Immediately unlock the second task so it completes quickly (within interrupt wait)
|
|
1006
|
+
second_task_complete.notify_one();
|
|
1007
|
+
|
|
1008
|
+
// Second task should be received because it completes within the interrupt wait period
|
|
1009
|
+
let (task, _) = pb.poll().await.unwrap().unwrap();
|
|
1010
|
+
assert_eq!(task.task_token, vec![2]);
|
|
1011
|
+
|
|
1012
|
+
// Don't notify third_task_complete - the third poll will wait for the interrupt period
|
|
1013
|
+
// and then be interrupted. Should return None.
|
|
1014
|
+
let third_task_result = pb.poll().await;
|
|
1015
|
+
let elapsed = shutdown_time.elapsed();
|
|
1016
|
+
|
|
1017
|
+
assert!(
|
|
1018
|
+
third_task_result.is_none(),
|
|
1019
|
+
"Third task should not be received - poll should be interrupted after interrupt period"
|
|
1020
|
+
);
|
|
1021
|
+
|
|
1022
|
+
// Total elapsed time should be at least the interrupt wait (200ms), but not significantly
|
|
1023
|
+
// longer.
|
|
1024
|
+
assert!(
|
|
1025
|
+
elapsed >= Duration::from_millis(200),
|
|
1026
|
+
"Should wait at least the interrupt period. Elapsed: {elapsed:?}",
|
|
1027
|
+
);
|
|
1028
|
+
assert!(
|
|
1029
|
+
elapsed < Duration::from_secs(1),
|
|
1030
|
+
"Should not wait too long. Elapsed: {elapsed:?}",
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
// Clean up
|
|
1034
|
+
POLL_SHUTDOWN_INTERRUPT.with(|v| {
|
|
1035
|
+
*v.borrow_mut() = None;
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
#[rstest]
|
|
1040
|
+
#[case::resource_exhausted(Code::ResourceExhausted)]
|
|
1041
|
+
#[case::internal(Code::Internal)]
|
|
1042
|
+
#[tokio::test]
|
|
1043
|
+
async fn autoscaler_applies_backoff_on_errors(#[case] error_code: Code) {
|
|
1044
|
+
use temporalio_common::protos::temporal::api::taskqueue::v1::PollerScalingDecision;
|
|
1045
|
+
|
|
1046
|
+
let call_count = Arc::new(AtomicUsize::new(0));
|
|
1047
|
+
let call_count_clone = call_count.clone();
|
|
1048
|
+
let first_poll_done = Arc::new(AtomicBool::new(false));
|
|
1049
|
+
let first_poll_done_clone = first_poll_done.clone();
|
|
1050
|
+
|
|
1051
|
+
let mut mock_client = mock_manual_worker_client();
|
|
1052
|
+
mock_client
|
|
1053
|
+
.expect_poll_workflow_task()
|
|
1054
|
+
.returning(move |_, _| {
|
|
1055
|
+
call_count_clone.fetch_add(1, Ordering::SeqCst);
|
|
1056
|
+
let first_done = first_poll_done_clone.clone();
|
|
1057
|
+
async move {
|
|
1058
|
+
// First poll: return empty response with scaling decision
|
|
1059
|
+
// This sets ever_saw_scaling_decision to true
|
|
1060
|
+
if !first_done.swap(true, Ordering::SeqCst) {
|
|
1061
|
+
Ok(PollWorkflowTaskQueueResponse {
|
|
1062
|
+
task_token: vec![], // Empty poll
|
|
1063
|
+
poller_scaling_decision: Some(PollerScalingDecision {
|
|
1064
|
+
// Aggressively scale up to max
|
|
1065
|
+
poll_request_delta_suggestion: 0,
|
|
1066
|
+
}),
|
|
1067
|
+
..Default::default()
|
|
1068
|
+
})
|
|
1069
|
+
} else {
|
|
1070
|
+
// All subsequent polls fail with a grpc error
|
|
1071
|
+
Err(tonic::Status::new(
|
|
1072
|
+
error_code,
|
|
1073
|
+
format!("simulated grpc error {error_code}"),
|
|
1074
|
+
))
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
.boxed()
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
let pb = Arc::new(LongPollBuffer::new_workflow_task(
|
|
1081
|
+
Arc::new(mock_client),
|
|
1082
|
+
"sometq".to_string(),
|
|
1083
|
+
None,
|
|
1084
|
+
PollerBehavior::Autoscaling {
|
|
1085
|
+
minimum: 5,
|
|
1086
|
+
maximum: 100,
|
|
1087
|
+
initial: 10,
|
|
1088
|
+
},
|
|
1089
|
+
fixed_size_permit_dealer(10),
|
|
1090
|
+
CancellationToken::new(),
|
|
1091
|
+
None::<fn(usize)>,
|
|
1092
|
+
WorkflowTaskOptions {
|
|
1093
|
+
wft_poller_shared: Some(Arc::new(WFTPollerShared::new(Some(10)))),
|
|
1094
|
+
},
|
|
1095
|
+
Arc::new(AtomicCell::new(None)),
|
|
1096
|
+
Arc::new(AtomicBool::new(false)),
|
|
1097
|
+
));
|
|
1098
|
+
|
|
1099
|
+
// Trigger the first poll to initialize and get the scaling decision
|
|
1100
|
+
let pb_clone = pb.clone();
|
|
1101
|
+
tokio::spawn(async move {
|
|
1102
|
+
let _ = pb_clone.poll().await;
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
// Wait for the first poll to complete
|
|
1106
|
+
tokio::time::sleep(Duration::from_millis(20)).await;
|
|
1107
|
+
|
|
1108
|
+
// Let the hot loop run for 100ms while we continue to attempt consuming
|
|
1109
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
1110
|
+
let hot_loop_calls = call_count.load(Ordering::SeqCst);
|
|
1111
|
+
|
|
1112
|
+
// Without backoff, this was producing ~6300 polls in ~100ms on my machine.
|
|
1113
|
+
// With exponential backoff, I'm getting exactly 10 (initial poller count).
|
|
1114
|
+
assert!(
|
|
1115
|
+
hot_loop_calls == 10,
|
|
1116
|
+
"Expected proper backoff with == 10 polls in 100ms, but got {} polls.",
|
|
1117
|
+
hot_loop_calls
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
Arc::try_unwrap(pb)
|
|
1121
|
+
.unwrap_or_else(|_| panic!("Failed to unwrap Arc"))
|
|
1122
|
+
.shutdown()
|
|
1123
|
+
.await;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
#[rstest]
|
|
1127
|
+
#[case::graceful(true)]
|
|
1128
|
+
#[case::legacy(false)]
|
|
1129
|
+
#[tokio::test]
|
|
1130
|
+
async fn inflight_poll_survives_shutdown_only_when_graceful(#[case] graceful: bool) {
|
|
1131
|
+
let mut mock_client = mock_manual_worker_client();
|
|
1132
|
+
let task_started = Arc::new(Notify::new());
|
|
1133
|
+
let task_started_clone = task_started.clone();
|
|
1134
|
+
let task_complete = Arc::new(Notify::new());
|
|
1135
|
+
let task_complete_clone = task_complete.clone();
|
|
1136
|
+
let call_count = Arc::new(AtomicUsize::new(0));
|
|
1137
|
+
let call_count_clone = call_count.clone();
|
|
1138
|
+
|
|
1139
|
+
mock_client
|
|
1140
|
+
.expect_poll_workflow_task()
|
|
1141
|
+
.returning(move |_, _| {
|
|
1142
|
+
let count = call_count_clone.fetch_add(1, Ordering::SeqCst);
|
|
1143
|
+
let started = task_started_clone.clone();
|
|
1144
|
+
let complete = task_complete_clone.clone();
|
|
1145
|
+
async move {
|
|
1146
|
+
match count {
|
|
1147
|
+
0 => Ok(PollWorkflowTaskQueueResponse {
|
|
1148
|
+
task_token: vec![1],
|
|
1149
|
+
..Default::default()
|
|
1150
|
+
}),
|
|
1151
|
+
_ => {
|
|
1152
|
+
started.notify_one();
|
|
1153
|
+
complete.notified().await;
|
|
1154
|
+
Ok(PollWorkflowTaskQueueResponse {
|
|
1155
|
+
task_token: vec![2],
|
|
1156
|
+
..Default::default()
|
|
1157
|
+
})
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
.boxed()
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
let shutdown_token = CancellationToken::new();
|
|
1165
|
+
let pb = LongPollBuffer::new_workflow_task(
|
|
1166
|
+
Arc::new(mock_client),
|
|
1167
|
+
"sometq".to_string(),
|
|
1168
|
+
None,
|
|
1169
|
+
PollerBehavior::SimpleMaximum(1),
|
|
1170
|
+
fixed_size_permit_dealer(10),
|
|
1171
|
+
shutdown_token.clone(),
|
|
1172
|
+
None::<fn(usize)>,
|
|
1173
|
+
WorkflowTaskOptions {
|
|
1174
|
+
wft_poller_shared: None,
|
|
1175
|
+
},
|
|
1176
|
+
Arc::new(AtomicCell::new(None)),
|
|
1177
|
+
Arc::new(AtomicBool::new(graceful)),
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
let first = pb.poll().await.unwrap().unwrap();
|
|
1181
|
+
assert_eq!(first.0.task_token, vec![1]);
|
|
1182
|
+
|
|
1183
|
+
// Wait for second poll to be in-flight
|
|
1184
|
+
task_started.notified().await;
|
|
1185
|
+
|
|
1186
|
+
shutdown_token.cancel();
|
|
1187
|
+
|
|
1188
|
+
if graceful {
|
|
1189
|
+
// Release the poll — simulates server returning empty after ShutdownWorker
|
|
1190
|
+
task_complete.notify_one();
|
|
1191
|
+
|
|
1192
|
+
// Graceful: in-flight poll survives, second task is received
|
|
1193
|
+
let second = tokio::time::timeout(Duration::from_secs(2), pb.poll())
|
|
1194
|
+
.await
|
|
1195
|
+
.expect("graceful poll should complete")
|
|
1196
|
+
.unwrap()
|
|
1197
|
+
.unwrap();
|
|
1198
|
+
assert_eq!(second.0.task_token, vec![2]);
|
|
1199
|
+
} else {
|
|
1200
|
+
// Don't release — the poll should be killed by shutdown token before completing.
|
|
1201
|
+
// Buffer drains to None because the killed poll sends no result.
|
|
1202
|
+
let result = tokio::time::timeout(Duration::from_secs(2), pb.poll())
|
|
1203
|
+
.await
|
|
1204
|
+
.expect("legacy poll should resolve quickly");
|
|
1205
|
+
assert!(
|
|
1206
|
+
result.is_none(),
|
|
1207
|
+
"Legacy shutdown should kill in-flight poll, buffer returns None"
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
pb.shutdown().await;
|
|
1212
|
+
}
|
|
834
1213
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
pub(crate) mod protocol_messages;
|
|
2
2
|
|
|
3
3
|
use crate::{
|
|
4
|
-
|
|
4
|
+
TaskToken,
|
|
5
5
|
protosext::protocol_messages::IncomingProtocolMessage,
|
|
6
6
|
retry_logic::ValidatedRetryPolicy,
|
|
7
|
-
worker::{LEGACY_QUERY_ID, LocalActivityExecutionResult},
|
|
7
|
+
worker::{CompleteActivityError, LEGACY_QUERY_ID, LocalActivityExecutionResult},
|
|
8
8
|
};
|
|
9
9
|
use anyhow::anyhow;
|
|
10
10
|
use itertools::Itertools;
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
//! users during testing.
|
|
4
4
|
|
|
5
5
|
use crate::{
|
|
6
|
-
Worker,
|
|
6
|
+
Worker, WorkerConfig,
|
|
7
7
|
worker::{
|
|
8
|
-
PostActivateHookData,
|
|
8
|
+
PollerBehavior, PostActivateHookData,
|
|
9
9
|
client::mocks::{MockManualWorkerClient, mock_manual_worker_client},
|
|
10
10
|
},
|
|
11
11
|
};
|
|
@@ -30,7 +30,7 @@ use temporalio_common::{
|
|
|
30
30
|
},
|
|
31
31
|
},
|
|
32
32
|
},
|
|
33
|
-
worker::
|
|
33
|
+
worker::WorkerTaskTypes,
|
|
34
34
|
};
|
|
35
35
|
use tokio::sync::{Mutex as TokioMutex, mpsc, mpsc::UnboundedSender};
|
|
36
36
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
@@ -126,11 +126,20 @@ where
|
|
|
126
126
|
|
|
127
127
|
/// A history which will be used during replay verification. Since histories do not include the
|
|
128
128
|
/// workflow id, it must be manually attached.
|
|
129
|
-
#[derive(Debug, Clone
|
|
129
|
+
#[derive(Debug, Clone)]
|
|
130
130
|
pub struct HistoryForReplay {
|
|
131
131
|
hist: History,
|
|
132
132
|
workflow_id: String,
|
|
133
133
|
}
|
|
134
|
+
impl HistoryForReplay {
|
|
135
|
+
/// Create a new history from replay from something that looks like a history and a workflow id.
|
|
136
|
+
pub fn new(history: impl Into<History>, workflow_id: impl Into<String>) -> Self {
|
|
137
|
+
Self {
|
|
138
|
+
hist: history.into(),
|
|
139
|
+
workflow_id: workflow_id.into(),
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
134
143
|
impl From<TestHistoryBuilder> for HistoryForReplay {
|
|
135
144
|
fn from(thb: TestHistoryBuilder) -> Self {
|
|
136
145
|
thb.get_full_history_info().unwrap().into()
|
|
@@ -138,7 +147,7 @@ impl From<TestHistoryBuilder> for HistoryForReplay {
|
|
|
138
147
|
}
|
|
139
148
|
impl From<HistoryInfo> for HistoryForReplay {
|
|
140
149
|
fn from(histinfo: HistoryInfo) -> Self {
|
|
141
|
-
HistoryForReplay::new(histinfo
|
|
150
|
+
HistoryForReplay::new(histinfo, "fake".to_owned())
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
153
|
|