@temporalio/core-bridge 0.14.0 → 0.16.4
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 +162 -38
- package/Cargo.toml +3 -3
- package/index.d.ts +14 -1
- package/index.node +0 -0
- package/package.json +8 -5
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/{x86_64-pc-windows-gnu → 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/scripts/build.js +77 -34
- package/sdk-core/.buildkite/docker/Dockerfile +1 -1
- package/sdk-core/Cargo.toml +6 -5
- package/sdk-core/fsm/Cargo.toml +1 -1
- package/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +2 -2
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +8 -9
- package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +13 -7
- package/sdk-core/fsm/rustfsm_trait/Cargo.toml +2 -2
- package/sdk-core/fsm/rustfsm_trait/src/lib.rs +1 -1
- package/sdk-core/protos/local/workflow_activation.proto +6 -3
- package/sdk-core/sdk-core-protos/Cargo.toml +4 -4
- package/sdk-core/sdk-core-protos/src/lib.rs +38 -50
- package/sdk-core/src/core_tests/activity_tasks.rs +5 -5
- package/sdk-core/src/core_tests/child_workflows.rs +55 -29
- package/sdk-core/src/core_tests/determinism.rs +19 -9
- package/sdk-core/src/core_tests/mod.rs +3 -3
- package/sdk-core/src/core_tests/retry.rs +14 -8
- package/sdk-core/src/core_tests/workers.rs +1 -1
- package/sdk-core/src/core_tests/workflow_tasks.rs +347 -4
- package/sdk-core/src/errors.rs +27 -44
- package/sdk-core/src/lib.rs +13 -3
- package/sdk-core/src/machines/activity_state_machine.rs +44 -5
- package/sdk-core/src/machines/child_workflow_state_machine.rs +31 -11
- package/sdk-core/src/machines/complete_workflow_state_machine.rs +1 -1
- package/sdk-core/src/machines/continue_as_new_workflow_state_machine.rs +1 -1
- package/sdk-core/src/machines/mod.rs +18 -23
- package/sdk-core/src/machines/patch_state_machine.rs +8 -8
- package/sdk-core/src/machines/signal_external_state_machine.rs +22 -1
- package/sdk-core/src/machines/timer_state_machine.rs +21 -3
- package/sdk-core/src/machines/transition_coverage.rs +3 -3
- package/sdk-core/src/machines/workflow_machines.rs +11 -11
- package/sdk-core/src/pending_activations.rs +27 -22
- package/sdk-core/src/pollers/gateway.rs +15 -7
- package/sdk-core/src/pollers/poll_buffer.rs +6 -5
- package/sdk-core/src/pollers/retry.rs +153 -120
- package/sdk-core/src/prototype_rust_sdk/workflow_context.rs +61 -46
- package/sdk-core/src/prototype_rust_sdk/workflow_future.rs +13 -12
- package/sdk-core/src/prototype_rust_sdk.rs +17 -23
- package/sdk-core/src/telemetry/metrics.rs +2 -4
- package/sdk-core/src/telemetry/mod.rs +6 -7
- package/sdk-core/src/test_help/canned_histories.rs +17 -93
- package/sdk-core/src/test_help/history_builder.rs +61 -2
- package/sdk-core/src/test_help/history_info.rs +21 -2
- package/sdk-core/src/test_help/mod.rs +26 -34
- package/sdk-core/src/worker/activities/activity_heartbeat_manager.rs +246 -138
- package/sdk-core/src/worker/activities.rs +46 -45
- package/sdk-core/src/worker/config.rs +11 -0
- package/sdk-core/src/worker/dispatcher.rs +5 -5
- package/sdk-core/src/worker/mod.rs +86 -56
- package/sdk-core/src/workflow/driven_workflow.rs +3 -3
- package/sdk-core/src/workflow/history_update.rs +1 -1
- package/sdk-core/src/workflow/mod.rs +2 -1
- package/sdk-core/src/workflow/workflow_tasks/cache_manager.rs +13 -17
- package/sdk-core/src/workflow/workflow_tasks/concurrency_manager.rs +10 -18
- package/sdk-core/src/workflow/workflow_tasks/mod.rs +72 -57
- package/sdk-core/test_utils/Cargo.toml +1 -1
- package/sdk-core/test_utils/src/lib.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +61 -1
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +49 -0
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +1 -0
- package/src/conversions.rs +17 -0
- package/src/errors.rs +0 -7
- package/src/lib.rs +0 -20
|
@@ -15,34 +15,45 @@ use temporal_sdk_core_protos::{
|
|
|
15
15
|
use tokio::{
|
|
16
16
|
sync::{
|
|
17
17
|
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
|
|
18
|
-
|
|
18
|
+
Mutex,
|
|
19
19
|
},
|
|
20
20
|
task::JoinHandle,
|
|
21
21
|
};
|
|
22
|
+
use tokio_util::sync::CancellationToken;
|
|
22
23
|
|
|
23
24
|
/// Used to supply new heartbeat events to the activity heartbeat manager, or to send a shutdown
|
|
24
25
|
/// request.
|
|
25
26
|
pub(crate) struct ActivityHeartbeatManager {
|
|
26
|
-
heartbeat_tx: UnboundedSender<HBAction>,
|
|
27
27
|
/// Cancellations that have been received when heartbeating are queued here and can be consumed
|
|
28
28
|
/// by [fetch_cancellations]
|
|
29
29
|
incoming_cancels: Mutex<UnboundedReceiver<PendingActivityCancel>>,
|
|
30
|
-
|
|
30
|
+
cancellation_token: CancellationToken,
|
|
31
31
|
/// Used during `shutdown` to await until all inflight requests are sent.
|
|
32
32
|
join_handle: Mutex<Option<JoinHandle<()>>>,
|
|
33
|
+
heartbeat_tx: UnboundedSender<HeartbeatAction>,
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
#[derive(Debug)]
|
|
36
|
-
enum
|
|
37
|
-
|
|
37
|
+
enum HeartbeatAction {
|
|
38
|
+
SendHeartbeat(ValidActivityHeartbeat),
|
|
38
39
|
Evict(TaskToken),
|
|
40
|
+
CompleteReport(TaskToken),
|
|
41
|
+
CompleteThrottle(TaskToken),
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
#[derive(Debug)]
|
|
42
45
|
pub struct ValidActivityHeartbeat {
|
|
43
46
|
pub task_token: TaskToken,
|
|
44
47
|
pub details: Vec<common::Payload>,
|
|
45
|
-
pub
|
|
48
|
+
pub throttle_interval: time::Duration,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug)]
|
|
52
|
+
enum HeartbeatExecutorAction {
|
|
53
|
+
/// Heartbeats are throttled for this task token, sleep until duration or wait to be cancelled
|
|
54
|
+
Sleep(TaskToken, Duration, CancellationToken),
|
|
55
|
+
/// Report heartbeat to the server
|
|
56
|
+
Report(TaskToken, Vec<common::Payload>),
|
|
46
57
|
}
|
|
47
58
|
|
|
48
59
|
/// Handle that is used by the core for all interactions with the manager, allows sending new
|
|
@@ -58,18 +69,17 @@ impl ActivityHeartbeatManager {
|
|
|
58
69
|
/// no longer an efficient way to forget about that task token.
|
|
59
70
|
pub(super) fn record(
|
|
60
71
|
&self,
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
hb: ActivityHeartbeat,
|
|
73
|
+
throttle_interval: Duration,
|
|
63
74
|
) -> Result<(), ActivityHeartbeatError> {
|
|
64
|
-
if
|
|
75
|
+
if self.cancellation_token.is_cancelled() {
|
|
65
76
|
return Err(ActivityHeartbeatError::ShuttingDown);
|
|
66
77
|
}
|
|
67
|
-
|
|
68
78
|
self.heartbeat_tx
|
|
69
|
-
.send(
|
|
70
|
-
task_token: TaskToken(
|
|
71
|
-
details:
|
|
72
|
-
|
|
79
|
+
.send(HeartbeatAction::SendHeartbeat(ValidActivityHeartbeat {
|
|
80
|
+
task_token: TaskToken(hb.task_token),
|
|
81
|
+
details: hb.details,
|
|
82
|
+
throttle_interval,
|
|
73
83
|
}))
|
|
74
84
|
.expect("Receive half of the heartbeats event channel must not be dropped");
|
|
75
85
|
|
|
@@ -77,9 +87,9 @@ impl ActivityHeartbeatManager {
|
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
/// Tell the heartbeat manager we are done forever with a certain task, so it may be forgotten.
|
|
80
|
-
/// Record should
|
|
90
|
+
/// Record *should* not be called with the same TaskToken after calling this.
|
|
81
91
|
pub(super) fn evict(&self, task_token: TaskToken) {
|
|
82
|
-
let _ = self.heartbeat_tx.send(
|
|
92
|
+
let _ = self.heartbeat_tx.send(HeartbeatAction::Evict(task_token));
|
|
83
93
|
}
|
|
84
94
|
|
|
85
95
|
/// Returns a future that resolves any time there is a new activity cancel that must be
|
|
@@ -88,15 +98,11 @@ impl ActivityHeartbeatManager {
|
|
|
88
98
|
self.incoming_cancels.lock().await.recv().await
|
|
89
99
|
}
|
|
90
100
|
|
|
91
|
-
pub(super) fn notify_shutdown(&self) {
|
|
92
|
-
let _ = self.shutting_down.send(true);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
101
|
// TODO: Can own self now!
|
|
96
|
-
/// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all
|
|
97
|
-
///
|
|
102
|
+
/// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all in-flight
|
|
103
|
+
/// heartbeat requests to be flushed to the server.
|
|
98
104
|
pub(super) async fn shutdown(&self) {
|
|
99
|
-
self.
|
|
105
|
+
let _ = self.cancellation_token.cancel();
|
|
100
106
|
let mut handle = self.join_handle.lock().await;
|
|
101
107
|
if let Some(h) = handle.take() {
|
|
102
108
|
h.await.expect("shutdown should exit cleanly");
|
|
@@ -105,132 +111,212 @@ impl ActivityHeartbeatManager {
|
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
#[derive(Debug)]
|
|
108
|
-
struct
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
struct ActivityHeartbeatState {
|
|
115
|
+
/// If None and throttle interval is over, untrack this task token
|
|
116
|
+
last_recorded_details: Option<Vec<common::Payload>>,
|
|
117
|
+
last_send_requested: Instant,
|
|
118
|
+
throttle_interval: Duration,
|
|
119
|
+
throttled_cancellation_token: Option<CancellationToken>,
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
impl
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
impl ActivityHeartbeatState {
|
|
123
|
+
/// Get duration to sleep by subtracting `throttle_interval` by elapsed time since
|
|
124
|
+
/// `last_send_requested`
|
|
125
|
+
fn get_throttle_sleep_duration(&self) -> Duration {
|
|
126
|
+
let time_since_last_sent = self.last_send_requested.elapsed();
|
|
127
|
+
|
|
128
|
+
if time_since_last_sent > Duration::ZERO && self.throttle_interval > time_since_last_sent {
|
|
129
|
+
self.throttle_interval - time_since_last_sent
|
|
130
|
+
} else {
|
|
131
|
+
Duration::ZERO
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
#[derive(Debug)]
|
|
123
|
-
struct
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
struct HeartbeatStreamState {
|
|
138
|
+
tt_to_state: HashMap<TaskToken, ActivityHeartbeatState>,
|
|
139
|
+
incoming_hbs: UnboundedReceiver<HeartbeatAction>,
|
|
140
|
+
/// Token that can be used to cancel the entire stream.
|
|
141
|
+
/// Requests to the server are not cancelled with this token.
|
|
142
|
+
cancellation_token: CancellationToken,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl HeartbeatStreamState {
|
|
146
|
+
fn new() -> (Self, UnboundedSender<HeartbeatAction>, CancellationToken) {
|
|
147
|
+
let (heartbeat_tx, incoming_hbs) = unbounded_channel();
|
|
148
|
+
let cancellation_token = CancellationToken::new();
|
|
149
|
+
(
|
|
150
|
+
Self {
|
|
151
|
+
cancellation_token: cancellation_token.clone(),
|
|
152
|
+
tt_to_state: Default::default(),
|
|
153
|
+
incoming_hbs,
|
|
154
|
+
},
|
|
155
|
+
heartbeat_tx,
|
|
156
|
+
cancellation_token,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/// Record a heartbeat received from lang
|
|
161
|
+
fn record(&mut self, hb: ValidActivityHeartbeat) -> Option<HeartbeatExecutorAction> {
|
|
162
|
+
match self.tt_to_state.entry(hb.task_token.clone()) {
|
|
163
|
+
Entry::Vacant(e) => {
|
|
164
|
+
let state = ActivityHeartbeatState {
|
|
165
|
+
throttle_interval: hb.throttle_interval,
|
|
166
|
+
last_send_requested: Instant::now(),
|
|
167
|
+
// Don't record here because we already flush out these details.
|
|
168
|
+
// None is used to mark that after throttling we can stop tracking this task
|
|
169
|
+
// token.
|
|
170
|
+
last_recorded_details: None,
|
|
171
|
+
throttled_cancellation_token: None,
|
|
172
|
+
};
|
|
173
|
+
e.insert(state);
|
|
174
|
+
Some(HeartbeatExecutorAction::Report(hb.task_token, hb.details))
|
|
175
|
+
}
|
|
176
|
+
Entry::Occupied(mut o) => {
|
|
177
|
+
let state = o.get_mut();
|
|
178
|
+
state.last_recorded_details = Some(hb.details);
|
|
179
|
+
None
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Heartbeat report to server completed
|
|
185
|
+
fn handle_report_completed(&mut self, tt: TaskToken) -> Option<HeartbeatExecutorAction> {
|
|
186
|
+
if let Some(st) = self.tt_to_state.get_mut(&tt) {
|
|
187
|
+
let cancellation_token = self.cancellation_token.child_token();
|
|
188
|
+
st.throttled_cancellation_token = Some(cancellation_token.clone());
|
|
189
|
+
// Always sleep for simplicity even if the duration is 0
|
|
190
|
+
Some(HeartbeatExecutorAction::Sleep(
|
|
191
|
+
tt.clone(),
|
|
192
|
+
st.get_throttle_sleep_duration(),
|
|
193
|
+
cancellation_token,
|
|
194
|
+
))
|
|
195
|
+
} else {
|
|
196
|
+
None
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// Throttling completed, report or stop tracking task token
|
|
201
|
+
fn handle_throttle_completed(&mut self, tt: TaskToken) -> Option<HeartbeatExecutorAction> {
|
|
202
|
+
match self.tt_to_state.entry(tt.clone()) {
|
|
203
|
+
Entry::Occupied(mut e) => {
|
|
204
|
+
let state = e.get_mut();
|
|
205
|
+
if let Some(details) = state.last_recorded_details.take() {
|
|
206
|
+
// Delete the recorded details before reporting
|
|
207
|
+
// Reset the cancellation token and schedule another report
|
|
208
|
+
state.throttled_cancellation_token = None;
|
|
209
|
+
state.last_send_requested = Instant::now();
|
|
210
|
+
Some(HeartbeatExecutorAction::Report(tt, details))
|
|
211
|
+
} else {
|
|
212
|
+
// Nothing to report, forget this task token
|
|
213
|
+
e.remove();
|
|
214
|
+
None
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
Entry::Vacant(_) => None,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Activity should not be tracked anymore, cancel throttle timer if running
|
|
222
|
+
fn evict(&mut self, tt: TaskToken) -> Option<HeartbeatExecutorAction> {
|
|
223
|
+
if let Some(token) = self
|
|
224
|
+
.tt_to_state
|
|
225
|
+
.remove(&tt)
|
|
226
|
+
.and_then(|st| st.throttled_cancellation_token)
|
|
227
|
+
{
|
|
228
|
+
let _ = token.cancel();
|
|
229
|
+
};
|
|
230
|
+
None
|
|
231
|
+
}
|
|
126
232
|
}
|
|
127
233
|
|
|
128
234
|
impl ActivityHeartbeatManager {
|
|
129
235
|
/// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
|
|
130
236
|
/// which allows to send new heartbeats and initiate the shutdown.
|
|
131
237
|
pub fn new(sg: Arc<impl ServerGatewayApis + Send + Sync + 'static + ?Sized>) -> Self {
|
|
132
|
-
let (
|
|
238
|
+
let (heartbeat_stream_state, heartbeat_tx_source, cancellation_token) =
|
|
239
|
+
HeartbeatStreamState::new();
|
|
133
240
|
let (cancels_tx, cancels_rx) = unbounded_channel();
|
|
134
|
-
let
|
|
241
|
+
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
135
242
|
|
|
136
243
|
let join_handle = tokio::spawn(
|
|
137
244
|
// The stream of incoming heartbeats uses unfold to carry state across each item in the
|
|
138
245
|
// stream The closure checks if, for any given activity, we should heartbeat or not
|
|
139
246
|
// depending on its delay and when we last issued a heartbeat for it.
|
|
140
|
-
futures::stream::unfold(
|
|
141
|
-
let mut shutdown = shutdown_rx.clone();
|
|
247
|
+
futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
|
|
142
248
|
async move {
|
|
143
|
-
let hb =
|
|
144
|
-
|
|
145
|
-
biased;
|
|
146
|
-
|
|
147
|
-
_ = shutdown.changed() => return None,
|
|
148
|
-
|
|
149
|
-
hb = hb_states.incoming_hbs.recv() => {
|
|
150
|
-
if let Some(hb) = hb {
|
|
151
|
-
match hb {
|
|
152
|
-
HBAction::HB(hb) => break hb,
|
|
153
|
-
HBAction::Evict(tt) => {
|
|
154
|
-
hb_states.last_sent.remove(&tt);
|
|
155
|
-
continue
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
return None;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
};
|
|
249
|
+
let hb = tokio::select! {
|
|
250
|
+
biased;
|
|
164
251
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
e.insert(ActivityHbState {
|
|
168
|
-
delay: hb.delay,
|
|
169
|
-
last_sent: Instant::now(),
|
|
170
|
-
});
|
|
171
|
-
true
|
|
252
|
+
_ = hb_states.cancellation_token.cancelled() => {
|
|
253
|
+
return None
|
|
172
254
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let elapsed = o.last_sent.elapsed();
|
|
177
|
-
let do_rec = elapsed >= o.delay;
|
|
178
|
-
if do_rec {
|
|
179
|
-
o.last_sent = now;
|
|
180
|
-
o.delay = hb.delay;
|
|
181
|
-
}
|
|
182
|
-
do_rec
|
|
255
|
+
hb = hb_states.incoming_hbs.recv() => match hb {
|
|
256
|
+
None => return None,
|
|
257
|
+
Some(hb) => hb,
|
|
183
258
|
}
|
|
184
259
|
};
|
|
185
260
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
261
|
+
Some((
|
|
262
|
+
match hb {
|
|
263
|
+
HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
|
|
264
|
+
HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
|
|
265
|
+
HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
|
|
266
|
+
HeartbeatAction::Evict(tt) => hb_states.evict(tt),
|
|
267
|
+
},
|
|
268
|
+
hb_states,
|
|
269
|
+
))
|
|
192
270
|
}
|
|
193
271
|
})
|
|
194
|
-
.
|
|
272
|
+
.filter_map(|opt| async { opt })
|
|
273
|
+
.for_each_concurrent(None, move |action| {
|
|
274
|
+
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
195
275
|
let sg = sg.clone();
|
|
196
276
|
let cancels_tx = cancels_tx.clone();
|
|
197
277
|
async move {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
hb.task_token.clone(),
|
|
207
|
-
hb.details.clone().into_payloads(),
|
|
208
|
-
)
|
|
209
|
-
.await
|
|
210
|
-
{
|
|
211
|
-
Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
|
|
212
|
-
if cancel_requested {
|
|
213
|
-
cancels_tx
|
|
214
|
-
.send(PendingActivityCancel::new(
|
|
215
|
-
hb.task_token.clone(),
|
|
216
|
-
ActivityCancelReason::Cancelled,
|
|
217
|
-
))
|
|
218
|
-
.expect("Receive half of heartbeat cancels not blocked");
|
|
219
|
-
}
|
|
278
|
+
match action {
|
|
279
|
+
HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
|
|
280
|
+
tokio::select! {
|
|
281
|
+
_ = cancellation_token.cancelled() => (),
|
|
282
|
+
_ = tokio::time::sleep(duration) => {
|
|
283
|
+
let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
|
|
284
|
+
},
|
|
285
|
+
};
|
|
220
286
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
287
|
+
HeartbeatExecutorAction::Report(tt, details) => {
|
|
288
|
+
match sg
|
|
289
|
+
.record_activity_heartbeat(tt.clone(), details.into_payloads())
|
|
290
|
+
.await
|
|
291
|
+
{
|
|
292
|
+
Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
|
|
293
|
+
if cancel_requested {
|
|
294
|
+
cancels_tx
|
|
295
|
+
.send(PendingActivityCancel::new(
|
|
296
|
+
tt.clone(),
|
|
297
|
+
ActivityCancelReason::Cancelled,
|
|
298
|
+
))
|
|
299
|
+
.expect(
|
|
300
|
+
"Receive half of heartbeat cancels not blocked",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Send cancels for any activity that learns its workflow already finished
|
|
305
|
+
// (which is one thing not found implies - other reasons would seem equally
|
|
306
|
+
// valid).
|
|
307
|
+
Err(s) if s.code() == tonic::Code::NotFound => {
|
|
308
|
+
cancels_tx
|
|
309
|
+
.send(PendingActivityCancel::new(
|
|
310
|
+
tt.clone(),
|
|
311
|
+
ActivityCancelReason::NotFound,
|
|
312
|
+
))
|
|
313
|
+
.expect("Receive half of heartbeat cancels not blocked");
|
|
314
|
+
}
|
|
315
|
+
Err(e) => {
|
|
316
|
+
warn!("Error when recording heartbeat: {:?}", e);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
|
|
234
320
|
}
|
|
235
321
|
}
|
|
236
322
|
}
|
|
@@ -238,10 +324,10 @@ impl ActivityHeartbeatManager {
|
|
|
238
324
|
);
|
|
239
325
|
|
|
240
326
|
Self {
|
|
241
|
-
heartbeat_tx,
|
|
242
327
|
incoming_cancels: Mutex::new(cancels_rx),
|
|
243
|
-
shutting_down: shutdown_tx,
|
|
244
328
|
join_handle: Mutex::new(Some(join_handle)),
|
|
329
|
+
cancellation_token,
|
|
330
|
+
heartbeat_tx,
|
|
245
331
|
}
|
|
246
332
|
}
|
|
247
333
|
}
|
|
@@ -257,7 +343,7 @@ mod test {
|
|
|
257
343
|
};
|
|
258
344
|
use tokio::time::sleep;
|
|
259
345
|
|
|
260
|
-
/// Ensure that heartbeats that are sent with a small
|
|
346
|
+
/// Ensure that heartbeats that are sent with a small `throttle_interval` are aggregated and sent roughly once
|
|
261
347
|
/// every 1/2 of the heartbeat timeout.
|
|
262
348
|
#[tokio::test]
|
|
263
349
|
async fn process_heartbeats_and_shutdown() {
|
|
@@ -268,18 +354,38 @@ mod test {
|
|
|
268
354
|
.times(2);
|
|
269
355
|
let hm = ActivityHeartbeatManager::new(Arc::new(mock_gateway));
|
|
270
356
|
let fake_task_token = vec![1, 2, 3];
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
for i in
|
|
275
|
-
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(
|
|
276
|
-
sleep(Duration::from_millis(
|
|
357
|
+
// Send 2 heartbeat requests for 20ms apart.
|
|
358
|
+
// The first heartbeat should be sent right away, and
|
|
359
|
+
// the second should be throttled until 50ms have passed.
|
|
360
|
+
for i in 0_u8..2 {
|
|
361
|
+
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(50));
|
|
362
|
+
sleep(Duration::from_millis(20)).await;
|
|
277
363
|
}
|
|
364
|
+
// sleep again to let heartbeats be flushed
|
|
365
|
+
sleep(Duration::from_millis(20)).await;
|
|
278
366
|
hm.shutdown().await;
|
|
279
367
|
}
|
|
280
368
|
|
|
281
|
-
|
|
282
|
-
|
|
369
|
+
#[tokio::test]
|
|
370
|
+
async fn send_heartbeats_less_frequently_than_throttle_interval() {
|
|
371
|
+
let mut mock_gateway = MockServerGatewayApis::new();
|
|
372
|
+
mock_gateway
|
|
373
|
+
.expect_record_activity_heartbeat()
|
|
374
|
+
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
375
|
+
.times(3);
|
|
376
|
+
let hm = ActivityHeartbeatManager::new(Arc::new(mock_gateway));
|
|
377
|
+
let fake_task_token = vec![1, 2, 3];
|
|
378
|
+
// Heartbeats always get sent if recorded less frequently than the throttle intreval
|
|
379
|
+
for i in 0_u8..3 {
|
|
380
|
+
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(10));
|
|
381
|
+
sleep(Duration::from_millis(20)).await;
|
|
382
|
+
}
|
|
383
|
+
// sleep again to let heartbeats be flushed
|
|
384
|
+
hm.shutdown().await;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/// Ensure that heartbeat can be called from a tight loop without any throttle_interval, resulting in two
|
|
388
|
+
/// interactions with the server - one immediately and one after 500ms after the throttle_interval.
|
|
283
389
|
#[tokio::test]
|
|
284
390
|
async fn process_tight_loop_and_shutdown() {
|
|
285
391
|
let mut mock_gateway = MockServerGatewayApis::new();
|
|
@@ -290,15 +396,15 @@ mod test {
|
|
|
290
396
|
let hm = ActivityHeartbeatManager::new(Arc::new(mock_gateway));
|
|
291
397
|
let fake_task_token = vec![1, 2, 3];
|
|
292
398
|
// Send a whole bunch of heartbeats very fast. We should still only send one total.
|
|
293
|
-
for i in
|
|
294
|
-
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(
|
|
399
|
+
for i in 0_u8..50 {
|
|
400
|
+
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(2000));
|
|
401
|
+
// Let it propagate
|
|
402
|
+
sleep(Duration::from_millis(10)).await;
|
|
295
403
|
}
|
|
296
|
-
// Let it propagate
|
|
297
|
-
sleep(Duration::from_millis(50)).await;
|
|
298
404
|
hm.shutdown().await;
|
|
299
405
|
}
|
|
300
406
|
|
|
301
|
-
/// This test reports one heartbeat and waits for the
|
|
407
|
+
/// This test reports one heartbeat and waits for the throttle_interval to elapse before sending another
|
|
302
408
|
#[tokio::test]
|
|
303
409
|
async fn report_heartbeat_after_timeout() {
|
|
304
410
|
let mut mock_gateway = MockServerGatewayApis::new();
|
|
@@ -326,10 +432,12 @@ mod test {
|
|
|
326
432
|
let hm = ActivityHeartbeatManager::new(Arc::new(mock_gateway));
|
|
327
433
|
let fake_task_token = vec![1, 2, 3];
|
|
328
434
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
435
|
+
// Let it propagate
|
|
436
|
+
sleep(Duration::from_millis(10)).await;
|
|
329
437
|
hm.evict(fake_task_token.clone().into());
|
|
330
438
|
record_heartbeat(&hm, fake_task_token, 0, Duration::from_millis(100));
|
|
331
439
|
// Let it propagate
|
|
332
|
-
sleep(Duration::from_millis(
|
|
440
|
+
sleep(Duration::from_millis(10)).await;
|
|
333
441
|
// We know it works b/c otherwise we would have only called record 1 time w/o sleep
|
|
334
442
|
hm.shutdown().await;
|
|
335
443
|
}
|
|
@@ -356,7 +464,7 @@ mod test {
|
|
|
356
464
|
Duration::from_millis(1000),
|
|
357
465
|
) {
|
|
358
466
|
Ok(_) => {
|
|
359
|
-
unreachable!("heartbeat should not be recorded after the shutdown")
|
|
467
|
+
unreachable!("heartbeat should not be recorded after the shutdown");
|
|
360
468
|
}
|
|
361
469
|
Err(e) => {
|
|
362
470
|
matches!(e, ActivityHeartbeatError::ShuttingDown);
|
|
@@ -368,7 +476,7 @@ mod test {
|
|
|
368
476
|
hm: &ActivityHeartbeatManager,
|
|
369
477
|
task_token: Vec<u8>,
|
|
370
478
|
payload_data: u8,
|
|
371
|
-
|
|
479
|
+
throttle_interval: Duration,
|
|
372
480
|
) {
|
|
373
481
|
hm.record(
|
|
374
482
|
ActivityHeartbeat {
|
|
@@ -380,7 +488,7 @@ mod test {
|
|
|
380
488
|
}],
|
|
381
489
|
},
|
|
382
490
|
// Mimic the same delay we would apply in activity task manager
|
|
383
|
-
|
|
491
|
+
throttle_interval,
|
|
384
492
|
)
|
|
385
493
|
.expect("hearbeat recording should not fail");
|
|
386
494
|
}
|