@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.
- package/Cargo.lock +239 -382
- package/Cargo.toml +11 -11
- package/lib/native.d.ts +10 -3
- package/package.json +3 -3
- 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/.cargo/config.toml +71 -11
- package/sdk-core/.clippy.toml +1 -0
- package/sdk-core/.github/workflows/heavy.yml +2 -0
- package/sdk-core/.github/workflows/per-pr.yml +50 -18
- package/sdk-core/ARCHITECTURE.md +44 -48
- package/sdk-core/Cargo.toml +26 -7
- package/sdk-core/README.md +4 -0
- package/sdk-core/arch_docs/diagrams/TimerMachine_Coverage.puml +14 -0
- package/sdk-core/arch_docs/diagrams/initial_event_history.png +0 -0
- package/sdk-core/arch_docs/sdks_intro.md +299 -0
- package/sdk-core/client/Cargo.toml +8 -7
- package/sdk-core/client/src/callback_based.rs +1 -2
- package/sdk-core/client/src/lib.rs +485 -299
- package/sdk-core/client/src/metrics.rs +32 -8
- package/sdk-core/client/src/proxy.rs +124 -5
- package/sdk-core/client/src/raw.rs +598 -307
- package/sdk-core/client/src/replaceable.rs +253 -0
- package/sdk-core/client/src/retry.rs +9 -6
- package/sdk-core/client/src/worker_registry/mod.rs +19 -3
- package/sdk-core/client/src/workflow_handle/mod.rs +20 -17
- package/sdk-core/core/Cargo.toml +100 -31
- package/sdk-core/core/src/core_tests/activity_tasks.rs +55 -225
- package/sdk-core/core/src/core_tests/mod.rs +2 -8
- package/sdk-core/core/src/core_tests/queries.rs +3 -5
- package/sdk-core/core/src/core_tests/replay_flag.rs +3 -62
- package/sdk-core/core/src/core_tests/updates.rs +4 -5
- package/sdk-core/core/src/core_tests/workers.rs +4 -3
- package/sdk-core/core/src/core_tests/workflow_cancels.rs +10 -7
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +28 -291
- package/sdk-core/core/src/ephemeral_server/mod.rs +15 -3
- package/sdk-core/core/src/internal_flags.rs +11 -1
- package/sdk-core/core/src/lib.rs +50 -36
- package/sdk-core/core/src/pollers/mod.rs +5 -5
- package/sdk-core/core/src/pollers/poll_buffer.rs +2 -2
- package/sdk-core/core/src/protosext/mod.rs +13 -5
- package/sdk-core/core/src/protosext/protocol_messages.rs +4 -11
- package/sdk-core/core/src/retry_logic.rs +256 -108
- package/sdk-core/core/src/telemetry/metrics.rs +1 -0
- package/sdk-core/core/src/telemetry/mod.rs +8 -2
- package/sdk-core/core/src/telemetry/prometheus_meter.rs +2 -2
- package/sdk-core/core/src/test_help/integ_helpers.rs +971 -0
- package/sdk-core/core/src/test_help/mod.rs +10 -1100
- package/sdk-core/core/src/test_help/unit_helpers.rs +218 -0
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +42 -6
- package/sdk-core/core/src/worker/activities/local_activities.rs +19 -19
- package/sdk-core/core/src/worker/activities.rs +10 -3
- package/sdk-core/core/src/worker/client/mocks.rs +3 -3
- package/sdk-core/core/src/worker/client.rs +130 -93
- package/sdk-core/core/src/worker/heartbeat.rs +12 -13
- package/sdk-core/core/src/worker/mod.rs +31 -21
- package/sdk-core/core/src/worker/nexus.rs +14 -3
- package/sdk-core/core/src/worker/slot_provider.rs +9 -0
- package/sdk-core/core/src/worker/tuner.rs +159 -0
- package/sdk-core/core/src/worker/workflow/history_update.rs +3 -265
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -54
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +0 -82
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +0 -67
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -192
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +0 -43
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +6 -554
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +0 -71
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +102 -3
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +10 -539
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +0 -139
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -119
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +6 -63
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +9 -4
- package/sdk-core/core/src/worker/workflow/mod.rs +5 -1
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +8 -3
- package/sdk-core/core-api/Cargo.toml +4 -4
- package/sdk-core/core-api/src/envconfig.rs +153 -54
- package/sdk-core/core-api/src/lib.rs +68 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +2 -1
- package/sdk-core/core-api/src/telemetry.rs +13 -0
- package/sdk-core/core-c-bridge/Cargo.toml +13 -8
- package/sdk-core/core-c-bridge/include/temporal-sdk-core-c-bridge.h +184 -22
- package/sdk-core/core-c-bridge/src/client.rs +462 -184
- package/sdk-core/core-c-bridge/src/envconfig.rs +314 -0
- package/sdk-core/core-c-bridge/src/lib.rs +1 -0
- package/sdk-core/core-c-bridge/src/random.rs +4 -4
- package/sdk-core/core-c-bridge/src/runtime.rs +22 -23
- package/sdk-core/core-c-bridge/src/testing.rs +1 -4
- package/sdk-core/core-c-bridge/src/tests/context.rs +31 -31
- package/sdk-core/core-c-bridge/src/tests/mod.rs +32 -28
- package/sdk-core/core-c-bridge/src/tests/utils.rs +7 -7
- package/sdk-core/core-c-bridge/src/worker.rs +319 -66
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +6 -1
- package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/dupe_transitions_fail.stderr +5 -5
- package/sdk-core/sdk/Cargo.toml +8 -2
- package/sdk-core/sdk/src/activity_context.rs +1 -1
- package/sdk-core/sdk/src/app_data.rs +1 -1
- package/sdk-core/sdk/src/interceptors.rs +1 -4
- package/sdk-core/sdk/src/lib.rs +1 -5
- package/sdk-core/sdk/src/workflow_context/options.rs +10 -1
- package/sdk-core/sdk/src/workflow_future.rs +1 -1
- package/sdk-core/sdk-core-protos/Cargo.toml +6 -6
- package/sdk-core/sdk-core-protos/build.rs +10 -23
- package/sdk-core/sdk-core-protos/protos/api_upstream/.github/workflows/create-release.yml +9 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +254 -5
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +234 -5
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +6 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +6 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +60 -2
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -6
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
- package/sdk-core/{test-utils → sdk-core-protos}/src/canned_histories.rs +5 -5
- package/sdk-core/sdk-core-protos/src/history_builder.rs +2 -2
- package/sdk-core/sdk-core-protos/src/lib.rs +25 -9
- package/sdk-core/sdk-core-protos/src/test_utils.rs +89 -0
- package/sdk-core/sdk-core-protos/src/utilities.rs +14 -5
- package/sdk-core/tests/c_bridge_smoke_test.c +10 -0
- package/sdk-core/tests/cloud_tests.rs +10 -8
- package/sdk-core/tests/common/http_proxy.rs +134 -0
- package/sdk-core/{test-utils/src/lib.rs → tests/common/mod.rs} +214 -281
- package/sdk-core/{test-utils/src → tests/common}/workflows.rs +4 -3
- package/sdk-core/tests/fuzzy_workflow.rs +1 -1
- package/sdk-core/tests/global_metric_tests.rs +8 -7
- package/sdk-core/tests/heavy_tests.rs +7 -3
- package/sdk-core/tests/integ_tests/client_tests.rs +111 -24
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +14 -9
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +4 -4
- package/sdk-core/tests/integ_tests/metrics_tests.rs +114 -14
- package/sdk-core/tests/integ_tests/pagination_tests.rs +273 -0
- package/sdk-core/tests/integ_tests/polling_tests.rs +311 -93
- package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
- package/sdk-core/tests/integ_tests/update_tests.rs +13 -7
- package/sdk-core/tests/integ_tests/visibility_tests.rs +26 -9
- package/sdk-core/tests/integ_tests/worker_tests.rs +668 -13
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +40 -24
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +244 -11
- package/sdk-core/tests/integ_tests/workflow_tests/appdata_propagation.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +78 -2
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +61 -2
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +465 -7
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +41 -2
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +315 -3
- package/sdk-core/tests/integ_tests/workflow_tests/eager.rs +1 -1
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1990 -14
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +65 -2
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +123 -23
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +525 -3
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +65 -16
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +32 -23
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +126 -5
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +1 -2
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +124 -8
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +62 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +67 -8
- package/sdk-core/tests/main.rs +26 -17
- package/sdk-core/tests/manual_tests.rs +5 -1
- package/sdk-core/tests/runner.rs +22 -40
- package/sdk-core/tests/shared_tests/mod.rs +1 -1
- package/sdk-core/tests/shared_tests/priority.rs +1 -1
- package/sdk-core/{core/benches/workflow_replay.rs → tests/workflow_replay_bench.rs} +10 -5
- package/src/client.rs +97 -20
- package/src/helpers/callbacks.rs +4 -4
- package/src/helpers/errors.rs +7 -1
- package/src/helpers/handles.rs +1 -0
- package/src/helpers/try_from_js.rs +4 -3
- package/src/lib.rs +3 -2
- package/src/metrics.rs +3 -0
- package/src/runtime.rs +5 -2
- package/src/worker.rs +9 -12
- package/ts/native.ts +13 -3
- package/sdk-core/arch_docs/diagrams/workflow_internals.svg +0 -1
- package/sdk-core/core/src/core_tests/child_workflows.rs +0 -281
- package/sdk-core/core/src/core_tests/determinism.rs +0 -318
- package/sdk-core/core/src/core_tests/local_activities.rs +0 -1442
- package/sdk-core/test-utils/Cargo.toml +0 -38
- package/sdk-core/test-utils/src/histfetch.rs +0 -28
- package/sdk-core/test-utils/src/interceptors.rs +0 -46
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//! Unit test helpers - only available in unit tests (cfg(test))
|
|
2
|
+
|
|
3
|
+
use futures_util::{StreamExt, stream::FuturesUnordered};
|
|
4
|
+
use std::{collections::HashSet, future::Future};
|
|
5
|
+
use temporal_sdk_core_api::Worker as CoreWorker;
|
|
6
|
+
use temporal_sdk_core_protos::coresdk::{
|
|
7
|
+
workflow_activation::workflow_activation_job,
|
|
8
|
+
workflow_completion::{WorkflowActivationCompletion, workflow_activation_completion},
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/// Given a desired number of concurrent executions and a provided function that produces a future,
|
|
12
|
+
/// run that many instances of the future concurrently.
|
|
13
|
+
///
|
|
14
|
+
/// Annoyingly, because of a sorta-bug in the way async blocks work, the async block produced by
|
|
15
|
+
/// the closure must be `async move` if it uses the provided iteration number. On the plus side,
|
|
16
|
+
/// since you're usually just accessing core in the closure, if core is a reference everything just
|
|
17
|
+
/// works. See <https://github.com/rust-lang/rust/issues/81653>
|
|
18
|
+
pub async fn fanout_tasks<FutureMaker, Fut>(num: usize, fm: FutureMaker)
|
|
19
|
+
where
|
|
20
|
+
FutureMaker: Fn(usize) -> Fut,
|
|
21
|
+
Fut: Future,
|
|
22
|
+
{
|
|
23
|
+
let mut tasks = FuturesUnordered::new();
|
|
24
|
+
for i in 0..num {
|
|
25
|
+
tasks.push(fm(i));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
while tasks.next().await.is_some() {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Generate asserts for [poll_and_reply] by passing patterns to match against the job list
|
|
32
|
+
#[macro_export]
|
|
33
|
+
macro_rules! job_assert {
|
|
34
|
+
($($pat:pat),+) => {
|
|
35
|
+
|res| {
|
|
36
|
+
assert_matches!(
|
|
37
|
+
res.jobs.as_slice(),
|
|
38
|
+
[$(WorkflowActivationJob {
|
|
39
|
+
variant: Some($pat),
|
|
40
|
+
}),+]
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type AsserterWithReply<'a> = (
|
|
47
|
+
&'a dyn Fn(&temporal_sdk_core_protos::coresdk::workflow_activation::WorkflowActivation),
|
|
48
|
+
workflow_activation_completion::Status,
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
/// Determines when workflows are kept in the cache or evicted for [poll_and_reply] type tests
|
|
52
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
53
|
+
pub(crate) enum WorkflowCachingPolicy {
|
|
54
|
+
/// Workflows are evicted after each workflow task completion. Note that this is *not* after
|
|
55
|
+
/// each workflow activation - there are often multiple activations per workflow task.
|
|
56
|
+
NonSticky,
|
|
57
|
+
|
|
58
|
+
/// Not a real mode, but good for imitating crashes. Evict workflows after *every* reply,
|
|
59
|
+
/// even if there are pending activations
|
|
60
|
+
AfterEveryReply,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// This function accepts a list of asserts and replies to workflow activations to run against the
|
|
64
|
+
/// provided instance of fake core.
|
|
65
|
+
///
|
|
66
|
+
/// It handles the business of re-sending the same activation replies over again in the event
|
|
67
|
+
/// of eviction or workflow activation failure. Activation failures specifically are only run once,
|
|
68
|
+
/// since they clearly can't be returned every time we replay the workflow, or it could never
|
|
69
|
+
/// proceed
|
|
70
|
+
pub(crate) async fn poll_and_reply<'a>(
|
|
71
|
+
worker: &'a crate::Worker,
|
|
72
|
+
eviction_mode: WorkflowCachingPolicy,
|
|
73
|
+
expect_and_reply: &'a [AsserterWithReply<'a>],
|
|
74
|
+
) {
|
|
75
|
+
poll_and_reply_clears_outstanding_evicts(worker, None, eviction_mode, expect_and_reply).await;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
use crate::{pollers::MockPoller, test_help::OutstandingWFTMap};
|
|
79
|
+
|
|
80
|
+
pub(crate) async fn poll_and_reply_clears_outstanding_evicts<'a>(
|
|
81
|
+
worker: &'a crate::Worker,
|
|
82
|
+
outstanding_map: Option<OutstandingWFTMap>,
|
|
83
|
+
eviction_mode: WorkflowCachingPolicy,
|
|
84
|
+
expect_and_reply: &'a [AsserterWithReply<'a>],
|
|
85
|
+
) {
|
|
86
|
+
let mut evictions = 0;
|
|
87
|
+
let expected_evictions = expect_and_reply.len() - 1;
|
|
88
|
+
let mut executed_failures = HashSet::new();
|
|
89
|
+
let expected_fail_count = expect_and_reply
|
|
90
|
+
.iter()
|
|
91
|
+
.filter(|(_, reply)| !reply.is_success())
|
|
92
|
+
.count();
|
|
93
|
+
|
|
94
|
+
'outer: loop {
|
|
95
|
+
let expect_iter = expect_and_reply.iter();
|
|
96
|
+
|
|
97
|
+
for (i, interaction) in expect_iter.enumerate() {
|
|
98
|
+
let (asserter, reply) = interaction;
|
|
99
|
+
let complete_is_failure = !reply.is_success();
|
|
100
|
+
// Only send activation failures once
|
|
101
|
+
if executed_failures.contains(&i) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let mut res = worker.poll_workflow_activation().await.unwrap();
|
|
106
|
+
if res.jobs.iter().any(|j| {
|
|
107
|
+
matches!(
|
|
108
|
+
j.variant,
|
|
109
|
+
Some(workflow_activation_job::Variant::RemoveFromCache(_))
|
|
110
|
+
)
|
|
111
|
+
}) && res.jobs.len() > 1
|
|
112
|
+
{
|
|
113
|
+
panic!("Saw an activation with an eviction & other work! {res:?}");
|
|
114
|
+
}
|
|
115
|
+
let is_eviction = res.is_only_eviction();
|
|
116
|
+
|
|
117
|
+
let mut do_release = false;
|
|
118
|
+
|
|
119
|
+
if is_eviction {
|
|
120
|
+
// If the job is an eviction, clear it, since in the tests we don't explicitly
|
|
121
|
+
// specify evict assertions
|
|
122
|
+
res.jobs.clear();
|
|
123
|
+
do_release = true;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// TODO: Can remove this if?
|
|
127
|
+
if !res.jobs.is_empty() {
|
|
128
|
+
asserter(&res);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let reply = if res.jobs.is_empty() {
|
|
132
|
+
// Just an eviction
|
|
133
|
+
WorkflowActivationCompletion::empty(res.run_id.clone())
|
|
134
|
+
} else {
|
|
135
|
+
// Eviction plus some work, we still want to issue the reply
|
|
136
|
+
WorkflowActivationCompletion {
|
|
137
|
+
run_id: res.run_id.clone(),
|
|
138
|
+
status: Some(reply.clone()),
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
let ends_execution = reply.has_execution_ending();
|
|
143
|
+
|
|
144
|
+
worker.complete_workflow_activation(reply).await.unwrap();
|
|
145
|
+
|
|
146
|
+
if do_release && let Some(omap) = outstanding_map.as_ref() {
|
|
147
|
+
omap.release_run(&res.run_id);
|
|
148
|
+
}
|
|
149
|
+
// Restart assertions from the beginning if it was an eviction (and workflow execution
|
|
150
|
+
// isn't over)
|
|
151
|
+
if is_eviction && !ends_execution {
|
|
152
|
+
continue 'outer;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if complete_is_failure {
|
|
156
|
+
executed_failures.insert(i);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
match eviction_mode {
|
|
160
|
+
WorkflowCachingPolicy::NonSticky => (),
|
|
161
|
+
WorkflowCachingPolicy::AfterEveryReply => {
|
|
162
|
+
if evictions < expected_evictions {
|
|
163
|
+
worker.request_workflow_eviction(&res.run_id);
|
|
164
|
+
evictions += 1;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
assert_eq!(expected_fail_count, executed_failures.len());
|
|
174
|
+
assert_eq!(worker.outstanding_workflow_tasks().await, 0);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
pub(crate) fn gen_assert_and_reply(
|
|
178
|
+
asserter: &dyn Fn(&temporal_sdk_core_protos::coresdk::workflow_activation::WorkflowActivation),
|
|
179
|
+
reply_commands: Vec<
|
|
180
|
+
temporal_sdk_core_protos::coresdk::workflow_commands::workflow_command::Variant,
|
|
181
|
+
>,
|
|
182
|
+
) -> AsserterWithReply<'_> {
|
|
183
|
+
(
|
|
184
|
+
asserter,
|
|
185
|
+
temporal_sdk_core_protos::coresdk::workflow_completion::Success::from_variants(
|
|
186
|
+
reply_commands,
|
|
187
|
+
)
|
|
188
|
+
.into(),
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
pub(crate) fn gen_assert_and_fail(
|
|
193
|
+
asserter: &dyn Fn(&temporal_sdk_core_protos::coresdk::workflow_activation::WorkflowActivation),
|
|
194
|
+
) -> AsserterWithReply<'_> {
|
|
195
|
+
(
|
|
196
|
+
asserter,
|
|
197
|
+
temporal_sdk_core_protos::coresdk::workflow_completion::Failure {
|
|
198
|
+
failure: Some(
|
|
199
|
+
temporal_sdk_core_protos::temporal::api::failure::v1::Failure {
|
|
200
|
+
message: "Intentional test failure".to_string(),
|
|
201
|
+
..Default::default()
|
|
202
|
+
},
|
|
203
|
+
),
|
|
204
|
+
..Default::default()
|
|
205
|
+
}
|
|
206
|
+
.into(),
|
|
207
|
+
)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
pub(crate) fn mock_poller<T>() -> MockPoller<T>
|
|
211
|
+
where
|
|
212
|
+
T: Send + Sync + 'static,
|
|
213
|
+
{
|
|
214
|
+
let mut mock_poller = MockPoller::new();
|
|
215
|
+
mock_poller.expect_shutdown_box().return_const(());
|
|
216
|
+
mock_poller.expect_notify_shutdown().return_const(());
|
|
217
|
+
mock_poller
|
|
218
|
+
}
|
|
@@ -42,6 +42,7 @@ enum HeartbeatAction {
|
|
|
42
42
|
Evict {
|
|
43
43
|
token: TaskToken,
|
|
44
44
|
on_complete: Arc<Notify>,
|
|
45
|
+
should_flush: bool,
|
|
45
46
|
},
|
|
46
47
|
CompleteReport(TaskToken),
|
|
47
48
|
CompleteThrottle(TaskToken),
|
|
@@ -118,7 +119,7 @@ impl ActivityHeartbeatManager {
|
|
|
118
119
|
HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
|
|
119
120
|
HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
|
|
120
121
|
HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
|
|
121
|
-
HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
|
|
122
|
+
HeartbeatAction::Evict{ token, on_complete, should_flush } => hb_states.evict(token, on_complete, should_flush),
|
|
122
123
|
},
|
|
123
124
|
hb_states,
|
|
124
125
|
))
|
|
@@ -230,13 +231,14 @@ impl ActivityHeartbeatManager {
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
/// Tell the heartbeat manager we are done forever with a certain task, so it may be forgotten.
|
|
233
|
-
///
|
|
234
|
+
/// If should_flush is true, will also force-flush the most recently provided details.
|
|
234
235
|
/// Record *should* not be called with the same TaskToken after calling this.
|
|
235
|
-
pub(super) async fn evict(&self, task_token: TaskToken) {
|
|
236
|
+
pub(super) async fn evict(&self, task_token: TaskToken, should_flush: bool) {
|
|
236
237
|
let completed = Arc::new(Notify::new());
|
|
237
238
|
let _ = self.heartbeat_tx.send(HeartbeatAction::Evict {
|
|
238
239
|
token: task_token,
|
|
239
240
|
on_complete: completed.clone(),
|
|
241
|
+
should_flush,
|
|
240
242
|
});
|
|
241
243
|
completed.notified().await;
|
|
242
244
|
}
|
|
@@ -397,12 +399,15 @@ impl HeartbeatStreamState {
|
|
|
397
399
|
&mut self,
|
|
398
400
|
tt: TaskToken,
|
|
399
401
|
on_complete: Arc<Notify>,
|
|
402
|
+
should_flush: bool,
|
|
400
403
|
) -> Option<HeartbeatExecutorAction> {
|
|
401
404
|
if let Some(state) = self.tt_to_state.remove(&tt) {
|
|
402
405
|
if let Some(cancel_tok) = state.throttled_cancellation_token {
|
|
403
406
|
cancel_tok.cancel();
|
|
404
407
|
}
|
|
405
|
-
if let Some(last_deets) = state.last_recorded_details
|
|
408
|
+
if let Some(last_deets) = state.last_recorded_details
|
|
409
|
+
&& should_flush
|
|
410
|
+
{
|
|
406
411
|
self.tt_needs_flush.insert(tt.clone(), on_complete);
|
|
407
412
|
return Some(HeartbeatExecutorAction::Report {
|
|
408
413
|
task_token: tt,
|
|
@@ -524,7 +529,7 @@ mod test {
|
|
|
524
529
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
525
530
|
// Let it propagate
|
|
526
531
|
sleep(Duration::from_millis(10)).await;
|
|
527
|
-
hm.evict(fake_task_token.clone().into()).await;
|
|
532
|
+
hm.evict(fake_task_token.clone().into(), true).await;
|
|
528
533
|
record_heartbeat(&hm, fake_task_token, 0, Duration::from_millis(100));
|
|
529
534
|
// Let it propagate
|
|
530
535
|
sleep(Duration::from_millis(10)).await;
|
|
@@ -543,7 +548,38 @@ mod test {
|
|
|
543
548
|
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
|
|
544
549
|
let fake_task_token = vec![1, 2, 3];
|
|
545
550
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
546
|
-
hm.evict(fake_task_token.clone().into()).await;
|
|
551
|
+
hm.evict(fake_task_token.clone().into(), true).await;
|
|
552
|
+
hm.shutdown().await;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
#[tokio::test]
|
|
556
|
+
async fn no_flush_on_successful_completion() {
|
|
557
|
+
let mut mock_client = mock_worker_client();
|
|
558
|
+
// Should only expect 1 heartbeat call, not 2 (the second would be from evict flushing)
|
|
559
|
+
mock_client
|
|
560
|
+
.expect_record_activity_heartbeat()
|
|
561
|
+
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
562
|
+
.times(1);
|
|
563
|
+
let (cancel_tx, _cancel_rx) = unbounded_channel();
|
|
564
|
+
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
|
|
565
|
+
let fake_task_token = vec![1, 2, 3];
|
|
566
|
+
|
|
567
|
+
// Record initial heartbeat - this should be sent immediately
|
|
568
|
+
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
569
|
+
|
|
570
|
+
// Wait a bit for initial heartbeat to process and enter throttling phase
|
|
571
|
+
sleep(Duration::from_millis(50)).await;
|
|
572
|
+
|
|
573
|
+
// Record another heartbeat while throttled - this should be stored in last_recorded_details
|
|
574
|
+
record_heartbeat(&hm, fake_task_token.clone(), 1, Duration::from_millis(100));
|
|
575
|
+
|
|
576
|
+
// Wait a bit to ensure the second heartbeat is recorded but not sent
|
|
577
|
+
sleep(Duration::from_millis(10)).await;
|
|
578
|
+
|
|
579
|
+
// Evict the activity with should_flush false
|
|
580
|
+
// This should NOT send the stored heartbeat details since the activity completed successfully
|
|
581
|
+
hm.evict(fake_task_token.into(), false).await;
|
|
582
|
+
|
|
547
583
|
hm.shutdown().await;
|
|
548
584
|
}
|
|
549
585
|
|
|
@@ -2,7 +2,6 @@ use crate::{
|
|
|
2
2
|
MetricsContext, TaskToken,
|
|
3
3
|
abstractions::{MeteredPermitDealer, OwnedMeteredSemPermit, UsedMeteredSemPermit, dbg_panic},
|
|
4
4
|
protosext::ValidScheduleLA,
|
|
5
|
-
retry_logic::RetryPolicyExt,
|
|
6
5
|
telemetry::metrics::{activity_type, should_record_failure_metric, workflow_type},
|
|
7
6
|
worker::workflow::HeartbeatTimeoutMsg,
|
|
8
7
|
};
|
|
@@ -13,6 +12,7 @@ use parking_lot::{Mutex, MutexGuard};
|
|
|
13
12
|
use std::{
|
|
14
13
|
collections::{HashMap, hash_map::Entry},
|
|
15
14
|
fmt::{Debug, Formatter},
|
|
15
|
+
num::NonZero,
|
|
16
16
|
pin::Pin,
|
|
17
17
|
task::{Context, Poll},
|
|
18
18
|
time::{Duration, Instant, SystemTime},
|
|
@@ -490,7 +490,7 @@ impl LocalActivityManager {
|
|
|
490
490
|
dispatch_time: Instant::now(),
|
|
491
491
|
attempt,
|
|
492
492
|
_permit: permit.into_used(LocalActivitySlotInfo {
|
|
493
|
-
activity_type:
|
|
493
|
+
activity_type: sa.activity_type.clone(),
|
|
494
494
|
}),
|
|
495
495
|
},
|
|
496
496
|
);
|
|
@@ -525,7 +525,7 @@ impl LocalActivityManager {
|
|
|
525
525
|
.or(schedule_to_close)
|
|
526
526
|
.and_then(|t| t.try_into().ok()),
|
|
527
527
|
heartbeat_timeout: None,
|
|
528
|
-
retry_policy: Some(sa.retry_policy),
|
|
528
|
+
retry_policy: Some(sa.retry_policy.into()),
|
|
529
529
|
priority: Some(Default::default()),
|
|
530
530
|
is_local: true,
|
|
531
531
|
})),
|
|
@@ -570,7 +570,7 @@ impl LocalActivityManager {
|
|
|
570
570
|
macro_rules! calc_backoff {
|
|
571
571
|
($fail: ident) => {
|
|
572
572
|
info.la_info.schedule_cmd.retry_policy.should_retry(
|
|
573
|
-
info.attempt
|
|
573
|
+
info.attempt.try_into().unwrap_or(NonZero::<u32>::MIN),
|
|
574
574
|
$fail
|
|
575
575
|
.failure
|
|
576
576
|
.as_ref()
|
|
@@ -976,7 +976,7 @@ impl Drop for TimeoutBag {
|
|
|
976
976
|
#[cfg(test)]
|
|
977
977
|
mod tests {
|
|
978
978
|
use super::*;
|
|
979
|
-
use crate::{prost_dur, protosext::LACloseTimeouts};
|
|
979
|
+
use crate::{prost_dur, protosext::LACloseTimeouts, retry_logic::ValidatedRetryPolicy};
|
|
980
980
|
use futures_util::FutureExt;
|
|
981
981
|
use temporal_sdk_core_protos::temporal::api::{
|
|
982
982
|
common::v1::RetryPolicy,
|
|
@@ -1111,13 +1111,13 @@ mod tests {
|
|
|
1111
1111
|
seq: 1,
|
|
1112
1112
|
activity_id: 1.to_string(),
|
|
1113
1113
|
attempt: 5,
|
|
1114
|
-
retry_policy: RetryPolicy {
|
|
1114
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1115
1115
|
initial_interval: Some(prost_dur!(from_secs(1))),
|
|
1116
1116
|
backoff_coefficient: 10.0,
|
|
1117
1117
|
maximum_interval: Some(prost_dur!(from_secs(10))),
|
|
1118
1118
|
maximum_attempts: 10,
|
|
1119
1119
|
non_retryable_error_types: vec![],
|
|
1120
|
-
},
|
|
1120
|
+
}),
|
|
1121
1121
|
local_retry_threshold: Duration::from_secs(5),
|
|
1122
1122
|
..Default::default()
|
|
1123
1123
|
},
|
|
@@ -1146,13 +1146,13 @@ mod tests {
|
|
|
1146
1146
|
seq: 1,
|
|
1147
1147
|
activity_id: "1".to_string(),
|
|
1148
1148
|
attempt: 1,
|
|
1149
|
-
retry_policy: RetryPolicy {
|
|
1149
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1150
1150
|
initial_interval: Some(prost_dur!(from_secs(1))),
|
|
1151
1151
|
backoff_coefficient: 10.0,
|
|
1152
1152
|
maximum_interval: Some(prost_dur!(from_secs(10))),
|
|
1153
1153
|
maximum_attempts: 10,
|
|
1154
1154
|
non_retryable_error_types: vec!["TestError".to_string()],
|
|
1155
|
-
},
|
|
1155
|
+
}),
|
|
1156
1156
|
local_retry_threshold: Duration::from_secs(5),
|
|
1157
1157
|
..Default::default()
|
|
1158
1158
|
},
|
|
@@ -1190,13 +1190,13 @@ mod tests {
|
|
|
1190
1190
|
seq: 1,
|
|
1191
1191
|
activity_id: 1.to_string(),
|
|
1192
1192
|
attempt: 5,
|
|
1193
|
-
retry_policy: RetryPolicy {
|
|
1193
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1194
1194
|
initial_interval: Some(prost_dur!(from_secs(10))),
|
|
1195
1195
|
backoff_coefficient: 1.0,
|
|
1196
1196
|
maximum_interval: Some(prost_dur!(from_secs(10))),
|
|
1197
1197
|
maximum_attempts: 10,
|
|
1198
1198
|
non_retryable_error_types: vec![],
|
|
1199
|
-
},
|
|
1199
|
+
}),
|
|
1200
1200
|
local_retry_threshold: Duration::from_secs(500),
|
|
1201
1201
|
..Default::default()
|
|
1202
1202
|
},
|
|
@@ -1239,11 +1239,11 @@ mod tests {
|
|
|
1239
1239
|
seq: 1,
|
|
1240
1240
|
activity_id: 1.to_string(),
|
|
1241
1241
|
attempt: 5,
|
|
1242
|
-
retry_policy: RetryPolicy {
|
|
1242
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1243
1243
|
initial_interval: Some(prost_dur!(from_millis(10))),
|
|
1244
1244
|
backoff_coefficient: 1.0,
|
|
1245
1245
|
..Default::default()
|
|
1246
|
-
},
|
|
1246
|
+
}),
|
|
1247
1247
|
local_retry_threshold: Duration::from_secs(500),
|
|
1248
1248
|
..Default::default()
|
|
1249
1249
|
},
|
|
@@ -1276,11 +1276,11 @@ mod tests {
|
|
|
1276
1276
|
seq: 1,
|
|
1277
1277
|
activity_id: 1.to_string(),
|
|
1278
1278
|
attempt: 5,
|
|
1279
|
-
retry_policy: RetryPolicy {
|
|
1279
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1280
1280
|
initial_interval: Some(prost_dur!(from_millis(10))),
|
|
1281
1281
|
backoff_coefficient: 1.0,
|
|
1282
1282
|
..Default::default()
|
|
1283
|
-
},
|
|
1283
|
+
}),
|
|
1284
1284
|
local_retry_threshold: Duration::from_secs(500),
|
|
1285
1285
|
schedule_to_start_timeout: Some(timeout),
|
|
1286
1286
|
..Default::default()
|
|
@@ -1319,12 +1319,12 @@ mod tests {
|
|
|
1319
1319
|
seq: 1,
|
|
1320
1320
|
activity_id: 1.to_string(),
|
|
1321
1321
|
attempt: 5,
|
|
1322
|
-
retry_policy: RetryPolicy {
|
|
1322
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1323
1323
|
initial_interval: Some(prost_dur!(from_millis(10))),
|
|
1324
1324
|
backoff_coefficient: 1.0,
|
|
1325
1325
|
maximum_attempts: 1,
|
|
1326
1326
|
..Default::default()
|
|
1327
|
-
},
|
|
1327
|
+
}),
|
|
1328
1328
|
local_retry_threshold: Duration::from_secs(500),
|
|
1329
1329
|
close_timeouts,
|
|
1330
1330
|
..Default::default()
|
|
@@ -1400,11 +1400,11 @@ mod tests {
|
|
|
1400
1400
|
schedule_cmd: ValidScheduleLA {
|
|
1401
1401
|
seq: 1,
|
|
1402
1402
|
activity_id: 1.to_string(),
|
|
1403
|
-
retry_policy: RetryPolicy {
|
|
1403
|
+
retry_policy: ValidatedRetryPolicy::from_proto_with_defaults(RetryPolicy {
|
|
1404
1404
|
initial_interval: Some(prost_dur!(from_millis(1))),
|
|
1405
1405
|
backoff_coefficient: 1.0,
|
|
1406
1406
|
..Default::default()
|
|
1407
|
-
},
|
|
1407
|
+
}),
|
|
1408
1408
|
local_retry_threshold: Duration::from_secs(500),
|
|
1409
1409
|
..Default::default()
|
|
1410
1410
|
},
|
|
@@ -333,7 +333,14 @@ impl WorkerActivityTasks {
|
|
|
333
333
|
if let Some(jh) = act_info.local_timeouts_task {
|
|
334
334
|
jh.abort()
|
|
335
335
|
};
|
|
336
|
-
|
|
336
|
+
let should_flush = !known_not_found
|
|
337
|
+
&& !matches!(
|
|
338
|
+
&status,
|
|
339
|
+
aer::Status::Completed(_) | aer::Status::WillCompleteAsync(_)
|
|
340
|
+
);
|
|
341
|
+
self.heartbeat_manager
|
|
342
|
+
.evict(task_token.clone(), should_flush)
|
|
343
|
+
.await;
|
|
337
344
|
|
|
338
345
|
// No need to report activities which we already know the server doesn't care about
|
|
339
346
|
if !known_not_found {
|
|
@@ -400,8 +407,6 @@ impl WorkerActivityTasks {
|
|
|
400
407
|
}
|
|
401
408
|
};
|
|
402
409
|
|
|
403
|
-
self.complete_notify.notify_waiters();
|
|
404
|
-
|
|
405
410
|
if let Some(e) = maybe_net_err {
|
|
406
411
|
if e.code() == tonic::Code::NotFound {
|
|
407
412
|
warn!(task_token=?task_token, details=?e, "Activity not found on \
|
|
@@ -418,6 +423,8 @@ impl WorkerActivityTasks {
|
|
|
418
423
|
&task_token
|
|
419
424
|
);
|
|
420
425
|
}
|
|
426
|
+
|
|
427
|
+
self.complete_notify.notify_waiters();
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
/// Attempt to record an activity heartbeat
|
|
@@ -20,9 +20,9 @@ pub(crate) static DEFAULT_TEST_CAPABILITIES: &Capabilities = &Capabilities {
|
|
|
20
20
|
nexus: false,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
#[cfg(test)]
|
|
23
|
+
#[cfg(any(feature = "test-utilities", test))]
|
|
24
24
|
/// Create a mock client primed with basic necessary expectations
|
|
25
|
-
pub
|
|
25
|
+
pub fn mock_worker_client() -> MockWorkerClient {
|
|
26
26
|
let mut r = MockWorkerClient::new();
|
|
27
27
|
r.expect_capabilities()
|
|
28
28
|
.returning(|| Some(*DEFAULT_TEST_CAPABILITIES));
|
|
@@ -152,7 +152,7 @@ mockall::mock! {
|
|
|
152
152
|
|
|
153
153
|
fn record_worker_heartbeat<'a, 'b>(&self, heartbeat: WorkerHeartbeat) -> impl Future<Output = Result<RecordWorkerHeartbeatResponse>> + Send + 'b where 'a: 'b, Self: 'b;
|
|
154
154
|
|
|
155
|
-
fn replace_client(&self, new_client:
|
|
155
|
+
fn replace_client(&self, new_client: Client);
|
|
156
156
|
fn capabilities(&self) -> Option<Capabilities>;
|
|
157
157
|
fn workers(&self) -> Arc<SlotManager>;
|
|
158
158
|
fn is_mock(&self) -> bool;
|