temporalio 0.0.2 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +25 -23
- data/bridge/Cargo.lock +168 -59
- data/bridge/Cargo.toml +4 -2
- data/bridge/sdk-core/README.md +19 -6
- data/bridge/sdk-core/client/src/lib.rs +215 -39
- data/bridge/sdk-core/client/src/metrics.rs +17 -8
- data/bridge/sdk-core/client/src/raw.rs +4 -4
- data/bridge/sdk-core/client/src/retry.rs +32 -20
- data/bridge/sdk-core/core/Cargo.toml +22 -9
- data/bridge/sdk-core/core/src/abstractions.rs +203 -14
- data/bridge/sdk-core/core/src/core_tests/activity_tasks.rs +76 -41
- data/bridge/sdk-core/core/src/core_tests/determinism.rs +165 -2
- data/bridge/sdk-core/core/src/core_tests/local_activities.rs +204 -83
- data/bridge/sdk-core/core/src/core_tests/queries.rs +3 -4
- data/bridge/sdk-core/core/src/core_tests/workers.rs +1 -3
- data/bridge/sdk-core/core/src/core_tests/workflow_tasks.rs +397 -54
- data/bridge/sdk-core/core/src/ephemeral_server/mod.rs +106 -12
- data/bridge/sdk-core/core/src/internal_flags.rs +136 -0
- data/bridge/sdk-core/core/src/lib.rs +16 -9
- data/bridge/sdk-core/core/src/telemetry/log_export.rs +1 -1
- data/bridge/sdk-core/core/src/telemetry/metrics.rs +69 -35
- data/bridge/sdk-core/core/src/telemetry/mod.rs +29 -13
- data/bridge/sdk-core/core/src/telemetry/prometheus_server.rs +17 -12
- data/bridge/sdk-core/core/src/test_help/mod.rs +62 -12
- data/bridge/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +112 -156
- data/bridge/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
- data/bridge/sdk-core/core/src/worker/activities/local_activities.rs +352 -122
- data/bridge/sdk-core/core/src/worker/activities.rs +233 -157
- data/bridge/sdk-core/core/src/worker/client/mocks.rs +22 -2
- data/bridge/sdk-core/core/src/worker/client.rs +18 -2
- data/bridge/sdk-core/core/src/worker/mod.rs +165 -58
- data/bridge/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
- data/bridge/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
- data/bridge/sdk-core/core/src/worker/workflow/history_update.rs +856 -277
- data/bridge/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +100 -43
- data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +7 -7
- data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +5 -4
- data/bridge/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +87 -27
- data/bridge/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +5 -4
- data/bridge/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +5 -4
- data/bridge/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +5 -4
- data/bridge/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +137 -62
- data/bridge/sdk-core/core/src/worker/workflow/machines/mod.rs +25 -17
- data/bridge/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +7 -6
- data/bridge/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +103 -152
- data/bridge/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +7 -7
- data/bridge/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +9 -9
- data/bridge/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
- data/bridge/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +14 -7
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +5 -16
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +201 -121
- data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +11 -14
- data/bridge/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +30 -15
- data/bridge/sdk-core/core/src/worker/workflow/managed_run.rs +1026 -376
- data/bridge/sdk-core/core/src/worker/workflow/mod.rs +460 -384
- data/bridge/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
- data/bridge/sdk-core/core/src/worker/workflow/wft_extraction.rs +125 -0
- data/bridge/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
- data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
- data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
- data/bridge/sdk-core/core/src/worker/workflow/workflow_stream.rs +448 -718
- data/bridge/sdk-core/core-api/Cargo.toml +2 -1
- data/bridge/sdk-core/core-api/src/errors.rs +1 -34
- data/bridge/sdk-core/core-api/src/lib.rs +6 -2
- data/bridge/sdk-core/core-api/src/telemetry.rs +0 -6
- data/bridge/sdk-core/core-api/src/worker.rs +14 -1
- data/bridge/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +18 -15
- data/bridge/sdk-core/fsm/rustfsm_trait/src/lib.rs +8 -3
- data/bridge/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
- data/bridge/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
- data/bridge/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
- data/bridge/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
- data/bridge/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
- data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
- data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
- data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +7 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -0
- data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +6 -0
- data/bridge/sdk-core/sdk/Cargo.toml +3 -2
- data/bridge/sdk-core/sdk/src/lib.rs +87 -20
- data/bridge/sdk-core/sdk/src/workflow_future.rs +9 -8
- data/bridge/sdk-core/sdk-core-protos/Cargo.toml +5 -2
- data/bridge/sdk-core/sdk-core-protos/build.rs +36 -1
- data/bridge/sdk-core/sdk-core-protos/src/history_builder.rs +100 -87
- data/bridge/sdk-core/sdk-core-protos/src/history_info.rs +5 -1
- data/bridge/sdk-core/sdk-core-protos/src/lib.rs +175 -57
- data/bridge/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
- data/bridge/sdk-core/test-utils/Cargo.toml +3 -1
- data/bridge/sdk-core/test-utils/src/canned_histories.rs +106 -296
- data/bridge/sdk-core/test-utils/src/histfetch.rs +1 -1
- data/bridge/sdk-core/test-utils/src/lib.rs +82 -23
- data/bridge/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
- data/bridge/sdk-core/test-utils/src/workflows.rs +29 -0
- data/bridge/sdk-core/tests/fuzzy_workflow.rs +130 -0
- data/bridge/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
- data/bridge/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
- data/bridge/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
- data/bridge/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
- data/bridge/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
- data/bridge/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
- data/bridge/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -69
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +72 -191
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +1 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/patches.rs +7 -28
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/replay.rs +12 -7
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
- data/bridge/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -4
- data/bridge/sdk-core/tests/integ_tests/workflow_tests.rs +10 -11
- data/bridge/sdk-core/tests/main.rs +3 -13
- data/bridge/sdk-core/tests/runner.rs +75 -36
- data/bridge/sdk-core/tests/wf_input_replay.rs +32 -0
- data/bridge/src/connection.rs +41 -25
- data/bridge/src/lib.rs +269 -14
- data/bridge/src/runtime.rs +1 -1
- data/bridge/src/test_server.rs +153 -0
- data/bridge/src/worker.rs +89 -16
- data/lib/gen/temporal/api/command/v1/message_pb.rb +4 -18
- data/lib/gen/temporal/api/common/v1/message_pb.rb +4 -0
- data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +1 -3
- data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +3 -3
- data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +2 -0
- data/lib/gen/temporal/api/enums/v1/update_pb.rb +6 -4
- data/lib/gen/temporal/api/history/v1/message_pb.rb +27 -19
- data/lib/gen/temporal/api/namespace/v1/message_pb.rb +1 -0
- data/lib/gen/temporal/api/operatorservice/v1/request_response_pb.rb +3 -0
- data/lib/gen/temporal/api/protocol/v1/message_pb.rb +30 -0
- data/lib/gen/temporal/api/sdk/v1/task_complete_metadata_pb.rb +23 -0
- data/lib/gen/temporal/api/testservice/v1/request_response_pb.rb +49 -0
- data/lib/gen/temporal/api/testservice/v1/service_pb.rb +21 -0
- data/lib/gen/temporal/api/update/v1/message_pb.rb +72 -0
- data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +26 -16
- data/lib/gen/temporal/sdk/core/activity_result/activity_result_pb.rb +13 -9
- data/lib/gen/temporal/sdk/core/activity_task/activity_task_pb.rb +10 -6
- data/lib/gen/temporal/sdk/core/child_workflow/child_workflow_pb.rb +13 -9
- data/lib/gen/temporal/sdk/core/common/common_pb.rb +7 -3
- data/lib/gen/temporal/sdk/core/core_interface_pb.rb +9 -3
- data/lib/gen/temporal/sdk/core/external_data/external_data_pb.rb +7 -3
- data/lib/gen/temporal/sdk/core/workflow_activation/workflow_activation_pb.rb +27 -21
- data/lib/gen/temporal/sdk/core/workflow_commands/workflow_commands_pb.rb +28 -24
- data/lib/gen/temporal/sdk/core/workflow_completion/workflow_completion_pb.rb +12 -5
- data/lib/temporalio/activity/context.rb +13 -8
- data/lib/temporalio/activity/info.rb +1 -1
- data/lib/temporalio/bridge/connect_options.rb +15 -0
- data/lib/temporalio/bridge/retry_config.rb +24 -0
- data/lib/temporalio/bridge/tls_options.rb +19 -0
- data/lib/temporalio/client/implementation.rb +8 -8
- data/lib/temporalio/connection/retry_config.rb +44 -0
- data/lib/temporalio/connection/service.rb +20 -0
- data/lib/temporalio/connection/test_service.rb +92 -0
- data/lib/temporalio/connection/tls_options.rb +51 -0
- data/lib/temporalio/connection/workflow_service.rb +731 -0
- data/lib/temporalio/connection.rb +55 -720
- data/lib/temporalio/interceptor/activity_inbound.rb +22 -0
- data/lib/temporalio/interceptor/activity_outbound.rb +24 -0
- data/lib/temporalio/interceptor/chain.rb +5 -5
- data/lib/temporalio/interceptor/client.rb +8 -4
- data/lib/temporalio/interceptor.rb +22 -0
- data/lib/temporalio/retry_policy.rb +13 -3
- data/lib/temporalio/testing/time_skipping_handle.rb +32 -0
- data/lib/temporalio/testing/time_skipping_interceptor.rb +23 -0
- data/lib/temporalio/testing/workflow_environment.rb +112 -0
- data/lib/temporalio/testing.rb +175 -0
- data/lib/temporalio/version.rb +1 -1
- data/lib/temporalio/worker/activity_runner.rb +26 -4
- data/lib/temporalio/worker/activity_worker.rb +44 -18
- data/lib/temporalio/worker/sync_worker.rb +47 -11
- data/lib/temporalio/worker.rb +27 -21
- data/lib/temporalio/workflow/async.rb +46 -0
- data/lib/temporalio/workflow/future.rb +138 -0
- data/lib/temporalio/workflow/info.rb +76 -0
- data/temporalio.gemspec +4 -3
- metadata +67 -17
- data/bridge/sdk-core/Cargo.lock +0 -2606
- data/bridge/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
- data/lib/bridge.so +0 -0
- data/lib/gen/temporal/api/enums/v1/interaction_type_pb.rb +0 -25
- data/lib/gen/temporal/api/interaction/v1/message_pb.rb +0 -49
- data/lib/gen/temporal/sdk/core/bridge/bridge_pb.rb +0 -222
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
use crate::telemetry::default_resource;
|
|
2
2
|
use hyper::{
|
|
3
3
|
header::CONTENT_TYPE,
|
|
4
|
+
server::conn::AddrIncoming,
|
|
4
5
|
service::{make_service_fn, service_fn},
|
|
5
6
|
Body, Method, Request, Response, Server,
|
|
6
7
|
};
|
|
7
|
-
use opentelemetry::{
|
|
8
|
-
metrics::
|
|
9
|
-
|
|
10
|
-
export::metrics::{aggregation::TemporalitySelector, AggregatorSelector},
|
|
11
|
-
metrics::{controllers, processors},
|
|
12
|
-
},
|
|
8
|
+
use opentelemetry::sdk::{
|
|
9
|
+
export::metrics::{aggregation::TemporalitySelector, AggregatorSelector},
|
|
10
|
+
metrics::{controllers, processors},
|
|
13
11
|
};
|
|
14
12
|
use opentelemetry_prometheus::{ExporterBuilder, PrometheusExporter};
|
|
15
13
|
use prometheus::{Encoder, TextEncoder};
|
|
16
|
-
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
|
14
|
+
use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
|
|
17
15
|
|
|
18
16
|
/// Exposes prometheus metrics for scraping
|
|
19
17
|
pub(super) struct PromServer {
|
|
20
|
-
|
|
18
|
+
bound_addr: AddrIncoming,
|
|
21
19
|
pub exporter: Arc<PrometheusExporter>,
|
|
22
20
|
}
|
|
23
21
|
|
|
@@ -26,19 +24,22 @@ impl PromServer {
|
|
|
26
24
|
addr: SocketAddr,
|
|
27
25
|
aggregation: impl AggregatorSelector + Send + Sync + 'static,
|
|
28
26
|
temporality: impl TemporalitySelector + Send + Sync + 'static,
|
|
29
|
-
) -> Result<Self,
|
|
27
|
+
) -> Result<Self, anyhow::Error> {
|
|
30
28
|
let controller =
|
|
31
29
|
controllers::basic(processors::factory(aggregation, temporality).with_memory(true))
|
|
30
|
+
// Because Prom is pull-based, make this always refresh
|
|
31
|
+
.with_collect_period(Duration::from_secs(0))
|
|
32
32
|
.with_resource(default_resource())
|
|
33
33
|
.build();
|
|
34
34
|
let exporter = ExporterBuilder::new(controller).try_init()?;
|
|
35
|
+
let bound_addr = AddrIncoming::bind(&addr)?;
|
|
35
36
|
Ok(Self {
|
|
36
37
|
exporter: Arc::new(exporter),
|
|
37
|
-
|
|
38
|
+
bound_addr,
|
|
38
39
|
})
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
pub async fn run(
|
|
42
|
+
pub async fn run(self) -> hyper::Result<()> {
|
|
42
43
|
// Spin up hyper server to serve metrics for scraping. We use hyper since we already depend
|
|
43
44
|
// on it via Tonic.
|
|
44
45
|
let expclone = self.exporter.clone();
|
|
@@ -46,9 +47,13 @@ impl PromServer {
|
|
|
46
47
|
let expclone = expclone.clone();
|
|
47
48
|
async move { Ok::<_, Infallible>(service_fn(move |req| metrics_req(req, expclone.clone()))) }
|
|
48
49
|
});
|
|
49
|
-
let server = Server::
|
|
50
|
+
let server = Server::builder(self.bound_addr).serve(svc);
|
|
50
51
|
server.await
|
|
51
52
|
}
|
|
53
|
+
|
|
54
|
+
pub fn bound_addr(&self) -> SocketAddr {
|
|
55
|
+
self.bound_addr.local_addr()
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/// Serves prometheus metrics in the expected format for scraping
|
|
@@ -14,6 +14,7 @@ use crate::{
|
|
|
14
14
|
},
|
|
15
15
|
TaskToken, Worker, WorkerConfig, WorkerConfigBuilder,
|
|
16
16
|
};
|
|
17
|
+
use async_trait::async_trait;
|
|
17
18
|
use bimap::BiMap;
|
|
18
19
|
use futures::{future::BoxFuture, stream, stream::BoxStream, FutureExt, Stream, StreamExt};
|
|
19
20
|
use mockall::TimesRange;
|
|
@@ -29,6 +30,7 @@ use std::{
|
|
|
29
30
|
task::{Context, Poll},
|
|
30
31
|
time::Duration,
|
|
31
32
|
};
|
|
33
|
+
use temporal_sdk_core_api::errors::{PollActivityError, PollWfError};
|
|
32
34
|
use temporal_sdk_core_api::Worker as WorkerTrait;
|
|
33
35
|
use temporal_sdk_core_protos::{
|
|
34
36
|
coresdk::{
|
|
@@ -52,7 +54,6 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
|
52
54
|
use tokio_util::sync::CancellationToken;
|
|
53
55
|
|
|
54
56
|
pub const TEST_Q: &str = "q";
|
|
55
|
-
pub static NO_MORE_WORK_ERROR_MSG: &str = "No more work to do";
|
|
56
57
|
|
|
57
58
|
pub fn test_worker_cfg() -> WorkerConfigBuilder {
|
|
58
59
|
let mut wcb = WorkerConfigBuilder::default();
|
|
@@ -148,6 +149,7 @@ pub(crate) fn mock_worker(mocks: MocksHolder) -> Worker {
|
|
|
148
149
|
mocks.inputs.wft_stream,
|
|
149
150
|
act_poller,
|
|
150
151
|
MetricsContext::no_op(),
|
|
152
|
+
None,
|
|
151
153
|
CancellationToken::new(),
|
|
152
154
|
)
|
|
153
155
|
}
|
|
@@ -301,7 +303,7 @@ where
|
|
|
301
303
|
}
|
|
302
304
|
.boxed()
|
|
303
305
|
} else {
|
|
304
|
-
async {
|
|
306
|
+
async { None }.boxed()
|
|
305
307
|
}
|
|
306
308
|
});
|
|
307
309
|
Box::new(mock_poller) as BoxedPoller<T>
|
|
@@ -375,6 +377,7 @@ pub(crate) struct MockPollCfg {
|
|
|
375
377
|
pub expect_fail_wft_matcher:
|
|
376
378
|
Box<dyn Fn(&TaskToken, &WorkflowTaskFailedCause, &Option<Failure>) -> bool + Send>,
|
|
377
379
|
pub completion_asserts: Option<Box<dyn Fn(&WorkflowTaskCompletion) + Send>>,
|
|
380
|
+
pub num_expected_completions: Option<TimesRange>,
|
|
378
381
|
/// If being used with the Rust SDK, this is set true. It ensures pollers will not error out
|
|
379
382
|
/// early with no work, since we cannot know the exact number of times polling will happen.
|
|
380
383
|
/// Instead, they will just block forever.
|
|
@@ -396,6 +399,7 @@ impl MockPollCfg {
|
|
|
396
399
|
mock_client: mock_workflow_client(),
|
|
397
400
|
expect_fail_wft_matcher: Box::new(|_, _, _| true),
|
|
398
401
|
completion_asserts: None,
|
|
402
|
+
num_expected_completions: None,
|
|
399
403
|
using_rust_sdk: false,
|
|
400
404
|
make_poll_stream_interminable: false,
|
|
401
405
|
}
|
|
@@ -418,6 +422,7 @@ impl MockPollCfg {
|
|
|
418
422
|
mock_client,
|
|
419
423
|
expect_fail_wft_matcher: Box::new(|_, _, _| true),
|
|
420
424
|
completion_asserts: None,
|
|
425
|
+
num_expected_completions: None,
|
|
421
426
|
using_rust_sdk: false,
|
|
422
427
|
make_poll_stream_interminable: false,
|
|
423
428
|
}
|
|
@@ -587,16 +592,20 @@ pub(crate) fn build_mock_pollers(mut cfg: MockPollCfg) -> MocksHolder {
|
|
|
587
592
|
);
|
|
588
593
|
|
|
589
594
|
let outstanding = outstanding_wf_task_tokens.clone();
|
|
590
|
-
cfg.mock_client
|
|
591
|
-
|
|
592
|
-
.
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
595
|
+
let expect_completes = cfg.mock_client.expect_complete_workflow_task();
|
|
596
|
+
if let Some(range) = cfg.num_expected_completions {
|
|
597
|
+
expect_completes.times(range);
|
|
598
|
+
} else if cfg.completion_asserts.is_some() {
|
|
599
|
+
expect_completes.times(1..);
|
|
600
|
+
}
|
|
601
|
+
expect_completes.returning(move |comp| {
|
|
602
|
+
if let Some(ass) = cfg.completion_asserts.as_ref() {
|
|
603
|
+
// tee hee
|
|
604
|
+
ass(&comp)
|
|
605
|
+
}
|
|
606
|
+
outstanding.release_token(&comp.task_token);
|
|
607
|
+
Ok(RespondWorkflowTaskCompletedResponse::default())
|
|
608
|
+
});
|
|
600
609
|
let outstanding = outstanding_wf_task_tokens.clone();
|
|
601
610
|
cfg.mock_client
|
|
602
611
|
.expect_fail_workflow_task()
|
|
@@ -842,6 +851,7 @@ pub(crate) fn gen_assert_and_fail(asserter: &dyn Fn(&WorkflowActivation)) -> Ass
|
|
|
842
851
|
message: "Intentional test failure".to_string(),
|
|
843
852
|
..Default::default()
|
|
844
853
|
}),
|
|
854
|
+
..Default::default()
|
|
845
855
|
}
|
|
846
856
|
.into(),
|
|
847
857
|
)
|
|
@@ -887,3 +897,43 @@ macro_rules! prost_dur {
|
|
|
887
897
|
.expect("test duration fits")
|
|
888
898
|
};
|
|
889
899
|
}
|
|
900
|
+
|
|
901
|
+
#[async_trait]
|
|
902
|
+
pub(crate) trait WorkerExt {
|
|
903
|
+
/// Initiate shutdown, drain the pollers, and wait for shutdown to complete.
|
|
904
|
+
async fn drain_pollers_and_shutdown(self);
|
|
905
|
+
/// Initiate shutdown, drain the *activity* poller, and wait for shutdown to complete.
|
|
906
|
+
/// Takes a ref because of that one test that needs it.
|
|
907
|
+
async fn drain_activity_poller_and_shutdown(&self);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
#[async_trait]
|
|
911
|
+
impl WorkerExt for Worker {
|
|
912
|
+
async fn drain_pollers_and_shutdown(self) {
|
|
913
|
+
self.initiate_shutdown();
|
|
914
|
+
tokio::join!(
|
|
915
|
+
async {
|
|
916
|
+
assert_matches!(
|
|
917
|
+
self.poll_activity_task().await.unwrap_err(),
|
|
918
|
+
PollActivityError::ShutDown
|
|
919
|
+
);
|
|
920
|
+
},
|
|
921
|
+
async {
|
|
922
|
+
assert_matches!(
|
|
923
|
+
self.poll_workflow_activation().await.unwrap_err(),
|
|
924
|
+
PollWfError::ShutDown
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
);
|
|
928
|
+
self.finalize_shutdown().await;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async fn drain_activity_poller_and_shutdown(&self) {
|
|
932
|
+
self.initiate_shutdown();
|
|
933
|
+
assert_matches!(
|
|
934
|
+
self.poll_activity_task().await.unwrap_err(),
|
|
935
|
+
PollActivityError::ShutDown
|
|
936
|
+
);
|
|
937
|
+
self.shutdown().await;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
@@ -26,9 +26,6 @@ use tokio_util::sync::CancellationToken;
|
|
|
26
26
|
/// Used to supply new heartbeat events to the activity heartbeat manager, or to send a shutdown
|
|
27
27
|
/// request.
|
|
28
28
|
pub(crate) struct ActivityHeartbeatManager {
|
|
29
|
-
/// Cancellations that have been received when heartbeating are queued here and can be consumed
|
|
30
|
-
/// by [fetch_cancellations]
|
|
31
|
-
incoming_cancels: Mutex<UnboundedReceiver<PendingActivityCancel>>,
|
|
32
29
|
shutdown_token: CancellationToken,
|
|
33
30
|
/// Used during `shutdown` to await until all inflight requests are sent.
|
|
34
31
|
join_handle: Mutex<Option<JoinHandle<()>>>,
|
|
@@ -74,15 +71,118 @@ pub enum ActivityHeartbeatError {
|
|
|
74
71
|
/// to heartbeat.
|
|
75
72
|
#[error("Unable to parse activity heartbeat timeout.")]
|
|
76
73
|
InvalidHeartbeatTimeout,
|
|
77
|
-
/// Core is shutting down and thus new heartbeats are not accepted
|
|
78
|
-
#[error("New heartbeat requests are not accepted while shutting down")]
|
|
79
|
-
ShuttingDown,
|
|
80
74
|
}
|
|
81
75
|
|
|
82
76
|
/// Manages activity heartbeating for a worker. Allows sending new heartbeats or requesting and
|
|
83
77
|
/// awaiting for the shutdown. When shutdown is requested, signal gets sent to all processors, which
|
|
84
78
|
/// allows them to complete gracefully.
|
|
85
79
|
impl ActivityHeartbeatManager {
|
|
80
|
+
/// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
|
|
81
|
+
/// which allows to send new heartbeats and initiate the shutdown.
|
|
82
|
+
/// Returns the manager and a channel that buffers cancellation notifications to be sent to Lang.
|
|
83
|
+
pub(super) fn new(
|
|
84
|
+
client: Arc<dyn WorkerClient>,
|
|
85
|
+
) -> (Self, UnboundedReceiver<PendingActivityCancel>) {
|
|
86
|
+
let (heartbeat_stream_state, heartbeat_tx_source, shutdown_token) =
|
|
87
|
+
HeartbeatStreamState::new();
|
|
88
|
+
let (cancels_tx, cancels_rx) = unbounded_channel();
|
|
89
|
+
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
90
|
+
|
|
91
|
+
let join_handle = tokio::spawn(
|
|
92
|
+
// The stream of incoming heartbeats uses unfold to carry state across each item in the
|
|
93
|
+
// stream. The closure checks if, for any given activity, we should heartbeat or not
|
|
94
|
+
// depending on its delay and when we last issued a heartbeat for it.
|
|
95
|
+
futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
|
|
96
|
+
async move {
|
|
97
|
+
let hb = tokio::select! {
|
|
98
|
+
biased;
|
|
99
|
+
|
|
100
|
+
_ = hb_states.cancellation_token.cancelled() => {
|
|
101
|
+
return None
|
|
102
|
+
}
|
|
103
|
+
hb = hb_states.incoming_hbs.recv() => match hb {
|
|
104
|
+
None => return None,
|
|
105
|
+
Some(hb) => hb,
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Some((
|
|
110
|
+
match hb {
|
|
111
|
+
HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
|
|
112
|
+
HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
|
|
113
|
+
HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
|
|
114
|
+
HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
|
|
115
|
+
},
|
|
116
|
+
hb_states,
|
|
117
|
+
))
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
// Filters out `None`s
|
|
121
|
+
.filter_map(|opt| async { opt })
|
|
122
|
+
.for_each_concurrent(None, move |action| {
|
|
123
|
+
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
124
|
+
let sg = client.clone();
|
|
125
|
+
let cancels_tx = cancels_tx.clone();
|
|
126
|
+
async move {
|
|
127
|
+
match action {
|
|
128
|
+
HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
|
|
129
|
+
tokio::select! {
|
|
130
|
+
_ = cancellation_token.cancelled() => (),
|
|
131
|
+
_ = tokio::time::sleep(duration) => {
|
|
132
|
+
let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
HeartbeatExecutorAction::Report { task_token: tt, details } => {
|
|
137
|
+
match sg
|
|
138
|
+
.record_activity_heartbeat(tt.clone(), details.into_payloads())
|
|
139
|
+
.await
|
|
140
|
+
{
|
|
141
|
+
Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
|
|
142
|
+
if cancel_requested {
|
|
143
|
+
cancels_tx
|
|
144
|
+
.send(PendingActivityCancel::new(
|
|
145
|
+
tt.clone(),
|
|
146
|
+
ActivityCancelReason::Cancelled,
|
|
147
|
+
))
|
|
148
|
+
.expect(
|
|
149
|
+
"Receive half of heartbeat cancels not blocked",
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Send cancels for any activity that learns its workflow already
|
|
154
|
+
// finished (which is one thing not found implies - other reasons
|
|
155
|
+
// would seem equally valid).
|
|
156
|
+
Err(s) if s.code() == tonic::Code::NotFound => {
|
|
157
|
+
debug!(task_token = %tt,
|
|
158
|
+
"Activity not found when recording heartbeat");
|
|
159
|
+
cancels_tx
|
|
160
|
+
.send(PendingActivityCancel::new(
|
|
161
|
+
tt.clone(),
|
|
162
|
+
ActivityCancelReason::NotFound,
|
|
163
|
+
))
|
|
164
|
+
.expect("Receive half of heartbeat cancels not blocked");
|
|
165
|
+
}
|
|
166
|
+
Err(e) => {
|
|
167
|
+
warn!("Error when recording heartbeat: {:?}", e);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}),
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
(
|
|
178
|
+
Self {
|
|
179
|
+
join_handle: Mutex::new(Some(join_handle)),
|
|
180
|
+
shutdown_token,
|
|
181
|
+
heartbeat_tx,
|
|
182
|
+
},
|
|
183
|
+
cancels_rx,
|
|
184
|
+
)
|
|
185
|
+
}
|
|
86
186
|
/// Records a new heartbeat, the first call will result in an immediate call to the server,
|
|
87
187
|
/// while rapid successive calls would accumulate for up to `delay` and then latest heartbeat
|
|
88
188
|
/// details will be sent to the server.
|
|
@@ -95,9 +195,6 @@ impl ActivityHeartbeatManager {
|
|
|
95
195
|
hb: ActivityHeartbeat,
|
|
96
196
|
throttle_interval: Duration,
|
|
97
197
|
) -> Result<(), ActivityHeartbeatError> {
|
|
98
|
-
if self.shutdown_token.is_cancelled() {
|
|
99
|
-
return Err(ActivityHeartbeatError::ShuttingDown);
|
|
100
|
-
}
|
|
101
198
|
self.heartbeat_tx
|
|
102
199
|
.send(HeartbeatAction::SendHeartbeat(ValidActivityHeartbeat {
|
|
103
200
|
task_token: TaskToken(hb.task_token),
|
|
@@ -121,13 +218,6 @@ impl ActivityHeartbeatManager {
|
|
|
121
218
|
completed.notified().await;
|
|
122
219
|
}
|
|
123
220
|
|
|
124
|
-
/// Returns a future that resolves any time there is a new activity cancel that must be
|
|
125
|
-
/// dispatched to lang
|
|
126
|
-
pub(super) async fn next_pending_cancel(&self) -> Option<PendingActivityCancel> {
|
|
127
|
-
self.incoming_cancels.lock().await.recv().await
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// TODO: Can own self now!
|
|
131
221
|
/// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all in-flight
|
|
132
222
|
/// heartbeat requests to be flushed to the server.
|
|
133
223
|
pub(super) async fn shutdown(&self) {
|
|
@@ -301,110 +391,6 @@ impl HeartbeatStreamState {
|
|
|
301
391
|
}
|
|
302
392
|
}
|
|
303
393
|
|
|
304
|
-
impl ActivityHeartbeatManager {
|
|
305
|
-
/// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
|
|
306
|
-
/// which allows to send new heartbeats and initiate the shutdown.
|
|
307
|
-
pub fn new(client: Arc<dyn WorkerClient>) -> Self {
|
|
308
|
-
let (heartbeat_stream_state, heartbeat_tx_source, shutdown_token) =
|
|
309
|
-
HeartbeatStreamState::new();
|
|
310
|
-
let (cancels_tx, cancels_rx) = unbounded_channel();
|
|
311
|
-
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
312
|
-
|
|
313
|
-
let join_handle = tokio::spawn(
|
|
314
|
-
// The stream of incoming heartbeats uses unfold to carry state across each item in the
|
|
315
|
-
// stream. The closure checks if, for any given activity, we should heartbeat or not
|
|
316
|
-
// depending on its delay and when we last issued a heartbeat for it.
|
|
317
|
-
futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
|
|
318
|
-
async move {
|
|
319
|
-
let hb = tokio::select! {
|
|
320
|
-
biased;
|
|
321
|
-
|
|
322
|
-
_ = hb_states.cancellation_token.cancelled() => {
|
|
323
|
-
return None
|
|
324
|
-
}
|
|
325
|
-
hb = hb_states.incoming_hbs.recv() => match hb {
|
|
326
|
-
None => return None,
|
|
327
|
-
Some(hb) => hb,
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
Some((
|
|
332
|
-
match hb {
|
|
333
|
-
HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
|
|
334
|
-
HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
|
|
335
|
-
HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
|
|
336
|
-
HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
|
|
337
|
-
},
|
|
338
|
-
hb_states,
|
|
339
|
-
))
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
// Filters out `None`s
|
|
343
|
-
.filter_map(|opt| async { opt })
|
|
344
|
-
.for_each_concurrent(None, move |action| {
|
|
345
|
-
let heartbeat_tx = heartbeat_tx_source.clone();
|
|
346
|
-
let sg = client.clone();
|
|
347
|
-
let cancels_tx = cancels_tx.clone();
|
|
348
|
-
async move {
|
|
349
|
-
match action {
|
|
350
|
-
HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
|
|
351
|
-
tokio::select! {
|
|
352
|
-
_ = cancellation_token.cancelled() => (),
|
|
353
|
-
_ = tokio::time::sleep(duration) => {
|
|
354
|
-
let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
|
|
355
|
-
},
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
HeartbeatExecutorAction::Report { task_token: tt, details } => {
|
|
359
|
-
match sg
|
|
360
|
-
.record_activity_heartbeat(tt.clone(), details.into_payloads())
|
|
361
|
-
.await
|
|
362
|
-
{
|
|
363
|
-
Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
|
|
364
|
-
if cancel_requested {
|
|
365
|
-
cancels_tx
|
|
366
|
-
.send(PendingActivityCancel::new(
|
|
367
|
-
tt.clone(),
|
|
368
|
-
ActivityCancelReason::Cancelled,
|
|
369
|
-
))
|
|
370
|
-
.expect(
|
|
371
|
-
"Receive half of heartbeat cancels not blocked",
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// Send cancels for any activity that learns its workflow already
|
|
376
|
-
// finished (which is one thing not found implies - other reasons
|
|
377
|
-
// would seem equally valid).
|
|
378
|
-
Err(s) if s.code() == tonic::Code::NotFound => {
|
|
379
|
-
debug!(task_token = %tt,
|
|
380
|
-
"Activity not found when recording heartbeat");
|
|
381
|
-
cancels_tx
|
|
382
|
-
.send(PendingActivityCancel::new(
|
|
383
|
-
tt.clone(),
|
|
384
|
-
ActivityCancelReason::NotFound,
|
|
385
|
-
))
|
|
386
|
-
.expect("Receive half of heartbeat cancels not blocked");
|
|
387
|
-
}
|
|
388
|
-
Err(e) => {
|
|
389
|
-
warn!("Error when recording heartbeat: {:?}", e);
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}),
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
Self {
|
|
400
|
-
incoming_cancels: Mutex::new(cancels_rx),
|
|
401
|
-
join_handle: Mutex::new(Some(join_handle)),
|
|
402
|
-
shutdown_token,
|
|
403
|
-
heartbeat_tx,
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
394
|
#[cfg(test)]
|
|
409
395
|
mod test {
|
|
410
396
|
use super::*;
|
|
@@ -425,7 +411,7 @@ mod test {
|
|
|
425
411
|
.expect_record_activity_heartbeat()
|
|
426
412
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
427
413
|
.times(2);
|
|
428
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
414
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
429
415
|
let fake_task_token = vec![1, 2, 3];
|
|
430
416
|
// Send 2 heartbeat requests for 20ms apart.
|
|
431
417
|
// The first heartbeat should be sent right away, and
|
|
@@ -446,14 +432,13 @@ mod test {
|
|
|
446
432
|
.expect_record_activity_heartbeat()
|
|
447
433
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
448
434
|
.times(3);
|
|
449
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
435
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
450
436
|
let fake_task_token = vec![1, 2, 3];
|
|
451
437
|
// Heartbeats always get sent if recorded less frequently than the throttle interval
|
|
452
438
|
for i in 0_u8..3 {
|
|
453
439
|
record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(10));
|
|
454
440
|
sleep(Duration::from_millis(20)).await;
|
|
455
441
|
}
|
|
456
|
-
// sleep again to let heartbeats be flushed
|
|
457
442
|
hm.shutdown().await;
|
|
458
443
|
}
|
|
459
444
|
|
|
@@ -466,7 +451,7 @@ mod test {
|
|
|
466
451
|
.expect_record_activity_heartbeat()
|
|
467
452
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
468
453
|
.times(1);
|
|
469
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
454
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
470
455
|
let fake_task_token = vec![1, 2, 3];
|
|
471
456
|
// Send a whole bunch of heartbeats very fast. We should still only send one total.
|
|
472
457
|
for i in 0_u8..50 {
|
|
@@ -485,7 +470,7 @@ mod test {
|
|
|
485
470
|
.expect_record_activity_heartbeat()
|
|
486
471
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
487
472
|
.times(2);
|
|
488
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
473
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
489
474
|
let fake_task_token = vec![1, 2, 3];
|
|
490
475
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
491
476
|
sleep(Duration::from_millis(500)).await;
|
|
@@ -502,7 +487,7 @@ mod test {
|
|
|
502
487
|
.expect_record_activity_heartbeat()
|
|
503
488
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
504
489
|
.times(2);
|
|
505
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
490
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
506
491
|
let fake_task_token = vec![1, 2, 3];
|
|
507
492
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
508
493
|
// Let it propagate
|
|
@@ -522,42 +507,13 @@ mod test {
|
|
|
522
507
|
.expect_record_activity_heartbeat()
|
|
523
508
|
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
524
509
|
.times(1);
|
|
525
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
510
|
+
let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
526
511
|
let fake_task_token = vec![1, 2, 3];
|
|
527
512
|
record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
|
|
528
513
|
hm.evict(fake_task_token.clone().into()).await;
|
|
529
514
|
hm.shutdown().await;
|
|
530
515
|
}
|
|
531
516
|
|
|
532
|
-
/// Recording new heartbeats after shutdown is not allowed, and will result in error.
|
|
533
|
-
#[tokio::test]
|
|
534
|
-
async fn record_after_shutdown() {
|
|
535
|
-
let mut mock_client = mock_workflow_client();
|
|
536
|
-
mock_client
|
|
537
|
-
.expect_record_activity_heartbeat()
|
|
538
|
-
.returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
|
|
539
|
-
.times(0);
|
|
540
|
-
let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
|
|
541
|
-
hm.shutdown().await;
|
|
542
|
-
match hm.record(
|
|
543
|
-
ActivityHeartbeat {
|
|
544
|
-
task_token: vec![1, 2, 3],
|
|
545
|
-
details: vec![Payload {
|
|
546
|
-
// payload doesn't matter in this case, as it shouldn't get sent anyways.
|
|
547
|
-
..Default::default()
|
|
548
|
-
}],
|
|
549
|
-
},
|
|
550
|
-
Duration::from_millis(1000),
|
|
551
|
-
) {
|
|
552
|
-
Ok(_) => {
|
|
553
|
-
unreachable!("heartbeat should not be recorded after the shutdown");
|
|
554
|
-
}
|
|
555
|
-
Err(e) => {
|
|
556
|
-
matches!(e, ActivityHeartbeatError::ShuttingDown);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
517
|
fn record_heartbeat(
|
|
562
518
|
hm: &ActivityHeartbeatManager,
|
|
563
519
|
task_token: Vec<u8>,
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
use crate::abstractions::MeteredSemaphore;
|
|
2
|
+
use crate::worker::activities::PermittedTqResp;
|
|
3
|
+
use crate::{pollers::BoxedActPoller, MetricsContext};
|
|
4
|
+
use futures::{stream, Stream};
|
|
5
|
+
use governor::clock::DefaultClock;
|
|
6
|
+
use governor::middleware::NoOpMiddleware;
|
|
7
|
+
use governor::state::{InMemoryState, NotKeyed};
|
|
8
|
+
use governor::RateLimiter;
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
use temporal_sdk_core_protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse;
|
|
11
|
+
use tokio::select;
|
|
12
|
+
use tokio_util::sync::CancellationToken;
|
|
13
|
+
|
|
14
|
+
struct StreamState {
|
|
15
|
+
poller: BoxedActPoller,
|
|
16
|
+
semaphore: Arc<MeteredSemaphore>,
|
|
17
|
+
rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
|
|
18
|
+
metrics: MetricsContext,
|
|
19
|
+
shutdown_token: CancellationToken,
|
|
20
|
+
poller_was_shutdown: bool,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub(crate) fn new_activity_task_poller(
|
|
24
|
+
poller: BoxedActPoller,
|
|
25
|
+
semaphore: Arc<MeteredSemaphore>,
|
|
26
|
+
rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
|
|
27
|
+
metrics: MetricsContext,
|
|
28
|
+
shutdown_token: CancellationToken,
|
|
29
|
+
) -> impl Stream<Item = Result<PermittedTqResp, tonic::Status>> {
|
|
30
|
+
let state = StreamState {
|
|
31
|
+
poller,
|
|
32
|
+
semaphore,
|
|
33
|
+
rate_limiter,
|
|
34
|
+
metrics,
|
|
35
|
+
shutdown_token,
|
|
36
|
+
poller_was_shutdown: false,
|
|
37
|
+
};
|
|
38
|
+
stream::unfold(state, |mut state| async move {
|
|
39
|
+
loop {
|
|
40
|
+
let poll = async {
|
|
41
|
+
let permit = state
|
|
42
|
+
.semaphore
|
|
43
|
+
.acquire_owned()
|
|
44
|
+
.await
|
|
45
|
+
.expect("outstanding activity semaphore not closed");
|
|
46
|
+
if !state.poller_was_shutdown {
|
|
47
|
+
if let Some(ref rl) = state.rate_limiter {
|
|
48
|
+
rl.until_ready().await;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
loop {
|
|
52
|
+
return match state.poller.poll().await {
|
|
53
|
+
Some(Ok(resp)) => {
|
|
54
|
+
if resp == PollActivityTaskQueueResponse::default() {
|
|
55
|
+
// We get the default proto in the event that the long poll times out.
|
|
56
|
+
debug!("Poll activity task timeout");
|
|
57
|
+
state.metrics.act_poll_timeout();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
Some(Ok(PermittedTqResp { permit, resp }))
|
|
61
|
+
}
|
|
62
|
+
Some(Err(e)) => {
|
|
63
|
+
warn!(error=?e, "Error while polling for activity tasks");
|
|
64
|
+
Some(Err(e))
|
|
65
|
+
}
|
|
66
|
+
// If poller returns None, it's dead, thus we also return None to terminate this
|
|
67
|
+
// stream.
|
|
68
|
+
None => None,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if state.poller_was_shutdown {
|
|
73
|
+
return poll.await.map(|res| (res, state));
|
|
74
|
+
}
|
|
75
|
+
select! {
|
|
76
|
+
biased;
|
|
77
|
+
|
|
78
|
+
_ = state.shutdown_token.cancelled() => {
|
|
79
|
+
state.poller.notify_shutdown();
|
|
80
|
+
state.poller_was_shutdown = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
res = poll => {
|
|
84
|
+
return res.map(|res| (res, state));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
}
|