@temporalio/core-bridge 0.16.0 → 0.17.1
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 +1 -0
- package/index.d.ts +14 -0
- package/index.node +0 -0
- 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.toml +1 -0
- package/sdk-core/fsm/rustfsm_procmacro/Cargo.toml +1 -1
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +8 -9
- package/sdk-core/fsm/rustfsm_trait/Cargo.toml +1 -1
- package/sdk-core/fsm/rustfsm_trait/src/lib.rs +1 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +43 -48
- package/sdk-core/src/core_tests/activity_tasks.rs +5 -5
- package/sdk-core/src/core_tests/mod.rs +2 -2
- package/sdk-core/src/core_tests/queries.rs +9 -2
- package/sdk-core/src/core_tests/workflow_tasks.rs +87 -8
- package/sdk-core/src/errors.rs +13 -13
- package/sdk-core/src/lib.rs +2 -2
- package/sdk-core/src/machines/activity_state_machine.rs +3 -3
- package/sdk-core/src/machines/child_workflow_state_machine.rs +6 -15
- 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 +16 -22
- package/sdk-core/src/machines/patch_state_machine.rs +8 -8
- package/sdk-core/src/machines/signal_external_state_machine.rs +2 -2
- package/sdk-core/src/machines/timer_state_machine.rs +4 -4
- package/sdk-core/src/machines/transition_coverage.rs +3 -3
- package/sdk-core/src/machines/workflow_machines.rs +26 -24
- package/sdk-core/src/pending_activations.rs +19 -20
- package/sdk-core/src/pollers/gateway.rs +3 -3
- package/sdk-core/src/pollers/poll_buffer.rs +2 -2
- package/sdk-core/src/pollers/retry.rs +4 -4
- package/sdk-core/src/prototype_rust_sdk/workflow_context.rs +3 -3
- package/sdk-core/src/prototype_rust_sdk/workflow_future.rs +4 -4
- package/sdk-core/src/prototype_rust_sdk.rs +3 -11
- 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 +8 -5
- package/sdk-core/src/test_help/history_builder.rs +12 -2
- package/sdk-core/src/test_help/history_info.rs +23 -3
- package/sdk-core/src/test_help/mod.rs +24 -40
- 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 +71 -52
- 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 +1 -1
- package/sdk-core/src/workflow/workflow_tasks/cache_manager.rs +13 -17
- package/sdk-core/src/workflow/workflow_tasks/concurrency_manager.rs +4 -8
- package/sdk-core/src/workflow/workflow_tasks/mod.rs +46 -53
- package/sdk-core/test_utils/src/lib.rs +2 -2
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +61 -1
- package/src/conversions.rs +17 -0
|
@@ -21,7 +21,7 @@ use crate::{
|
|
|
21
21
|
use crossbeam::queue::SegQueue;
|
|
22
22
|
use futures::FutureExt;
|
|
23
23
|
use parking_lot::Mutex;
|
|
24
|
-
use std::{fmt::Debug,
|
|
24
|
+
use std::{fmt::Debug, time::Instant};
|
|
25
25
|
use temporal_sdk_core_protos::coresdk::{
|
|
26
26
|
workflow_activation::{
|
|
27
27
|
create_evict_activation, create_query_activation, wf_activation_job, QueryWorkflow,
|
|
@@ -77,9 +77,9 @@ pub(crate) enum OutstandingActivation {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
impl OutstandingActivation {
|
|
80
|
-
fn has_eviction(
|
|
80
|
+
const fn has_eviction(self) -> bool {
|
|
81
81
|
matches!(
|
|
82
|
-
|
|
82
|
+
self,
|
|
83
83
|
OutstandingActivation::Normal {
|
|
84
84
|
contains_eviction: true
|
|
85
85
|
}
|
|
@@ -135,7 +135,7 @@ pub enum ActivationAction {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
macro_rules! machine_mut {
|
|
138
|
-
($myself:ident, $run_id:ident, $
|
|
138
|
+
($myself:ident, $run_id:ident, $clos:expr) => {{
|
|
139
139
|
$myself
|
|
140
140
|
.workflow_machines
|
|
141
141
|
.access($run_id, $clos)
|
|
@@ -143,7 +143,6 @@ macro_rules! machine_mut {
|
|
|
143
143
|
.map_err(|source| WorkflowUpdateError {
|
|
144
144
|
source,
|
|
145
145
|
run_id: $run_id.to_owned(),
|
|
146
|
-
task_token: Some($task_token.clone()),
|
|
147
146
|
})
|
|
148
147
|
}};
|
|
149
148
|
}
|
|
@@ -256,6 +255,7 @@ impl WorkflowTaskManager {
|
|
|
256
255
|
debug!(
|
|
257
256
|
task_token = %&work.task_token,
|
|
258
257
|
history_length = %work.history.events.len(),
|
|
258
|
+
attempt = %work.attempt,
|
|
259
259
|
"Applying new workflow task from server"
|
|
260
260
|
);
|
|
261
261
|
let task_start_time = Instant::now();
|
|
@@ -303,13 +303,13 @@ impl WorkflowTaskManager {
|
|
|
303
303
|
)
|
|
304
304
|
.expect("Workflow machines must exist, we just created/updated them");
|
|
305
305
|
|
|
306
|
-
if
|
|
306
|
+
if next_activation.jobs.is_empty() {
|
|
307
|
+
NewWfTaskOutcome::Autocomplete
|
|
308
|
+
} else {
|
|
307
309
|
if let Err(wme) = self.insert_outstanding_activation(&next_activation) {
|
|
308
310
|
return NewWfTaskOutcome::Evict(wme.into());
|
|
309
311
|
}
|
|
310
312
|
NewWfTaskOutcome::IssueActivation(next_activation)
|
|
311
|
-
} else {
|
|
312
|
-
NewWfTaskOutcome::Autocomplete
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
@@ -325,19 +325,20 @@ impl WorkflowTaskManager {
|
|
|
325
325
|
return Ok(None);
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
let task_token
|
|
329
|
-
entry.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
328
|
+
let (task_token, is_leg_query_task) =
|
|
329
|
+
if let Some(entry) = self.workflow_machines.get_task(run_id) {
|
|
330
|
+
(entry.info.task_token.clone(), entry.legacy_query.is_some())
|
|
331
|
+
} else {
|
|
332
|
+
if !self.activation_has_eviction(run_id) {
|
|
333
|
+
// Don't bother warning if this was an eviction, since it's normal to issue
|
|
334
|
+
// eviction activations without an associated workflow task in that case.
|
|
335
|
+
warn!(
|
|
336
|
+
run_id,
|
|
337
|
+
"Attempted to complete activation for run without associated workflow task"
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return Ok(None);
|
|
341
|
+
};
|
|
341
342
|
|
|
342
343
|
// If the only command in the activation is a legacy query response, that means we need
|
|
343
344
|
// to respond differently than a typical activation.
|
|
@@ -364,7 +365,6 @@ impl WorkflowTaskManager {
|
|
|
364
365
|
return Err(WorkflowUpdateError {
|
|
365
366
|
source: WFMachinesError::Fatal("Legacy query activation response included other commands, this is not allowed and constitutes an error in the lang SDK".to_string()),
|
|
366
367
|
run_id: run_id.to_string(),
|
|
367
|
-
task_token: Some(task_token)
|
|
368
368
|
});
|
|
369
369
|
}
|
|
370
370
|
query_responses.push(qr);
|
|
@@ -375,30 +375,32 @@ impl WorkflowTaskManager {
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
// Send commands from lang into the machines
|
|
378
|
-
machine_mut!(self, run_id,
|
|
378
|
+
machine_mut!(self, run_id, |wfm: &mut WorkflowManager| {
|
|
379
379
|
wfm.push_commands(commands).boxed()
|
|
380
380
|
})?;
|
|
381
381
|
// Check if the workflow run needs another activation and queue it up if there is one
|
|
382
382
|
// by pushing it into the pending activations list
|
|
383
|
-
let next_activation = machine_mut!(
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
task_token,
|
|
387
|
-
move |mgr: &mut WorkflowManager| mgr.get_next_activation().boxed()
|
|
388
|
-
)?;
|
|
383
|
+
let next_activation = machine_mut!(self, run_id, move |mgr: &mut WorkflowManager| mgr
|
|
384
|
+
.get_next_activation()
|
|
385
|
+
.boxed())?;
|
|
389
386
|
if !next_activation.jobs.is_empty() {
|
|
390
387
|
self.pending_activations.push(next_activation);
|
|
391
388
|
let _ = self.pending_activations_notifier.send(true);
|
|
392
389
|
}
|
|
393
390
|
// We want to fetch the outgoing commands only after any new activation has been queued,
|
|
394
391
|
// as doing so may have altered the outgoing commands.
|
|
395
|
-
let server_cmds =
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
392
|
+
let server_cmds = machine_mut!(self, run_id, |wfm: &mut WorkflowManager| {
|
|
393
|
+
async move { Ok(wfm.get_server_commands()) }.boxed()
|
|
394
|
+
})?;
|
|
395
|
+
let is_query_playback = is_leg_query_task && query_responses.is_empty();
|
|
399
396
|
// We only actually want to send commands back to the server if there are no more
|
|
400
|
-
// pending activations and we are caught up on replay.
|
|
401
|
-
if
|
|
397
|
+
// pending activations and we are caught up on replay. We don't want to complete a wft
|
|
398
|
+
// if we already saw the final event in the workflow, or if we are playing back for the
|
|
399
|
+
// express purpose of fulfilling a query
|
|
400
|
+
if !self.pending_activations.has_pending(run_id)
|
|
401
|
+
&& !server_cmds.replaying
|
|
402
|
+
&& !is_query_playback
|
|
403
|
+
{
|
|
402
404
|
Some(ServerCommandsWithWorkflowInfo {
|
|
403
405
|
task_token,
|
|
404
406
|
action: ActivationAction::WftComplete {
|
|
@@ -406,7 +408,9 @@ impl WorkflowTaskManager {
|
|
|
406
408
|
query_responses,
|
|
407
409
|
},
|
|
408
410
|
})
|
|
409
|
-
} else if
|
|
411
|
+
} else if query_responses.is_empty() {
|
|
412
|
+
None
|
|
413
|
+
} else {
|
|
410
414
|
Some(ServerCommandsWithWorkflowInfo {
|
|
411
415
|
task_token,
|
|
412
416
|
action: ActivationAction::WftComplete {
|
|
@@ -414,8 +418,6 @@ impl WorkflowTaskManager {
|
|
|
414
418
|
query_responses,
|
|
415
419
|
},
|
|
416
420
|
})
|
|
417
|
-
} else {
|
|
418
|
-
None
|
|
419
421
|
}
|
|
420
422
|
};
|
|
421
423
|
Ok(ret)
|
|
@@ -447,13 +449,9 @@ impl WorkflowTaskManager {
|
|
|
447
449
|
FailedActivationOutcome::ReportLegacyQueryFailure(tt)
|
|
448
450
|
} else {
|
|
449
451
|
// Blow up any cached data associated with the workflow
|
|
450
|
-
let should_report =
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
attempt <= 1
|
|
454
|
-
} else {
|
|
455
|
-
true
|
|
456
|
-
};
|
|
452
|
+
let should_report = self
|
|
453
|
+
.request_eviction(run_id, "Activation failed")
|
|
454
|
+
.map_or(true, |attempt| attempt <= 1);
|
|
457
455
|
if should_report {
|
|
458
456
|
FailedActivationOutcome::Report(tt)
|
|
459
457
|
} else {
|
|
@@ -511,11 +509,7 @@ impl WorkflowTaskManager {
|
|
|
511
509
|
|
|
512
510
|
Ok((wft_info, activation))
|
|
513
511
|
}
|
|
514
|
-
Err(source) => Err(WorkflowUpdateError {
|
|
515
|
-
source,
|
|
516
|
-
run_id,
|
|
517
|
-
task_token: Some(wft_info.task_token),
|
|
518
|
-
}),
|
|
512
|
+
Err(source) => Err(WorkflowUpdateError { source, run_id }),
|
|
519
513
|
}
|
|
520
514
|
}
|
|
521
515
|
|
|
@@ -541,11 +535,10 @@ impl WorkflowTaskManager {
|
|
|
541
535
|
if !just_evicted {
|
|
542
536
|
// Check if there was a legacy query which must be fulfilled, and if there is create
|
|
543
537
|
// a new pending activation for it.
|
|
544
|
-
if let Some(ref mut ot) = self
|
|
538
|
+
if let Some(ref mut ot) = &mut *self
|
|
545
539
|
.workflow_machines
|
|
546
540
|
.get_task_mut(run_id)
|
|
547
541
|
.expect("Machine must exist")
|
|
548
|
-
.deref_mut()
|
|
549
542
|
{
|
|
550
543
|
if let Some(query) = ot.legacy_query.take() {
|
|
551
544
|
let na = create_query_activation(run_id.to_string(), [query]);
|
|
@@ -623,7 +616,7 @@ impl WorkflowTaskManager {
|
|
|
623
616
|
fn activation_has_eviction(&self, run_id: &str) -> bool {
|
|
624
617
|
self.workflow_machines
|
|
625
618
|
.get_activation(run_id)
|
|
626
|
-
.map(
|
|
619
|
+
.map(OutstandingActivation::has_eviction)
|
|
627
620
|
.unwrap_or_default()
|
|
628
621
|
}
|
|
629
622
|
}
|
|
@@ -46,7 +46,7 @@ impl CoreWfStarter {
|
|
|
46
46
|
.unwrap(),
|
|
47
47
|
worker_config: WorkerConfigBuilder::default()
|
|
48
48
|
.task_queue(task_queue)
|
|
49
|
-
.max_cached_workflows(
|
|
49
|
+
.max_cached_workflows(1000_usize)
|
|
50
50
|
.build()
|
|
51
51
|
.unwrap(),
|
|
52
52
|
wft_timeout: None,
|
|
@@ -63,7 +63,7 @@ impl CoreWfStarter {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
pub async fn shutdown(&mut self) {
|
|
66
|
-
self.get_core().await.shutdown().await
|
|
66
|
+
self.get_core().await.shutdown().await;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
pub async fn get_core(&mut self) -> Arc<dyn Core> {
|
|
@@ -8,7 +8,7 @@ use temporal_sdk_core_protos::{
|
|
|
8
8
|
workflow_activation::{wf_activation_job, FireTimer, ResolveActivity, WfActivationJob},
|
|
9
9
|
workflow_commands::{ActivityCancellationType, RequestCancelActivity, StartTimer},
|
|
10
10
|
workflow_completion::WfActivationCompletion,
|
|
11
|
-
ActivityTaskCompletion, IntoCompletion,
|
|
11
|
+
ActivityHeartbeat, ActivityTaskCompletion, IntoCompletion,
|
|
12
12
|
},
|
|
13
13
|
temporal::api::{
|
|
14
14
|
common::v1::{ActivityType, Payloads},
|
|
@@ -647,3 +647,63 @@ async fn async_activity_completion_workflow() {
|
|
|
647
647
|
);
|
|
648
648
|
core.complete_execution(&task_q, &task.run_id).await;
|
|
649
649
|
}
|
|
650
|
+
|
|
651
|
+
#[tokio::test]
|
|
652
|
+
async fn activity_cancelled_after_heartbeat_times_out() {
|
|
653
|
+
let test_name = "activity_cancelled_after_heartbeat_times_out";
|
|
654
|
+
let (core, task_q) = init_core_and_create_wf(test_name).await;
|
|
655
|
+
let activity_id = "act-1";
|
|
656
|
+
let task = core.poll_workflow_activation(&task_q).await.unwrap();
|
|
657
|
+
// Complete workflow task and schedule activity
|
|
658
|
+
core.complete_workflow_activation(
|
|
659
|
+
schedule_activity_cmd(
|
|
660
|
+
0,
|
|
661
|
+
&task_q,
|
|
662
|
+
activity_id,
|
|
663
|
+
ActivityCancellationType::WaitCancellationCompleted,
|
|
664
|
+
Duration::from_secs(60),
|
|
665
|
+
Duration::from_secs(1),
|
|
666
|
+
)
|
|
667
|
+
.into_completion(task_q.clone(), task.run_id),
|
|
668
|
+
)
|
|
669
|
+
.await
|
|
670
|
+
.unwrap();
|
|
671
|
+
// Poll activity and verify that it's been scheduled with correct parameters
|
|
672
|
+
let task = core.poll_activity_task(&task_q).await.unwrap();
|
|
673
|
+
assert_matches!(
|
|
674
|
+
task.variant,
|
|
675
|
+
Some(act_task::Variant::Start(start_activity)) => {
|
|
676
|
+
assert_eq!(start_activity.activity_type, "test_activity".to_string())
|
|
677
|
+
}
|
|
678
|
+
);
|
|
679
|
+
// Delay the heartbeat
|
|
680
|
+
sleep(Duration::from_secs(2)).await;
|
|
681
|
+
core.record_activity_heartbeat(ActivityHeartbeat {
|
|
682
|
+
task_token: task.task_token.clone(),
|
|
683
|
+
task_queue: task_q.to_string(),
|
|
684
|
+
details: vec![],
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// Verify activity got cancelled
|
|
688
|
+
let cancel_task = core.poll_activity_task(&task_q).await.unwrap();
|
|
689
|
+
assert_eq!(cancel_task.task_token, task.task_token.clone());
|
|
690
|
+
assert_matches!(cancel_task.variant, Some(act_task::Variant::Cancel(_)));
|
|
691
|
+
|
|
692
|
+
// Complete activity with cancelled result
|
|
693
|
+
core.complete_activity_task(ActivityTaskCompletion {
|
|
694
|
+
task_token: task.task_token.clone(),
|
|
695
|
+
task_queue: task_q.to_string(),
|
|
696
|
+
result: Some(ActivityResult::cancel_from_details(None)),
|
|
697
|
+
})
|
|
698
|
+
.await
|
|
699
|
+
.unwrap();
|
|
700
|
+
|
|
701
|
+
// Verify shutdown completes
|
|
702
|
+
core.shutdown_worker(task_q.as_str()).await;
|
|
703
|
+
core.shutdown().await;
|
|
704
|
+
// Cleanup just in case
|
|
705
|
+
core.server_gateway()
|
|
706
|
+
.terminate_workflow_execution(test_name.to_string(), None)
|
|
707
|
+
.await
|
|
708
|
+
.unwrap();
|
|
709
|
+
}
|
package/src/conversions.rs
CHANGED
|
@@ -276,6 +276,21 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
|
|
|
276
276
|
) as u64);
|
|
277
277
|
let max_cached_workflows =
|
|
278
278
|
js_value_getter!(cx, self, "maxCachedWorkflows", JsNumber) as usize;
|
|
279
|
+
|
|
280
|
+
let max_heartbeat_throttle_interval = Duration::from_millis(js_value_getter!(
|
|
281
|
+
cx,
|
|
282
|
+
self,
|
|
283
|
+
"maxHeartbeatThrottleIntervalMs",
|
|
284
|
+
JsNumber
|
|
285
|
+
) as u64);
|
|
286
|
+
|
|
287
|
+
let default_heartbeat_throttle_interval = Duration::from_millis(js_value_getter!(
|
|
288
|
+
cx,
|
|
289
|
+
self,
|
|
290
|
+
"defaultHeartbeatThrottleIntervalMs",
|
|
291
|
+
JsNumber
|
|
292
|
+
) as u64);
|
|
293
|
+
|
|
279
294
|
Ok(WorkerConfig {
|
|
280
295
|
no_remote_activities: false, // TODO: make this configurable once Core implements local activities
|
|
281
296
|
max_concurrent_at_polls,
|
|
@@ -286,6 +301,8 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
|
|
|
286
301
|
nonsticky_to_sticky_poll_ratio,
|
|
287
302
|
sticky_queue_schedule_to_start_timeout,
|
|
288
303
|
task_queue,
|
|
304
|
+
max_heartbeat_throttle_interval,
|
|
305
|
+
default_heartbeat_throttle_interval,
|
|
289
306
|
})
|
|
290
307
|
}
|
|
291
308
|
}
|