@temporalio/core-bridge 0.20.2 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +137 -127
- package/index.d.ts +7 -2
- package/package.json +3 -3
- package/releases/aarch64-apple-darwin/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/.buildkite/docker/docker-compose.yaml +5 -4
- package/sdk-core/client/Cargo.toml +1 -0
- package/sdk-core/client/src/lib.rs +52 -9
- package/sdk-core/client/src/raw.rs +9 -1
- package/sdk-core/client/src/retry.rs +12 -1
- package/sdk-core/client/src/workflow_handle/mod.rs +183 -0
- package/sdk-core/core/src/abstractions.rs +10 -3
- package/sdk-core/core/src/core_tests/child_workflows.rs +7 -9
- package/sdk-core/core/src/core_tests/determinism.rs +8 -19
- package/sdk-core/core/src/core_tests/local_activities.rs +22 -32
- package/sdk-core/core/src/core_tests/queries.rs +272 -5
- package/sdk-core/core/src/core_tests/workers.rs +4 -34
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +197 -41
- package/sdk-core/core/src/pending_activations.rs +11 -0
- package/sdk-core/core/src/telemetry/mod.rs +1 -1
- package/sdk-core/core/src/test_help/mod.rs +57 -7
- package/sdk-core/core/src/worker/mod.rs +64 -15
- package/sdk-core/core/src/workflow/machines/mod.rs +1 -1
- package/sdk-core/core/src/workflow/machines/timer_state_machine.rs +2 -2
- package/sdk-core/core/src/workflow/machines/workflow_machines.rs +14 -3
- package/sdk-core/core/src/workflow/mod.rs +5 -2
- package/sdk-core/core/src/workflow/workflow_tasks/cache_manager.rs +47 -2
- package/sdk-core/core/src/workflow/workflow_tasks/concurrency_manager.rs +16 -2
- package/sdk-core/core/src/workflow/workflow_tasks/mod.rs +252 -125
- package/sdk-core/core-api/src/worker.rs +9 -0
- package/sdk-core/sdk/Cargo.toml +1 -0
- package/sdk-core/sdk/src/activity_context.rs +223 -0
- package/sdk-core/sdk/src/interceptors.rs +8 -2
- package/sdk-core/sdk/src/lib.rs +167 -122
- package/sdk-core/sdk-core-protos/src/history_info.rs +3 -7
- package/sdk-core/test-utils/Cargo.toml +1 -0
- package/sdk-core/test-utils/src/lib.rs +78 -37
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +11 -4
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +0 -1
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +0 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +33 -17
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +10 -1
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +0 -1
- package/sdk-core/tests/integ_tests/workflow_tests.rs +71 -3
- package/sdk-core/tests/load_tests.rs +80 -6
- package/src/errors.rs +9 -2
- package/src/lib.rs +39 -16
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
|
@@ -6,12 +6,13 @@ extern crate tracing;
|
|
|
6
6
|
|
|
7
7
|
pub mod canned_histories;
|
|
8
8
|
|
|
9
|
-
use
|
|
9
|
+
use crate::stream::TryStreamExt;
|
|
10
|
+
use futures::{stream, stream::FuturesUnordered, StreamExt};
|
|
10
11
|
use log::LevelFilter;
|
|
12
|
+
use parking_lot::Mutex;
|
|
11
13
|
use prost::Message;
|
|
12
14
|
use rand::{distributions::Standard, Rng};
|
|
13
15
|
use std::{
|
|
14
|
-
cell::RefCell,
|
|
15
16
|
convert::TryFrom,
|
|
16
17
|
env,
|
|
17
18
|
future::Future,
|
|
@@ -23,7 +24,9 @@ use std::{
|
|
|
23
24
|
},
|
|
24
25
|
time::Duration,
|
|
25
26
|
};
|
|
26
|
-
use temporal_client::{
|
|
27
|
+
use temporal_client::{
|
|
28
|
+
Client, RetryClient, WorkflowClientTrait, WorkflowExecutionInfo, WorkflowOptions,
|
|
29
|
+
};
|
|
27
30
|
use temporal_sdk::{interceptors::WorkerInterceptor, IntoActivityFunc, Worker, WorkflowFunction};
|
|
28
31
|
use temporal_sdk_core::{
|
|
29
32
|
init_replay_worker, init_worker, telemetry_init, ClientOptions, ClientOptionsBuilder,
|
|
@@ -251,25 +254,30 @@ impl CoreWfStarter {
|
|
|
251
254
|
}
|
|
252
255
|
}
|
|
253
256
|
|
|
254
|
-
/// Provides conveniences for running integ tests with the SDK
|
|
257
|
+
/// Provides conveniences for running integ tests with the SDK (against real server or mocks)
|
|
255
258
|
pub struct TestWorker {
|
|
256
259
|
inner: Worker,
|
|
257
|
-
|
|
260
|
+
pub orig_core_worker: Arc<dyn CoreWorker>,
|
|
261
|
+
client: Option<Arc<RetryClient<Client>>>,
|
|
262
|
+
/// Defaults true, and if set, auto-shutdown the worker once all workflows started via
|
|
263
|
+
/// `submit_wf` have completed (as determined by fetching their result with following runs).
|
|
264
|
+
pub auto_shutdown: bool,
|
|
265
|
+
started_workflows: Mutex<Vec<WorkflowExecutionInfo>>,
|
|
266
|
+
/// Used only for mocked testing, where fetching results to determine if a WF is finished is too
|
|
267
|
+
/// annoying to make work.
|
|
258
268
|
incomplete_workflows: Arc<AtomicUsize>,
|
|
259
269
|
}
|
|
260
270
|
impl TestWorker {
|
|
261
271
|
/// Create a new test worker
|
|
262
272
|
pub fn new(core_worker: Arc<dyn CoreWorker>, task_queue: impl Into<String>) -> Self {
|
|
263
273
|
let ct = Arc::new(AtomicUsize::new(0));
|
|
264
|
-
let
|
|
265
|
-
let iceptor = WorkflowCompletionCountingInterceptor {
|
|
266
|
-
incomplete_workflows: ct.clone(),
|
|
267
|
-
shutdown_handle: Box::new(inner.shutdown_handle()),
|
|
268
|
-
};
|
|
269
|
-
inner.set_worker_interceptor(Box::new(iceptor));
|
|
274
|
+
let inner = Worker::new_from_core(core_worker.clone(), task_queue);
|
|
270
275
|
Self {
|
|
271
276
|
inner,
|
|
277
|
+
orig_core_worker: core_worker,
|
|
272
278
|
client: None,
|
|
279
|
+
auto_shutdown: true,
|
|
280
|
+
started_workflows: Mutex::new(vec![]),
|
|
273
281
|
incomplete_workflows: ct,
|
|
274
282
|
}
|
|
275
283
|
}
|
|
@@ -278,11 +286,6 @@ impl TestWorker {
|
|
|
278
286
|
&mut self.inner
|
|
279
287
|
}
|
|
280
288
|
|
|
281
|
-
pub fn incr_expected_run_count(&self, amount: usize) {
|
|
282
|
-
self.incomplete_workflows
|
|
283
|
-
.fetch_add(amount, Ordering::AcqRel);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
289
|
// TODO: Maybe trait-ify?
|
|
287
290
|
pub fn register_wf<F: Into<WorkflowFunction>>(
|
|
288
291
|
&mut self,
|
|
@@ -313,7 +316,6 @@ impl TestWorker {
|
|
|
313
316
|
input: Vec<Payload>,
|
|
314
317
|
options: WorkflowOptions,
|
|
315
318
|
) -> Result<String, anyhow::Error> {
|
|
316
|
-
self.incr_expected_run_count(1);
|
|
317
319
|
if let Some(c) = self.client.as_ref() {
|
|
318
320
|
let wfid = workflow_id.into();
|
|
319
321
|
let res = c
|
|
@@ -325,6 +327,11 @@ impl TestWorker {
|
|
|
325
327
|
options,
|
|
326
328
|
)
|
|
327
329
|
.await?;
|
|
330
|
+
self.started_workflows.lock().push(WorkflowExecutionInfo {
|
|
331
|
+
namespace: c.namespace().to_string(),
|
|
332
|
+
workflow_id: wfid,
|
|
333
|
+
run_id: Some(res.run_id.clone()),
|
|
334
|
+
});
|
|
328
335
|
Ok(res.run_id)
|
|
329
336
|
} else {
|
|
330
337
|
Ok("fake_run_id".to_string())
|
|
@@ -333,46 +340,80 @@ impl TestWorker {
|
|
|
333
340
|
|
|
334
341
|
/// Runs until all expected workflows have completed
|
|
335
342
|
pub async fn run_until_done(&mut self) -> Result<(), anyhow::Error> {
|
|
336
|
-
self.
|
|
343
|
+
self.run_until_done_intercepted(Option::<TestWorkerInterceptor>::None)
|
|
344
|
+
.await
|
|
337
345
|
}
|
|
338
346
|
|
|
339
347
|
/// See [Self::run_until_done], except calls the provided callback just before performing core
|
|
340
348
|
/// shutdown.
|
|
341
|
-
pub async fn
|
|
349
|
+
pub async fn run_until_done_intercepted(
|
|
342
350
|
&mut self,
|
|
343
|
-
|
|
351
|
+
interceptor: Option<impl WorkerInterceptor + 'static>,
|
|
344
352
|
) -> Result<(), anyhow::Error> {
|
|
345
|
-
|
|
346
|
-
let b4shut = RefCell::new(Some(before_shutdown));
|
|
347
|
-
let shutdown_handle = self.inner.shutdown_handle();
|
|
348
|
-
let iceptor = WorkflowCompletionCountingInterceptor {
|
|
353
|
+
let iceptor = TestWorkerInterceptor {
|
|
349
354
|
incomplete_workflows: self.incomplete_workflows.clone(),
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
355
|
+
// Only use counting-based shutdown in the interceptor in mocked tests
|
|
356
|
+
shutdown_handle: if self.auto_shutdown && self.client.is_none() {
|
|
357
|
+
Some(Box::new(self.inner.shutdown_handle()))
|
|
358
|
+
} else {
|
|
359
|
+
None
|
|
360
|
+
},
|
|
361
|
+
next: interceptor.map(|i| Box::new(i) as Box<dyn WorkerInterceptor>),
|
|
356
362
|
};
|
|
357
363
|
self.inner.set_worker_interceptor(Box::new(iceptor));
|
|
358
|
-
|
|
364
|
+
let workflows_complete_fut = async {
|
|
365
|
+
if self.auto_shutdown && self.client.is_some() {
|
|
366
|
+
let wfs = std::mem::take(self.started_workflows.get_mut());
|
|
367
|
+
let client = self.client.clone().expect("Client must exist when running");
|
|
368
|
+
stream::iter(
|
|
369
|
+
wfs.into_iter()
|
|
370
|
+
.map(|info| info.bind_untyped((*client).clone())),
|
|
371
|
+
)
|
|
372
|
+
.map(Ok)
|
|
373
|
+
.try_for_each_concurrent(None, |wh| async move {
|
|
374
|
+
wh.get_workflow_result(Default::default()).await?;
|
|
375
|
+
Ok::<_, anyhow::Error>(())
|
|
376
|
+
})
|
|
377
|
+
.await?;
|
|
378
|
+
self.orig_core_worker.initiate_shutdown();
|
|
379
|
+
}
|
|
380
|
+
Ok::<_, anyhow::Error>(())
|
|
381
|
+
};
|
|
382
|
+
tokio::try_join!(self.inner.run(), workflows_complete_fut)?;
|
|
383
|
+
Ok(())
|
|
359
384
|
}
|
|
360
385
|
}
|
|
361
386
|
|
|
362
|
-
|
|
387
|
+
/// Implements calling the shutdown handle when the expected number of test workflows has completed
|
|
388
|
+
struct TestWorkerInterceptor {
|
|
363
389
|
incomplete_workflows: Arc<AtomicUsize>,
|
|
364
|
-
shutdown_handle: Box<dyn Fn()
|
|
390
|
+
shutdown_handle: Option<Box<dyn Fn()>>,
|
|
391
|
+
next: Option<Box<dyn WorkerInterceptor>>,
|
|
365
392
|
}
|
|
366
|
-
|
|
367
|
-
|
|
393
|
+
|
|
394
|
+
#[async_trait::async_trait(?Send)]
|
|
395
|
+
impl WorkerInterceptor for TestWorkerInterceptor {
|
|
396
|
+
async fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
|
|
368
397
|
if completion.has_execution_ending() {
|
|
369
398
|
info!("Workflow {} says it's finishing", &completion.run_id);
|
|
370
399
|
let prev = self.incomplete_workflows.fetch_sub(1, Ordering::SeqCst);
|
|
371
400
|
if prev <= 1 {
|
|
372
|
-
|
|
373
|
-
|
|
401
|
+
if let Some(sh) = self.shutdown_handle.as_ref() {
|
|
402
|
+
info!("Test worker calling shutdown");
|
|
403
|
+
// There are now zero, we just subtracted one
|
|
404
|
+
sh()
|
|
405
|
+
}
|
|
374
406
|
}
|
|
375
407
|
}
|
|
408
|
+
if let Some(n) = self.next.as_ref() {
|
|
409
|
+
n.on_workflow_activation_completion(completion).await;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
fn on_shutdown(&self, sdk_worker: &Worker) {
|
|
414
|
+
if let Some(n) = self.next.as_ref() {
|
|
415
|
+
n.on_shutdown(sdk_worker);
|
|
416
|
+
}
|
|
376
417
|
}
|
|
377
418
|
}
|
|
378
419
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use assert_matches::assert_matches;
|
|
2
2
|
use std::time::Duration;
|
|
3
|
-
use temporal_client::{WorkflowClientTrait, WorkflowOptions};
|
|
4
|
-
use temporal_sdk::{ActivityOptions, WfContext, WorkflowResult};
|
|
3
|
+
use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowExecutionResult, WorkflowOptions};
|
|
4
|
+
use temporal_sdk::{ActContext, ActivityOptions, WfContext, WorkflowResult};
|
|
5
5
|
use temporal_sdk_core_protos::{
|
|
6
6
|
coresdk::{
|
|
7
7
|
activity_result::{
|
|
@@ -44,13 +44,14 @@ async fn one_activity() {
|
|
|
44
44
|
let wf_name = "one_activity";
|
|
45
45
|
let mut starter = CoreWfStarter::new(wf_name);
|
|
46
46
|
let mut worker = starter.worker().await;
|
|
47
|
+
let client = starter.get_client().await;
|
|
47
48
|
worker.register_wf(wf_name.to_owned(), one_activity_wf);
|
|
48
49
|
worker.register_activity(
|
|
49
50
|
"echo_activity",
|
|
50
|
-
|echo_me: String| async move { Ok(echo_me) },
|
|
51
|
+
|_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
|
|
51
52
|
);
|
|
52
53
|
|
|
53
|
-
worker
|
|
54
|
+
let run_id = worker
|
|
54
55
|
.submit_wf(
|
|
55
56
|
wf_name.to_owned(),
|
|
56
57
|
wf_name.to_owned(),
|
|
@@ -60,6 +61,12 @@ async fn one_activity() {
|
|
|
60
61
|
.await
|
|
61
62
|
.unwrap();
|
|
62
63
|
worker.run_until_done().await.unwrap();
|
|
64
|
+
let handle = client.get_untyped_workflow_handle(wf_name, run_id);
|
|
65
|
+
let res = handle
|
|
66
|
+
.get_workflow_result(Default::default())
|
|
67
|
+
.await
|
|
68
|
+
.unwrap();
|
|
69
|
+
assert_matches!(res, WorkflowExecutionResult::Succeeded(_));
|
|
63
70
|
}
|
|
64
71
|
|
|
65
72
|
#[tokio::test]
|
|
@@ -36,7 +36,6 @@ async fn child_workflow_happy_path() {
|
|
|
36
36
|
|
|
37
37
|
worker.register_wf(PARENT_WF_TYPE.to_string(), parent_wf);
|
|
38
38
|
worker.register_wf(CHILD_WF_TYPE.to_string(), child_wf);
|
|
39
|
-
worker.incr_expected_run_count(1); // Expect another WF to be run as child
|
|
40
39
|
|
|
41
40
|
worker
|
|
42
41
|
.submit_wf(
|
|
@@ -33,8 +33,6 @@ async fn continue_as_new_happy_path() {
|
|
|
33
33
|
)
|
|
34
34
|
.await
|
|
35
35
|
.unwrap();
|
|
36
|
-
// The four additional runs
|
|
37
|
-
worker.incr_expected_run_count(4);
|
|
38
36
|
worker.run_until_done().await.unwrap();
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -58,6 +56,5 @@ async fn continue_as_new_multiple_concurrent() {
|
|
|
58
56
|
.await
|
|
59
57
|
.unwrap();
|
|
60
58
|
}
|
|
61
|
-
worker.incr_expected_run_count(20 * 4);
|
|
62
59
|
worker.run_until_done().await.unwrap();
|
|
63
60
|
}
|
|
@@ -3,16 +3,17 @@ use futures::future::join_all;
|
|
|
3
3
|
use std::time::Duration;
|
|
4
4
|
use temporal_client::WorkflowOptions;
|
|
5
5
|
use temporal_sdk::{
|
|
6
|
-
|
|
6
|
+
interceptors::WorkerInterceptor, ActContext, ActivityCancelledError, CancellableFuture,
|
|
7
7
|
LocalActivityOptions, WfContext, WorkflowResult,
|
|
8
8
|
};
|
|
9
9
|
use temporal_sdk_core_protos::coresdk::{
|
|
10
|
-
common::RetryPolicy, workflow_commands::ActivityCancellationType,
|
|
10
|
+
common::RetryPolicy, workflow_commands::ActivityCancellationType,
|
|
11
|
+
workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
|
|
11
12
|
};
|
|
12
13
|
use temporal_sdk_core_test_utils::CoreWfStarter;
|
|
13
14
|
use tokio_util::sync::CancellationToken;
|
|
14
15
|
|
|
15
|
-
pub async fn echo(e: String) -> anyhow::Result<String> {
|
|
16
|
+
pub async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
|
|
16
17
|
Ok(e)
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -119,7 +120,7 @@ async fn long_running_local_act_with_timer() {
|
|
|
119
120
|
starter.wft_timeout(Duration::from_secs(1));
|
|
120
121
|
let mut worker = starter.worker().await;
|
|
121
122
|
worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
|
|
122
|
-
worker.register_activity("echo_activity", |str: String| async {
|
|
123
|
+
worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
|
|
123
124
|
tokio::time::sleep(Duration::from_secs(4)).await;
|
|
124
125
|
Ok(str)
|
|
125
126
|
});
|
|
@@ -199,7 +200,7 @@ async fn local_act_retry_timer_backoff() {
|
|
|
199
200
|
assert!(res.failed());
|
|
200
201
|
Ok(().into())
|
|
201
202
|
});
|
|
202
|
-
worker.register_activity("echo", |_: String| async {
|
|
203
|
+
worker.register_activity("echo", |_: ActContext, _: String| async {
|
|
203
204
|
Result::<(), _>::Err(anyhow!("Oh no I failed!"))
|
|
204
205
|
});
|
|
205
206
|
|
|
@@ -245,12 +246,12 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
|
|
|
245
246
|
let manual_cancel = CancellationToken::new();
|
|
246
247
|
let manual_cancel_act = manual_cancel.clone();
|
|
247
248
|
|
|
248
|
-
worker.register_activity("echo", move |_: String| {
|
|
249
|
+
worker.register_activity("echo", move |ctx: ActContext, _: String| {
|
|
249
250
|
let manual_cancel_act = manual_cancel_act.clone();
|
|
250
251
|
async move {
|
|
251
252
|
tokio::select! {
|
|
252
253
|
_ = tokio::time::sleep(Duration::from_secs(10)) => {},
|
|
253
|
-
_ =
|
|
254
|
+
_ = ctx.cancelled() => {
|
|
254
255
|
return Err(anyhow!(ActivityCancelledError::default()))
|
|
255
256
|
}
|
|
256
257
|
_ = manual_cancel_act.cancelled() => {}
|
|
@@ -269,11 +270,24 @@ async fn cancel_immediate(#[case] cancel_type: ActivityCancellationType) {
|
|
|
269
270
|
.await
|
|
270
271
|
.unwrap();
|
|
271
272
|
worker
|
|
272
|
-
.
|
|
273
|
+
.run_until_done_intercepted(Some(LACancellerInterceptor {
|
|
274
|
+
token: manual_cancel,
|
|
275
|
+
}))
|
|
273
276
|
.await
|
|
274
277
|
.unwrap();
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
struct LACancellerInterceptor {
|
|
281
|
+
token: CancellationToken,
|
|
282
|
+
}
|
|
283
|
+
#[async_trait::async_trait(?Send)]
|
|
284
|
+
impl WorkerInterceptor for LACancellerInterceptor {
|
|
285
|
+
async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {}
|
|
286
|
+
fn on_shutdown(&self, _: &temporal_sdk::Worker) {
|
|
287
|
+
self.token.cancel()
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
277
291
|
#[rstest::rstest]
|
|
278
292
|
#[case::while_running(None)]
|
|
279
293
|
#[case::while_backing_off(Some(Duration::from_millis(1500)))]
|
|
@@ -329,11 +343,11 @@ async fn cancel_after_act_starts(
|
|
|
329
343
|
let manual_cancel = CancellationToken::new();
|
|
330
344
|
let manual_cancel_act = manual_cancel.clone();
|
|
331
345
|
|
|
332
|
-
worker.register_activity("echo", move |_: String| {
|
|
346
|
+
worker.register_activity("echo", move |ctx: ActContext, _: String| {
|
|
333
347
|
let manual_cancel_act = manual_cancel_act.clone();
|
|
334
348
|
async move {
|
|
335
349
|
if cancel_on_backoff.is_some() {
|
|
336
|
-
if
|
|
350
|
+
if ctx.is_cancelled() {
|
|
337
351
|
return Err(anyhow!(ActivityCancelledError::default()));
|
|
338
352
|
}
|
|
339
353
|
// Just fail constantly so we get stuck on the backoff timer
|
|
@@ -341,7 +355,7 @@ async fn cancel_after_act_starts(
|
|
|
341
355
|
} else {
|
|
342
356
|
tokio::select! {
|
|
343
357
|
_ = tokio::time::sleep(Duration::from_secs(100)) => {},
|
|
344
|
-
_ =
|
|
358
|
+
_ = ctx.cancelled() => {
|
|
345
359
|
return Err(anyhow!(ActivityCancelledError::default()))
|
|
346
360
|
}
|
|
347
361
|
_ = manual_cancel_act.cancelled() => {
|
|
@@ -363,7 +377,9 @@ async fn cancel_after_act_starts(
|
|
|
363
377
|
.await
|
|
364
378
|
.unwrap();
|
|
365
379
|
worker
|
|
366
|
-
.
|
|
380
|
+
.run_until_done_intercepted(Some(LACancellerInterceptor {
|
|
381
|
+
token: manual_cancel,
|
|
382
|
+
}))
|
|
367
383
|
.await
|
|
368
384
|
.unwrap();
|
|
369
385
|
}
|
|
@@ -406,10 +422,10 @@ async fn x_to_close_timeout(#[case] is_schedule: bool) {
|
|
|
406
422
|
assert!(res.timed_out());
|
|
407
423
|
Ok(().into())
|
|
408
424
|
});
|
|
409
|
-
worker.register_activity("echo", |_: String| async {
|
|
425
|
+
worker.register_activity("echo", |ctx: ActContext, _: String| async move {
|
|
410
426
|
tokio::select! {
|
|
411
427
|
_ = tokio::time::sleep(Duration::from_secs(100)) => {},
|
|
412
|
-
_ =
|
|
428
|
+
_ = ctx.cancelled() => {
|
|
413
429
|
return Err(anyhow!(ActivityCancelledError::default()))
|
|
414
430
|
}
|
|
415
431
|
};
|
|
@@ -462,7 +478,7 @@ async fn schedule_to_close_timeout_across_timer_backoff(#[case] cached: bool) {
|
|
|
462
478
|
assert!(res.timed_out());
|
|
463
479
|
Ok(().into())
|
|
464
480
|
});
|
|
465
|
-
worker.register_activity("echo", |_: String| async {
|
|
481
|
+
worker.register_activity("echo", |_: ActContext, _: String| async {
|
|
466
482
|
Result::<(), _>::Err(anyhow!("Oh no I failed!"))
|
|
467
483
|
});
|
|
468
484
|
|
|
@@ -486,7 +502,7 @@ async fn eviction_wont_make_local_act_get_dropped() {
|
|
|
486
502
|
starter.wft_timeout(Duration::from_secs(1));
|
|
487
503
|
let mut worker = starter.worker().await;
|
|
488
504
|
worker.register_wf(wf_name.to_owned(), local_act_then_timer_then_wait);
|
|
489
|
-
worker.register_activity("echo_activity", |str: String| async {
|
|
505
|
+
worker.register_activity("echo_activity", |_ctx: ActContext, str: String| async {
|
|
490
506
|
tokio::time::sleep(Duration::from_secs(4)).await;
|
|
491
507
|
Ok(str)
|
|
492
508
|
});
|
|
@@ -540,7 +556,7 @@ async fn timer_backoff_concurrent_with_non_timer_backoff() {
|
|
|
540
556
|
assert!(r2.failed());
|
|
541
557
|
Ok(().into())
|
|
542
558
|
});
|
|
543
|
-
worker.register_activity("echo", |_: String| async {
|
|
559
|
+
worker.register_activity("echo", |_: ActContext, _: String| async {
|
|
544
560
|
Result::<(), _>::Err(anyhow!("Oh no I failed!"))
|
|
545
561
|
});
|
|
546
562
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use futures::StreamExt;
|
|
2
2
|
use std::{sync::Arc, time::Duration};
|
|
3
|
-
use temporal_client::{WorkflowClientTrait, WorkflowOptions, WorkflowService};
|
|
3
|
+
use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions, WorkflowService};
|
|
4
4
|
use temporal_sdk::WfContext;
|
|
5
5
|
use temporal_sdk_core_protos::temporal::api::{
|
|
6
6
|
common::v1::WorkflowExecution, workflowservice::v1::ResetWorkflowExecutionRequest,
|
|
@@ -15,6 +15,7 @@ async fn reset_workflow() {
|
|
|
15
15
|
let wf_name = "reset_me_wf";
|
|
16
16
|
let mut starter = CoreWfStarter::new(wf_name);
|
|
17
17
|
let mut worker = starter.worker().await;
|
|
18
|
+
worker.auto_shutdown = false;
|
|
18
19
|
let notify = Arc::new(Notify::new());
|
|
19
20
|
|
|
20
21
|
let wf_notify = notify.clone();
|
|
@@ -77,6 +78,14 @@ async fn reset_workflow() {
|
|
|
77
78
|
)
|
|
78
79
|
.await
|
|
79
80
|
.unwrap();
|
|
81
|
+
|
|
82
|
+
// Wait for the now-reset workflow to finish
|
|
83
|
+
client
|
|
84
|
+
.get_untyped_workflow_handle(wf_name.to_owned(), "")
|
|
85
|
+
.get_workflow_result(Default::default())
|
|
86
|
+
.await
|
|
87
|
+
.unwrap();
|
|
88
|
+
starter.shutdown().await;
|
|
80
89
|
};
|
|
81
90
|
let run_fut = worker.run_until_done();
|
|
82
91
|
let (_, rr) = tokio::join!(resetter_fut, run_fut);
|
|
@@ -107,7 +107,6 @@ async fn sends_signal_to_child() {
|
|
|
107
107
|
worker.register_wf("child_signaler", signals_child);
|
|
108
108
|
worker.register_wf("child_receiver", signal_receiver);
|
|
109
109
|
|
|
110
|
-
worker.incr_expected_run_count(1); // Expect another WF to be run as child
|
|
111
110
|
worker
|
|
112
111
|
.submit_wf(
|
|
113
112
|
"sends-signal-to-child",
|
|
@@ -24,7 +24,9 @@ use std::{
|
|
|
24
24
|
time::Duration,
|
|
25
25
|
};
|
|
26
26
|
use temporal_client::{WorkflowClientTrait, WorkflowOptions};
|
|
27
|
-
use temporal_sdk::{
|
|
27
|
+
use temporal_sdk::{
|
|
28
|
+
interceptors::WorkerInterceptor, ActContext, ActivityOptions, WfContext, WorkflowResult,
|
|
29
|
+
};
|
|
28
30
|
use temporal_sdk_core_api::{errors::PollWfError, Worker};
|
|
29
31
|
use temporal_sdk_core_protos::{
|
|
30
32
|
coresdk::{
|
|
@@ -32,7 +34,7 @@ use temporal_sdk_core_protos::{
|
|
|
32
34
|
workflow_activation::{workflow_activation_job, WorkflowActivation, WorkflowActivationJob},
|
|
33
35
|
workflow_commands::{ActivityCancellationType, FailWorkflowExecution, StartTimer},
|
|
34
36
|
workflow_completion::WorkflowActivationCompletion,
|
|
35
|
-
ActivityTaskCompletion, IntoCompletion,
|
|
37
|
+
ActivityTaskCompletion, AsJsonPayloadExt, IntoCompletion,
|
|
36
38
|
},
|
|
37
39
|
temporal::api::failure::v1::Failure,
|
|
38
40
|
};
|
|
@@ -128,7 +130,19 @@ async fn workflow_lru_cache_evictions() {
|
|
|
128
130
|
.await
|
|
129
131
|
.unwrap();
|
|
130
132
|
}
|
|
131
|
-
|
|
133
|
+
struct CacheAsserter;
|
|
134
|
+
#[async_trait::async_trait(?Send)]
|
|
135
|
+
impl WorkerInterceptor for CacheAsserter {
|
|
136
|
+
async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {}
|
|
137
|
+
fn on_shutdown(&self, sdk_worker: &temporal_sdk::Worker) {
|
|
138
|
+
// 0 since the sdk worker force-evicts and drains everything on shutdown.
|
|
139
|
+
assert_eq!(sdk_worker.cached_workflows(), 0);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
worker
|
|
143
|
+
.run_until_done_intercepted(Some(CacheAsserter))
|
|
144
|
+
.await
|
|
145
|
+
.unwrap();
|
|
132
146
|
// The wf must have started more than # workflows times, since all but one must experience
|
|
133
147
|
// an eviction
|
|
134
148
|
assert!(RUN_CT.load(Ordering::SeqCst) > n_workflows);
|
|
@@ -515,3 +529,57 @@ async fn wft_timeout_doesnt_create_unsolvable_autocomplete() {
|
|
|
515
529
|
let wf_task = poll_sched_act_poll().await;
|
|
516
530
|
core.complete_execution(&wf_task.run_id).await;
|
|
517
531
|
}
|
|
532
|
+
|
|
533
|
+
/// We had a bug related to polling being faster than completion causing issues with cache
|
|
534
|
+
/// overflow. This test intentionally makes completes slower than polls to evaluate that.
|
|
535
|
+
///
|
|
536
|
+
/// It's expected that this test may generate some task timeouts.
|
|
537
|
+
#[tokio::test]
|
|
538
|
+
async fn slow_completes_with_small_cache() {
|
|
539
|
+
let wf_name = "slow_completes_with_small_cache";
|
|
540
|
+
let mut starter = CoreWfStarter::new(wf_name);
|
|
541
|
+
starter.max_wft(5).max_cached_workflows(5);
|
|
542
|
+
let mut worker = starter.worker().await;
|
|
543
|
+
worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
|
|
544
|
+
for _ in 0..3 {
|
|
545
|
+
ctx.activity(ActivityOptions {
|
|
546
|
+
activity_type: "echo_activity".to_string(),
|
|
547
|
+
start_to_close_timeout: Some(Duration::from_secs(5)),
|
|
548
|
+
input: "hi!".as_json_payload().expect("serializes fine"),
|
|
549
|
+
..Default::default()
|
|
550
|
+
})
|
|
551
|
+
.await;
|
|
552
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
553
|
+
}
|
|
554
|
+
Ok(().into())
|
|
555
|
+
});
|
|
556
|
+
worker.register_activity(
|
|
557
|
+
"echo_activity",
|
|
558
|
+
|_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
|
|
559
|
+
);
|
|
560
|
+
for i in 0..20 {
|
|
561
|
+
worker
|
|
562
|
+
.submit_wf(
|
|
563
|
+
format!("{}_{}", wf_name, i),
|
|
564
|
+
wf_name.to_owned(),
|
|
565
|
+
vec![],
|
|
566
|
+
WorkflowOptions::default(),
|
|
567
|
+
)
|
|
568
|
+
.await
|
|
569
|
+
.unwrap();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
struct SlowCompleter {}
|
|
573
|
+
#[async_trait::async_trait(?Send)]
|
|
574
|
+
impl WorkerInterceptor for SlowCompleter {
|
|
575
|
+
async fn on_workflow_activation_completion(&self, _: &WorkflowActivationCompletion) {
|
|
576
|
+
// They don't need to be much slower
|
|
577
|
+
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
578
|
+
}
|
|
579
|
+
fn on_shutdown(&self, _: &temporal_sdk::Worker) {}
|
|
580
|
+
}
|
|
581
|
+
worker
|
|
582
|
+
.run_until_done_intercepted(Some(SlowCompleter {}))
|
|
583
|
+
.await
|
|
584
|
+
.unwrap();
|
|
585
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
use assert_matches::assert_matches;
|
|
2
|
-
use futures::future::join_all;
|
|
2
|
+
use futures::{future::join_all, sink, stream::FuturesUnordered, StreamExt};
|
|
3
3
|
use std::time::{Duration, Instant};
|
|
4
|
-
use temporal_client::WorkflowOptions;
|
|
5
|
-
use temporal_sdk::{ActivityOptions, WfContext};
|
|
4
|
+
use temporal_client::{WfClientExt, WorkflowClientTrait, WorkflowOptions};
|
|
5
|
+
use temporal_sdk::{ActContext, ActivityOptions, WfContext};
|
|
6
6
|
use temporal_sdk_core_protos::coresdk::{
|
|
7
7
|
activity_result::ActivityExecutionResult, activity_task::activity_task as act_task,
|
|
8
|
-
workflow_commands::ActivityCancellationType, ActivityTaskCompletion,
|
|
8
|
+
workflow_commands::ActivityCancellationType, ActivityTaskCompletion, AsJsonPayloadExt,
|
|
9
9
|
};
|
|
10
10
|
use temporal_sdk_core_test_utils::CoreWfStarter;
|
|
11
11
|
|
|
12
|
-
const CONCURRENCY: usize = 1000;
|
|
13
|
-
|
|
14
12
|
#[tokio::test]
|
|
15
13
|
async fn activity_load() {
|
|
14
|
+
const CONCURRENCY: usize = 1000;
|
|
15
|
+
|
|
16
16
|
let mut starter = CoreWfStarter::new("activity_load");
|
|
17
17
|
starter
|
|
18
18
|
.max_wft(CONCURRENCY)
|
|
@@ -109,3 +109,77 @@ async fn activity_load() {
|
|
|
109
109
|
};
|
|
110
110
|
dbg!(running.elapsed());
|
|
111
111
|
}
|
|
112
|
+
|
|
113
|
+
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
|
114
|
+
async fn workflow_load() {
|
|
115
|
+
const SIGNAME: &str = "signame";
|
|
116
|
+
let wf_name = "workflow_load";
|
|
117
|
+
let mut starter = CoreWfStarter::new("workflow_load");
|
|
118
|
+
starter
|
|
119
|
+
.max_wft(5)
|
|
120
|
+
.max_cached_workflows(5)
|
|
121
|
+
.max_at_polls(10)
|
|
122
|
+
.max_at(100);
|
|
123
|
+
let mut worker = starter.worker().await;
|
|
124
|
+
worker.register_wf(wf_name.to_owned(), |ctx: WfContext| async move {
|
|
125
|
+
let sigchan = ctx.make_signal_channel(SIGNAME).map(Ok);
|
|
126
|
+
let drained_fut = sigchan.forward(sink::drain());
|
|
127
|
+
|
|
128
|
+
let real_stuff = async move {
|
|
129
|
+
for _ in 0..20 {
|
|
130
|
+
ctx.activity(ActivityOptions {
|
|
131
|
+
activity_type: "echo_activity".to_string(),
|
|
132
|
+
start_to_close_timeout: Some(Duration::from_secs(5)),
|
|
133
|
+
input: "hi!".as_json_payload().expect("serializes fine"),
|
|
134
|
+
..Default::default()
|
|
135
|
+
})
|
|
136
|
+
.await;
|
|
137
|
+
ctx.timer(Duration::from_secs(1)).await;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
tokio::select! {
|
|
141
|
+
_ = drained_fut => {}
|
|
142
|
+
_ = real_stuff => {}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Ok(().into())
|
|
146
|
+
});
|
|
147
|
+
worker.register_activity(
|
|
148
|
+
"echo_activity",
|
|
149
|
+
|_ctx: ActContext, echo_me: String| async move { Ok(echo_me) },
|
|
150
|
+
);
|
|
151
|
+
let client = starter.get_client().await;
|
|
152
|
+
|
|
153
|
+
let mut workflow_handles = vec![];
|
|
154
|
+
for i in 0..200 {
|
|
155
|
+
let wfid = format!("{}_{}", wf_name, i);
|
|
156
|
+
let rid = worker
|
|
157
|
+
.submit_wf(
|
|
158
|
+
wfid.clone(),
|
|
159
|
+
wf_name.to_owned(),
|
|
160
|
+
vec![],
|
|
161
|
+
WorkflowOptions::default(),
|
|
162
|
+
)
|
|
163
|
+
.await
|
|
164
|
+
.unwrap();
|
|
165
|
+
workflow_handles.push(client.get_untyped_workflow_handle(wfid, rid));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let sig_sender = async {
|
|
169
|
+
loop {
|
|
170
|
+
let sends: FuturesUnordered<_> = (0..200)
|
|
171
|
+
.map(|i| {
|
|
172
|
+
client.signal_workflow_execution(
|
|
173
|
+
format!("{}_{}", wf_name, i),
|
|
174
|
+
"".to_string(),
|
|
175
|
+
SIGNAME.to_string(),
|
|
176
|
+
None,
|
|
177
|
+
)
|
|
178
|
+
})
|
|
179
|
+
.collect();
|
|
180
|
+
sends.map(|_| Ok(())).forward(sink::drain()).await.unwrap();
|
|
181
|
+
tokio::time::sleep(Duration::from_secs(2)).await;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
tokio::select! { r1 = worker.run_until_done() => {r1.unwrap()}, _ = sig_sender => {}};
|
|
185
|
+
}
|