@temporalio/core-bridge 1.0.0 → 1.0.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/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/client/src/lib.rs +52 -2
- package/sdk-core/client/src/retry.rs +35 -12
- package/sdk-core/core/src/core_tests/activity_tasks.rs +70 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +179 -9
- package/sdk-core/core/src/core_tests/workers.rs +33 -1
- package/sdk-core/core/src/lib.rs +13 -4
- package/sdk-core/core/src/protosext/mod.rs +44 -7
- package/sdk-core/core/src/test_help/mod.rs +19 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +2 -2
- package/sdk-core/core/src/worker/client.rs +9 -1
- package/sdk-core/core/src/worker/mod.rs +11 -4
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +5 -6
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +1 -1
- package/sdk-core/core/src/worker/workflow/managed_run.rs +27 -8
- package/sdk-core/core/src/worker/workflow/mod.rs +44 -17
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +14 -12
- package/sdk-core/sdk/src/lib.rs +36 -9
- package/sdk-core/sdk-core-protos/src/history_builder.rs +1 -1
- package/sdk-core/test-utils/src/lib.rs +22 -3
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +37 -1
|
@@ -73,6 +73,8 @@ pub enum ResponseType {
|
|
|
73
73
|
OneTask(usize),
|
|
74
74
|
/// Waits until the future resolves before responding as `ToTaskNum` with the provided number
|
|
75
75
|
UntilResolved(BoxFuture<'static, ()>, usize),
|
|
76
|
+
/// Waits until the future resolves before responding with the provided response
|
|
77
|
+
UntilResolvedRaw(BoxFuture<'static, ()>, PollWorkflowTaskQueueResponse),
|
|
76
78
|
AllHistory,
|
|
77
79
|
Raw(PollWorkflowTaskQueueResponse),
|
|
78
80
|
}
|
|
@@ -81,6 +83,7 @@ pub enum HashableResponseType {
|
|
|
81
83
|
ToTaskNum(usize),
|
|
82
84
|
OneTask(usize),
|
|
83
85
|
UntilResolved(usize),
|
|
86
|
+
UntilResolvedRaw(TaskToken),
|
|
84
87
|
AllHistory,
|
|
85
88
|
Raw(TaskToken),
|
|
86
89
|
}
|
|
@@ -92,6 +95,9 @@ impl ResponseType {
|
|
|
92
95
|
ResponseType::AllHistory => HashableResponseType::AllHistory,
|
|
93
96
|
ResponseType::Raw(r) => HashableResponseType::Raw(r.task_token.clone().into()),
|
|
94
97
|
ResponseType::UntilResolved(_, x) => HashableResponseType::UntilResolved(*x),
|
|
98
|
+
ResponseType::UntilResolvedRaw(_, r) => {
|
|
99
|
+
HashableResponseType::UntilResolvedRaw(r.task_token.clone().into())
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
}
|
|
@@ -618,7 +624,11 @@ impl<T> From<T> for QueueResponse<T> {
|
|
|
618
624
|
}
|
|
619
625
|
impl From<QueueResponse<PollWorkflowTaskQueueResponse>> for ResponseType {
|
|
620
626
|
fn from(qr: QueueResponse<PollWorkflowTaskQueueResponse>) -> Self {
|
|
621
|
-
|
|
627
|
+
if let Some(du) = qr.delay_until {
|
|
628
|
+
ResponseType::UntilResolvedRaw(du, qr.resp)
|
|
629
|
+
} else {
|
|
630
|
+
ResponseType::Raw(qr.resp)
|
|
631
|
+
}
|
|
622
632
|
}
|
|
623
633
|
}
|
|
624
634
|
impl<T> Deref for QueueResponse<T> {
|
|
@@ -636,13 +646,13 @@ impl<T> DerefMut for QueueResponse<T> {
|
|
|
636
646
|
|
|
637
647
|
pub fn hist_to_poll_resp(
|
|
638
648
|
t: &TestHistoryBuilder,
|
|
639
|
-
wf_id: String
|
|
649
|
+
wf_id: impl Into<String>,
|
|
640
650
|
response_type: ResponseType,
|
|
641
651
|
task_queue: impl Into<String>,
|
|
642
652
|
) -> QueueResponse<PollWorkflowTaskQueueResponse> {
|
|
643
653
|
let run_id = t.get_orig_run_id();
|
|
644
654
|
let wf = WorkflowExecution {
|
|
645
|
-
workflow_id: wf_id,
|
|
655
|
+
workflow_id: wf_id.into(),
|
|
646
656
|
run_id: run_id.to_string(),
|
|
647
657
|
};
|
|
648
658
|
let mut delay_until = None;
|
|
@@ -660,6 +670,12 @@ pub fn hist_to_poll_resp(
|
|
|
660
670
|
delay_until = Some(fut);
|
|
661
671
|
t.get_history_info(tn).unwrap()
|
|
662
672
|
}
|
|
673
|
+
ResponseType::UntilResolvedRaw(fut, r) => {
|
|
674
|
+
return QueueResponse {
|
|
675
|
+
resp: r,
|
|
676
|
+
delay_until: Some(fut),
|
|
677
|
+
}
|
|
678
|
+
}
|
|
663
679
|
};
|
|
664
680
|
let mut resp = hist_info.as_poll_wft_response(task_queue);
|
|
665
681
|
resp.workflow_execution = Some(wf);
|
|
@@ -131,7 +131,7 @@ impl ActivityHeartbeatManager {
|
|
|
131
131
|
/// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all in-flight
|
|
132
132
|
/// heartbeat requests to be flushed to the server.
|
|
133
133
|
pub(super) async fn shutdown(&self) {
|
|
134
|
-
|
|
134
|
+
self.shutdown_token.cancel();
|
|
135
135
|
let mut handle = self.join_handle.lock().await;
|
|
136
136
|
if let Some(h) = handle.take() {
|
|
137
137
|
let handle_r = h.await;
|
|
@@ -282,7 +282,7 @@ impl HeartbeatStreamState {
|
|
|
282
282
|
) -> Option<HeartbeatExecutorAction> {
|
|
283
283
|
if let Some(state) = self.tt_to_state.remove(&tt) {
|
|
284
284
|
if let Some(cancel_tok) = state.throttled_cancellation_token {
|
|
285
|
-
|
|
285
|
+
cancel_tok.cancel();
|
|
286
286
|
}
|
|
287
287
|
if let Some(last_deets) = state.last_recorded_details {
|
|
288
288
|
self.tt_needs_flush.insert(tt.clone(), on_complete);
|
|
@@ -6,7 +6,7 @@ use std::{
|
|
|
6
6
|
borrow::Borrow,
|
|
7
7
|
ops::{Deref, DerefMut},
|
|
8
8
|
};
|
|
9
|
-
use temporal_client::{WorkflowClientTrait, WorkflowTaskCompletion};
|
|
9
|
+
use temporal_client::{WorkflowClientTrait, WorkflowTaskCompletion, RETRYABLE_ERROR_CODES};
|
|
10
10
|
use temporal_sdk_core_protos::{
|
|
11
11
|
coresdk::workflow_commands::QueryResult,
|
|
12
12
|
temporal::api::{
|
|
@@ -15,9 +15,17 @@ use temporal_sdk_core_protos::{
|
|
|
15
15
|
},
|
|
16
16
|
TaskToken,
|
|
17
17
|
};
|
|
18
|
+
use tonic::Code;
|
|
18
19
|
|
|
19
20
|
type Result<T, E = tonic::Status> = std::result::Result<T, E>;
|
|
20
21
|
|
|
22
|
+
/// Returns true if the network error should not be reported to lang. This can happen if we've
|
|
23
|
+
/// exceeded the max number of retries, and we prefer to just warn rather than blowing up lang.
|
|
24
|
+
pub(crate) fn should_swallow_net_error(err: &tonic::Status) -> bool {
|
|
25
|
+
RETRYABLE_ERROR_CODES.contains(&err.code())
|
|
26
|
+
|| matches!(err.code(), Code::Cancelled | Code::DeadlineExceeded)
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
/// Contains everything a worker needs to interact with the server
|
|
22
30
|
pub(crate) struct WorkerClientBag {
|
|
23
31
|
client: Box<dyn WorkerClient>,
|
|
@@ -18,14 +18,14 @@ use crate::{
|
|
|
18
18
|
new_activity_task_buffer, new_workflow_task_buffer, BoxedActPoller, Poller,
|
|
19
19
|
WorkflowTaskPoller,
|
|
20
20
|
},
|
|
21
|
-
protosext::ValidPollWFTQResponse,
|
|
21
|
+
protosext::{validate_activity_completion, ValidPollWFTQResponse},
|
|
22
22
|
telemetry::metrics::{
|
|
23
23
|
activity_poller, local_activity_worker_type, workflow_poller, workflow_sticky_poller,
|
|
24
24
|
MetricsContext,
|
|
25
25
|
},
|
|
26
26
|
worker::{
|
|
27
27
|
activities::{DispatchOrTimeoutLA, LACompleteAction, LocalActivityManager},
|
|
28
|
-
client::WorkerClientBag,
|
|
28
|
+
client::{should_swallow_net_error, WorkerClientBag},
|
|
29
29
|
workflow::{LocalResolution, Workflows},
|
|
30
30
|
},
|
|
31
31
|
ActivityHeartbeat, CompleteActivityError, PollActivityError, PollWfError, WorkerTrait,
|
|
@@ -398,6 +398,7 @@ impl Worker {
|
|
|
398
398
|
task_token: TaskToken,
|
|
399
399
|
status: activity_execution_result::Status,
|
|
400
400
|
) -> Result<(), CompleteActivityError> {
|
|
401
|
+
validate_activity_completion(&status)?;
|
|
401
402
|
if task_token.is_local_activity_task() {
|
|
402
403
|
let as_la_res: LocalActivityExecutionResult = status.try_into()?;
|
|
403
404
|
match self.local_act_mgr.complete(&task_token, &as_la_res) {
|
|
@@ -408,7 +409,7 @@ impl Worker {
|
|
|
408
409
|
// no other situations where core generates "internal" commands so it is much
|
|
409
410
|
// simpler for lang to reply with the timer / next LA command than to do it
|
|
410
411
|
// internally. Plus, this backoff hack we'd like to eliminate eventually.
|
|
411
|
-
self.complete_local_act(as_la_res, info, Some(backoff))
|
|
412
|
+
self.complete_local_act(as_la_res, info, Some(backoff));
|
|
412
413
|
}
|
|
413
414
|
LACompleteAction::WillBeRetried => {
|
|
414
415
|
// Nothing to do here
|
|
@@ -421,7 +422,13 @@ impl Worker {
|
|
|
421
422
|
}
|
|
422
423
|
|
|
423
424
|
if let Some(atm) = &self.at_task_mgr {
|
|
424
|
-
atm.complete(task_token, status, &**self.wf_client).await
|
|
425
|
+
match atm.complete(task_token, status, &**self.wf_client).await {
|
|
426
|
+
Err(CompleteActivityError::TonicError(e)) if should_swallow_net_error(&e) => {
|
|
427
|
+
warn!(error=?e, "Network error while completing activity");
|
|
428
|
+
Ok(())
|
|
429
|
+
}
|
|
430
|
+
o => o,
|
|
431
|
+
}
|
|
425
432
|
} else {
|
|
426
433
|
error!(
|
|
427
434
|
"Tried to complete activity {} on a worker that does not have an activity manager",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
use crate::worker::workflow::{WFCommand, WorkflowStartedInfo};
|
|
2
|
-
use std::collections::VecDeque;
|
|
3
2
|
use temporal_sdk_core_protos::{
|
|
4
3
|
coresdk::workflow_activation::{
|
|
5
4
|
start_workflow_from_attribs, workflow_activation_job, CancelWorkflow, SignalWorkflow,
|
|
@@ -15,7 +14,7 @@ pub struct DrivenWorkflow {
|
|
|
15
14
|
started_attrs: Option<WorkflowStartedInfo>,
|
|
16
15
|
fetcher: Box<dyn WorkflowFetcher>,
|
|
17
16
|
/// Outgoing activation jobs that need to be sent to the lang sdk
|
|
18
|
-
outgoing_wf_activation_jobs:
|
|
17
|
+
outgoing_wf_activation_jobs: Vec<workflow_activation_job::Variant>,
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
impl<WF> From<Box<WF>> for DrivenWorkflow
|
|
@@ -58,12 +57,12 @@ impl DrivenWorkflow {
|
|
|
58
57
|
|
|
59
58
|
/// Enqueue a new job to be sent to the driven workflow
|
|
60
59
|
pub fn send_job(&mut self, job: workflow_activation_job::Variant) {
|
|
61
|
-
self.outgoing_wf_activation_jobs.
|
|
60
|
+
self.outgoing_wf_activation_jobs.push(job);
|
|
62
61
|
}
|
|
63
62
|
|
|
64
|
-
///
|
|
65
|
-
pub fn
|
|
66
|
-
|
|
63
|
+
/// Observe pending jobs
|
|
64
|
+
pub fn peek_pending_jobs(&self) -> &[workflow_activation_job::Variant] {
|
|
65
|
+
self.outgoing_wf_activation_jobs.as_slice()
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
/// Drain all pending jobs, so that they may be sent to the driven workflow
|
|
@@ -111,6 +111,7 @@ impl ManagedRun {
|
|
|
111
111
|
.fold((self, heartbeat_tx), |(mut me, heartbeat_tx), action| {
|
|
112
112
|
let span = action.trace_span;
|
|
113
113
|
let action = action.action;
|
|
114
|
+
let mut no_wft = false;
|
|
114
115
|
async move {
|
|
115
116
|
let res = match action {
|
|
116
117
|
RunActions::NewIncomingWFT(wft) => me
|
|
@@ -124,10 +125,15 @@ impl ManagedRun {
|
|
|
124
125
|
RunActions::CheckMoreWork {
|
|
125
126
|
want_to_evict,
|
|
126
127
|
has_pending_queries,
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
has_wft,
|
|
129
|
+
} => {
|
|
130
|
+
if !has_wft {
|
|
131
|
+
no_wft = true;
|
|
132
|
+
}
|
|
133
|
+
me.check_more_work(want_to_evict, has_pending_queries, has_wft)
|
|
134
|
+
.await
|
|
135
|
+
.map(RunActionOutcome::AfterCheckWork)
|
|
136
|
+
}
|
|
131
137
|
RunActions::LocalResolution(r) => me
|
|
132
138
|
.local_resolution(r)
|
|
133
139
|
.await
|
|
@@ -145,7 +151,7 @@ impl ManagedRun {
|
|
|
145
151
|
};
|
|
146
152
|
match res {
|
|
147
153
|
Ok(outcome) => {
|
|
148
|
-
me.send_update_response(outcome);
|
|
154
|
+
me.send_update_response(outcome, no_wft);
|
|
149
155
|
}
|
|
150
156
|
Err(e) => {
|
|
151
157
|
error!(error=?e, "Error in run machines");
|
|
@@ -308,7 +314,12 @@ impl ManagedRun {
|
|
|
308
314
|
&mut self,
|
|
309
315
|
want_to_evict: Option<RequestEvictMsg>,
|
|
310
316
|
has_pending_queries: bool,
|
|
317
|
+
has_wft: bool,
|
|
311
318
|
) -> Result<Option<ActivationOrAuto>, RunUpdateErr> {
|
|
319
|
+
if !has_wft {
|
|
320
|
+
// It doesn't make sense to do work unless we have a WFT
|
|
321
|
+
return Ok(None);
|
|
322
|
+
}
|
|
312
323
|
if self.wfm.machines.has_pending_jobs() && !self.am_broken {
|
|
313
324
|
Ok(Some(ActivationOrAuto::LangActivation(
|
|
314
325
|
self.wfm.get_next_activation().await?,
|
|
@@ -427,7 +438,7 @@ impl ManagedRun {
|
|
|
427
438
|
false
|
|
428
439
|
}
|
|
429
440
|
|
|
430
|
-
fn send_update_response(&self, outcome: RunActionOutcome) {
|
|
441
|
+
fn send_update_response(&self, outcome: RunActionOutcome, no_wft: bool) {
|
|
431
442
|
let mut in_response_to_wft = false;
|
|
432
443
|
let (outgoing_activation, fulfillable_complete) = match outcome {
|
|
433
444
|
RunActionOutcome::AfterNewWFT(a) => {
|
|
@@ -439,7 +450,15 @@ impl ManagedRun {
|
|
|
439
450
|
RunActionOutcome::AfterCompletion(f) => (None, f),
|
|
440
451
|
RunActionOutcome::AfterHeartbeatTimeout(a) => (a, None),
|
|
441
452
|
};
|
|
442
|
-
|
|
453
|
+
let mut more_pending_work = self.wfm.machines.has_pending_jobs();
|
|
454
|
+
// We don't want to consider there to be more local-only work to be done if there is no
|
|
455
|
+
// workflow task associated with the run right now. This can happen if, ex, we complete
|
|
456
|
+
// a local activity while waiting for server to send us the next WFT. Activating lang would
|
|
457
|
+
// be harmful at this stage, as there might be work returned in that next WFT which should
|
|
458
|
+
// be part of the next activation.
|
|
459
|
+
if no_wft {
|
|
460
|
+
more_pending_work = false;
|
|
461
|
+
}
|
|
443
462
|
self.update_tx
|
|
444
463
|
.send(RunUpdateResponse {
|
|
445
464
|
kind: RunUpdateResponseKind::Good(GoodRunUpdate {
|
|
@@ -447,7 +466,7 @@ impl ManagedRun {
|
|
|
447
466
|
outgoing_activation,
|
|
448
467
|
fulfillable_complete,
|
|
449
468
|
have_seen_terminal_event: self.wfm.machines.have_seen_terminal_event,
|
|
450
|
-
more_pending_work
|
|
469
|
+
more_pending_work,
|
|
451
470
|
most_recently_processed_event_number: self.wfm.machines.last_processed_event
|
|
452
471
|
as usize,
|
|
453
472
|
in_response_to_wft,
|
|
@@ -24,6 +24,7 @@ use crate::{
|
|
|
24
24
|
telemetry::VecDisplayer,
|
|
25
25
|
worker::{
|
|
26
26
|
activities::{ActivitiesFromWFTsHandle, PermittedTqResp},
|
|
27
|
+
client::should_swallow_net_error,
|
|
27
28
|
workflow::{
|
|
28
29
|
managed_run::{ManagedRun, WorkflowManager},
|
|
29
30
|
wft_poller::validate_wft,
|
|
@@ -234,7 +235,7 @@ impl Workflows {
|
|
|
234
235
|
let reserved_act_permits =
|
|
235
236
|
self.reserve_activity_slots_for_outgoing_commands(commands.as_mut_slice());
|
|
236
237
|
debug!(commands=%commands.display(), query_responses=%query_responses.display(),
|
|
237
|
-
"Sending responses to server");
|
|
238
|
+
force_new_wft, "Sending responses to server");
|
|
238
239
|
let mut completion = WorkflowTaskCompletion {
|
|
239
240
|
task_token,
|
|
240
241
|
commands,
|
|
@@ -362,20 +363,26 @@ impl Workflows {
|
|
|
362
363
|
let mut should_evict = None;
|
|
363
364
|
let res = match completer().await {
|
|
364
365
|
Err(err) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
366
|
+
if should_swallow_net_error(&err) {
|
|
367
|
+
warn!(error= %err, "Network error while completing workflow activation");
|
|
368
|
+
should_evict = Some(EvictionReason::Fatal);
|
|
369
|
+
Ok(())
|
|
370
|
+
} else {
|
|
371
|
+
match err.code() {
|
|
372
|
+
// Silence unhandled command errors since the lang SDK cannot do anything
|
|
373
|
+
// about them besides poll again, which it will do anyway.
|
|
374
|
+
tonic::Code::InvalidArgument if err.message() == "UnhandledCommand" => {
|
|
375
|
+
debug!(error = %err, run_id, "Unhandled command response when completing");
|
|
376
|
+
should_evict = Some(EvictionReason::UnhandledCommand);
|
|
377
|
+
Ok(())
|
|
378
|
+
}
|
|
379
|
+
tonic::Code::NotFound => {
|
|
380
|
+
warn!(error = %err, run_id, "Task not found when completing");
|
|
381
|
+
should_evict = Some(EvictionReason::TaskNotFound);
|
|
382
|
+
Ok(())
|
|
383
|
+
}
|
|
384
|
+
_ => Err(err),
|
|
377
385
|
}
|
|
378
|
-
_ => Err(err),
|
|
379
386
|
}
|
|
380
387
|
}
|
|
381
388
|
_ => Ok(()),
|
|
@@ -486,8 +493,12 @@ impl Workflows {
|
|
|
486
493
|
) -> Result<(), tonic::Status> {
|
|
487
494
|
match self.client.respond_legacy_query(tt, res).await {
|
|
488
495
|
Ok(_) => Ok(()),
|
|
496
|
+
Err(e) if should_swallow_net_error(&e) => {
|
|
497
|
+
warn!(error= %e, "Network error while responding to legacy query");
|
|
498
|
+
Ok(())
|
|
499
|
+
}
|
|
489
500
|
Err(e) if e.code() == tonic::Code::NotFound => {
|
|
490
|
-
warn!(error=?e,"Query not found when attempting to respond to it");
|
|
501
|
+
warn!(error=?e, "Query not found when attempting to respond to it");
|
|
491
502
|
Ok(())
|
|
492
503
|
}
|
|
493
504
|
Err(e) => Err(e),
|
|
@@ -497,7 +508,20 @@ impl Workflows {
|
|
|
497
508
|
|
|
498
509
|
/// Manages access to a specific workflow run, and contains various bookkeeping information that the
|
|
499
510
|
/// [WFStream] may need to access quickly.
|
|
500
|
-
#[derive(
|
|
511
|
+
#[derive(derive_more::DebugCustom)]
|
|
512
|
+
#[debug(
|
|
513
|
+
fmt = "ManagedRunHandle {{ wft: {:?}, activation: {:?}, buffered_resp: {:?} \
|
|
514
|
+
have_seen_terminal_event: {}, most_recently_processed_event: {}, more_pending_work: {}, \
|
|
515
|
+
trying_to_evict: {}, last_action_acked: {} }}",
|
|
516
|
+
wft,
|
|
517
|
+
activation,
|
|
518
|
+
buffered_resp,
|
|
519
|
+
have_seen_terminal_event,
|
|
520
|
+
most_recently_processed_event_number,
|
|
521
|
+
more_pending_work,
|
|
522
|
+
"trying_to_evict.is_some()",
|
|
523
|
+
last_action_acked
|
|
524
|
+
)]
|
|
501
525
|
struct ManagedRunHandle {
|
|
502
526
|
/// If set, the WFT this run is currently/will be processing.
|
|
503
527
|
wft: Option<OutstandingTask>,
|
|
@@ -567,6 +591,7 @@ impl ManagedRunHandle {
|
|
|
567
591
|
.as_ref()
|
|
568
592
|
.map(|wft| !wft.pending_queries.is_empty())
|
|
569
593
|
.unwrap_or_default(),
|
|
594
|
+
has_wft: self.wft.is_some(),
|
|
570
595
|
});
|
|
571
596
|
}
|
|
572
597
|
}
|
|
@@ -677,7 +702,8 @@ impl ActivationOrAuto {
|
|
|
677
702
|
}
|
|
678
703
|
}
|
|
679
704
|
|
|
680
|
-
#[derive(
|
|
705
|
+
#[derive(derive_more::DebugCustom)]
|
|
706
|
+
#[debug(fmt = "PermittedWft {{ {:?} }}", wft)]
|
|
681
707
|
pub(crate) struct PermittedWFT {
|
|
682
708
|
wft: ValidPollWFTQResponse,
|
|
683
709
|
permit: OwnedMeteredSemPermit,
|
|
@@ -924,6 +950,7 @@ enum RunActions {
|
|
|
924
950
|
CheckMoreWork {
|
|
925
951
|
want_to_evict: Option<RequestEvictMsg>,
|
|
926
952
|
has_pending_queries: bool,
|
|
953
|
+
has_wft: bool,
|
|
927
954
|
},
|
|
928
955
|
LocalResolution(LocalResolution),
|
|
929
956
|
HeartbeatTimeout,
|
|
@@ -45,7 +45,7 @@ pub(crate) struct WFStream {
|
|
|
45
45
|
metrics: MetricsContext,
|
|
46
46
|
}
|
|
47
47
|
/// All possible inputs to the [WFStream]
|
|
48
|
-
#[derive(derive_more::From)]
|
|
48
|
+
#[derive(derive_more::From, Debug)]
|
|
49
49
|
enum WFStreamInput {
|
|
50
50
|
NewWft(PermittedWFT),
|
|
51
51
|
Local(LocalInput),
|
|
@@ -64,6 +64,8 @@ impl From<RunUpdateResponse> for WFStreamInput {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
/// A non-poller-received input to the [WFStream]
|
|
67
|
+
#[derive(derive_more::DebugCustom)]
|
|
68
|
+
#[debug(fmt = "LocalInput {{ {:?} }}", input)]
|
|
67
69
|
pub(super) struct LocalInput {
|
|
68
70
|
pub input: LocalInputs,
|
|
69
71
|
pub span: Span,
|
|
@@ -167,7 +169,7 @@ impl WFStream {
|
|
|
167
169
|
};
|
|
168
170
|
all_inputs
|
|
169
171
|
.map(move |action| {
|
|
170
|
-
let span = span!(Level::DEBUG, "new_stream_input");
|
|
172
|
+
let span = span!(Level::DEBUG, "new_stream_input", action=?action);
|
|
171
173
|
let _span_g = span.enter();
|
|
172
174
|
|
|
173
175
|
let maybe_activation = match action {
|
|
@@ -209,7 +211,7 @@ impl WFStream {
|
|
|
209
211
|
}
|
|
210
212
|
}
|
|
211
213
|
WFStreamInput::PollerDead => {
|
|
212
|
-
|
|
214
|
+
debug!("WFT poller died, shutting down");
|
|
213
215
|
state.shutdown_token.cancel();
|
|
214
216
|
None
|
|
215
217
|
}
|
|
@@ -259,17 +261,15 @@ impl WFStream {
|
|
|
259
261
|
debug!(resp=%resp, "Processing run update response from machines");
|
|
260
262
|
match resp {
|
|
261
263
|
RunUpdateResponseKind::Good(mut resp) => {
|
|
262
|
-
if let Some(r) = self.runs.get_mut(&resp.run_id) {
|
|
263
|
-
r.have_seen_terminal_event = resp.have_seen_terminal_event;
|
|
264
|
-
r.more_pending_work = resp.more_pending_work;
|
|
265
|
-
r.last_action_acked = true;
|
|
266
|
-
r.most_recently_processed_event_number =
|
|
267
|
-
resp.most_recently_processed_event_number;
|
|
268
|
-
}
|
|
269
264
|
let run_handle = self
|
|
270
265
|
.runs
|
|
271
266
|
.get_mut(&resp.run_id)
|
|
272
267
|
.expect("Workflow must exist, it just sent us an update response");
|
|
268
|
+
run_handle.have_seen_terminal_event = resp.have_seen_terminal_event;
|
|
269
|
+
run_handle.more_pending_work = resp.more_pending_work;
|
|
270
|
+
run_handle.last_action_acked = true;
|
|
271
|
+
run_handle.most_recently_processed_event_number =
|
|
272
|
+
resp.most_recently_processed_event_number;
|
|
273
273
|
|
|
274
274
|
let r = match resp.outgoing_activation {
|
|
275
275
|
Some(ActivationOrAuto::LangActivation(mut activation)) => {
|
|
@@ -304,18 +304,20 @@ impl WFStream {
|
|
|
304
304
|
None => {
|
|
305
305
|
// If the response indicates there is no activation to send yet but there
|
|
306
306
|
// is more pending work, we should check again.
|
|
307
|
-
if
|
|
307
|
+
if run_handle.more_pending_work {
|
|
308
308
|
run_handle.check_more_activations();
|
|
309
309
|
None
|
|
310
310
|
} else if let Some(reason) = run_handle.trying_to_evict.as_ref() {
|
|
311
311
|
// If a run update came back and had nothing to do, but we're trying to
|
|
312
312
|
// evict, just do that now as long as there's no other outstanding work.
|
|
313
313
|
if run_handle.activation.is_none() && !run_handle.more_pending_work {
|
|
314
|
-
let evict_act = create_evict_activation(
|
|
314
|
+
let mut evict_act = create_evict_activation(
|
|
315
315
|
resp.run_id,
|
|
316
316
|
reason.message.clone(),
|
|
317
317
|
reason.reason,
|
|
318
318
|
);
|
|
319
|
+
evict_act.history_length =
|
|
320
|
+
run_handle.most_recently_processed_event_number as u32;
|
|
319
321
|
Some(ActivationOrAuto::LangActivation(evict_act))
|
|
320
322
|
} else {
|
|
321
323
|
None
|
package/sdk-core/sdk/src/lib.rs
CHANGED
|
@@ -205,10 +205,10 @@ impl Worker {
|
|
|
205
205
|
|
|
206
206
|
/// Register an Activity function to invoke when the Worker is asked to run an activity of
|
|
207
207
|
/// `activity_type`
|
|
208
|
-
pub fn register_activity<A, R>(
|
|
208
|
+
pub fn register_activity<A, R, O>(
|
|
209
209
|
&mut self,
|
|
210
210
|
activity_type: impl Into<String>,
|
|
211
|
-
act_function: impl IntoActivityFunc<A, R>,
|
|
211
|
+
act_function: impl IntoActivityFunc<A, R, O>,
|
|
212
212
|
) {
|
|
213
213
|
self.activity_half.activity_fns.insert(
|
|
214
214
|
activity_type.into(),
|
|
@@ -485,7 +485,10 @@ impl ActivityHalf {
|
|
|
485
485
|
tokio::spawn(async move {
|
|
486
486
|
let output = (act_fn.act_func)(ctx, arg).await;
|
|
487
487
|
let result = match output {
|
|
488
|
-
Ok(
|
|
488
|
+
Ok(ActExitValue::Normal(p)) => ActivityExecutionResult::ok(p),
|
|
489
|
+
Ok(ActExitValue::WillCompleteAsync) => {
|
|
490
|
+
ActivityExecutionResult::will_complete_async()
|
|
491
|
+
}
|
|
489
492
|
Err(err) => match err.downcast::<ActivityCancelledError>() {
|
|
490
493
|
Ok(ce) => ActivityExecutionResult::cancel_from_details(ce.details),
|
|
491
494
|
Err(other_err) => ActivityExecutionResult::fail(other_err.into()),
|
|
@@ -497,7 +500,7 @@ impl ActivityHalf {
|
|
|
497
500
|
result: Some(result),
|
|
498
501
|
})
|
|
499
502
|
.await?;
|
|
500
|
-
|
|
503
|
+
Ok::<_, anyhow::Error>(())
|
|
501
504
|
});
|
|
502
505
|
}
|
|
503
506
|
Some(activity_task::Variant::Cancel(_)) => {
|
|
@@ -716,9 +719,22 @@ impl<T: Debug> WfExitValue<T> {
|
|
|
716
719
|
}
|
|
717
720
|
}
|
|
718
721
|
|
|
722
|
+
/// Activity functions may return these values when exiting
|
|
723
|
+
#[derive(Debug, derive_more::From)]
|
|
724
|
+
pub enum ActExitValue<T: Debug> {
|
|
725
|
+
/// Completion requires an asynchronous callback
|
|
726
|
+
#[from(ignore)]
|
|
727
|
+
WillCompleteAsync,
|
|
728
|
+
/// Finish with a result
|
|
729
|
+
Normal(T),
|
|
730
|
+
}
|
|
731
|
+
|
|
719
732
|
type BoxActFn = Arc<
|
|
720
|
-
dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<Payload
|
|
733
|
+
dyn Fn(ActContext, Payload) -> BoxFuture<'static, Result<ActExitValue<Payload>, anyhow::Error>>
|
|
734
|
+
+ Send
|
|
735
|
+
+ Sync,
|
|
721
736
|
>;
|
|
737
|
+
|
|
722
738
|
/// Container for user-defined activity functions
|
|
723
739
|
#[derive(Clone)]
|
|
724
740
|
pub struct ActivityFunction {
|
|
@@ -738,24 +754,35 @@ impl Display for ActivityCancelledError {
|
|
|
738
754
|
}
|
|
739
755
|
|
|
740
756
|
/// Closures / functions which can be turned into activity functions implement this trait
|
|
741
|
-
pub trait IntoActivityFunc<Args, Res> {
|
|
757
|
+
pub trait IntoActivityFunc<Args, Res, Out> {
|
|
742
758
|
/// Consume the closure or fn pointer and turned it into a boxed activity function
|
|
743
759
|
fn into_activity_fn(self) -> BoxActFn;
|
|
744
760
|
}
|
|
745
761
|
|
|
746
|
-
impl<A, Rf, R, F> IntoActivityFunc<A, Rf> for F
|
|
762
|
+
impl<A, Rf, R, O, F> IntoActivityFunc<A, Rf, O> for F
|
|
747
763
|
where
|
|
748
764
|
F: (Fn(ActContext, A) -> Rf) + Sync + Send + 'static,
|
|
749
765
|
A: FromJsonPayloadExt + Send,
|
|
750
766
|
Rf: Future<Output = Result<R, anyhow::Error>> + Send + 'static,
|
|
751
|
-
R:
|
|
767
|
+
R: Into<ActExitValue<O>>,
|
|
768
|
+
O: AsJsonPayloadExt + Debug,
|
|
752
769
|
{
|
|
753
770
|
fn into_activity_fn(self) -> BoxActFn {
|
|
754
771
|
let wrapper = move |ctx: ActContext, input: Payload| {
|
|
755
772
|
// Some minor gymnastics are required to avoid needing to clone the function
|
|
756
773
|
match A::from_json_payload(&input) {
|
|
757
774
|
Ok(deser) => (self)(ctx, deser)
|
|
758
|
-
.map(|r|
|
|
775
|
+
.map(|r| {
|
|
776
|
+
r.and_then(|r| {
|
|
777
|
+
let exit_val: ActExitValue<O> = r.into();
|
|
778
|
+
Ok(match exit_val {
|
|
779
|
+
ActExitValue::WillCompleteAsync => ActExitValue::WillCompleteAsync,
|
|
780
|
+
ActExitValue::Normal(x) => {
|
|
781
|
+
ActExitValue::Normal(x.as_json_payload()?)
|
|
782
|
+
}
|
|
783
|
+
})
|
|
784
|
+
})
|
|
785
|
+
})
|
|
759
786
|
.boxed(),
|
|
760
787
|
Err(e) => async move { Err(e.into()) }.boxed(),
|
|
761
788
|
}
|
|
@@ -29,7 +29,7 @@ use temporal_sdk_core_protos::{
|
|
|
29
29
|
coresdk::{
|
|
30
30
|
workflow_commands::{
|
|
31
31
|
workflow_command, ActivityCancellationType, CompleteWorkflowExecution,
|
|
32
|
-
ScheduleActivity, StartTimer,
|
|
32
|
+
ScheduleActivity, ScheduleLocalActivity, StartTimer,
|
|
33
33
|
},
|
|
34
34
|
workflow_completion::WorkflowActivationCompletion,
|
|
35
35
|
},
|
|
@@ -290,10 +290,10 @@ impl TestWorker {
|
|
|
290
290
|
self.inner.register_wf(workflow_type, wf_function)
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
-
pub fn register_activity<A, R>(
|
|
293
|
+
pub fn register_activity<A, R, O>(
|
|
294
294
|
&mut self,
|
|
295
295
|
activity_type: impl Into<String>,
|
|
296
|
-
act_function: impl IntoActivityFunc<A, R>,
|
|
296
|
+
act_function: impl IntoActivityFunc<A, R, O>,
|
|
297
297
|
) {
|
|
298
298
|
self.inner.register_activity(activity_type, act_function)
|
|
299
299
|
}
|
|
@@ -493,6 +493,25 @@ pub fn schedule_activity_cmd(
|
|
|
493
493
|
.into()
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
+
pub fn schedule_local_activity_cmd(
|
|
497
|
+
seq: u32,
|
|
498
|
+
activity_id: &str,
|
|
499
|
+
cancellation_type: ActivityCancellationType,
|
|
500
|
+
activity_timeout: Duration,
|
|
501
|
+
) -> workflow_command::Variant {
|
|
502
|
+
ScheduleLocalActivity {
|
|
503
|
+
seq,
|
|
504
|
+
activity_id: activity_id.to_string(),
|
|
505
|
+
activity_type: "test_activity".to_string(),
|
|
506
|
+
schedule_to_start_timeout: Some(activity_timeout.into()),
|
|
507
|
+
start_to_close_timeout: Some(activity_timeout.into()),
|
|
508
|
+
schedule_to_close_timeout: Some(activity_timeout.into()),
|
|
509
|
+
cancellation_type: cancellation_type as i32,
|
|
510
|
+
..Default::default()
|
|
511
|
+
}
|
|
512
|
+
.into()
|
|
513
|
+
}
|
|
514
|
+
|
|
496
515
|
pub fn start_timer_cmd(seq: u32, duration: Duration) -> workflow_command::Variant {
|
|
497
516
|
StartTimer {
|
|
498
517
|
seq,
|