@temporalio/core-bridge 1.6.0 → 1.7.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 +83 -98
- package/lib/index.d.ts +8 -6
- package/lib/index.js.map +1 -1
- package/package.json +8 -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/.buildkite/docker/Dockerfile +2 -2
- package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
- package/sdk-core/.buildkite/pipeline.yml +1 -1
- package/sdk-core/.github/workflows/heavy.yml +1 -0
- package/sdk-core/README.md +13 -7
- package/sdk-core/client/src/lib.rs +4 -3
- package/sdk-core/client/src/metrics.rs +17 -8
- package/sdk-core/client/src/raw.rs +3 -3
- package/sdk-core/core/Cargo.toml +3 -4
- package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
- package/sdk-core/core/src/abstractions.rs +197 -18
- package/sdk-core/core/src/core_tests/activity_tasks.rs +137 -45
- package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
- package/sdk-core/core/src/core_tests/determinism.rs +165 -2
- package/sdk-core/core/src/core_tests/local_activities.rs +183 -36
- package/sdk-core/core/src/core_tests/queries.rs +32 -14
- package/sdk-core/core/src/core_tests/workers.rs +8 -5
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +340 -51
- package/sdk-core/core/src/ephemeral_server/mod.rs +110 -8
- package/sdk-core/core/src/internal_flags.rs +155 -0
- package/sdk-core/core/src/lib.rs +14 -9
- package/sdk-core/core/src/replay/mod.rs +16 -27
- package/sdk-core/core/src/telemetry/metrics.rs +69 -35
- package/sdk-core/core/src/telemetry/mod.rs +38 -14
- package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
- package/sdk-core/core/src/test_help/mod.rs +65 -13
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
- package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
- package/sdk-core/core/src/worker/activities/local_activities.rs +122 -6
- package/sdk-core/core/src/worker/activities.rs +347 -173
- package/sdk-core/core/src/worker/client/mocks.rs +22 -2
- package/sdk-core/core/src/worker/client.rs +18 -2
- package/sdk-core/core/src/worker/mod.rs +136 -44
- package/sdk-core/core/src/worker/workflow/history_update.rs +132 -51
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +207 -166
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +6 -7
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -7
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +157 -82
- package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +12 -12
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -7
- package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +13 -15
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +170 -60
- package/sdk-core/core/src/worker/workflow/machines/mod.rs +24 -16
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +6 -8
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +320 -204
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +10 -13
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +15 -23
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +256 -50
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +240 -111
- package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +13 -13
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +10 -6
- package/sdk-core/core/src/worker/workflow/managed_run.rs +71 -60
- package/sdk-core/core/src/worker/workflow/mod.rs +321 -73
- package/sdk-core/core/src/worker/workflow/run_cache.rs +18 -11
- package/sdk-core/core/src/worker/workflow/wft_extraction.rs +15 -3
- package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +2 -0
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +46 -25
- package/sdk-core/core-api/Cargo.toml +0 -1
- package/sdk-core/core-api/src/lib.rs +13 -7
- package/sdk-core/core-api/src/telemetry.rs +4 -6
- package/sdk-core/core-api/src/worker.rs +5 -0
- package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +80 -55
- package/sdk-core/fsm/rustfsm_trait/src/lib.rs +22 -68
- package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
- package/sdk-core/histories/old_change_marker_format.bin +0 -0
- package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
- package/sdk-core/protos/api_upstream/Makefile +1 -1
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
- package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
- package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
- package/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
- package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
- package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
- package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
- package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
- package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
- package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
- package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +65 -60
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
- package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
- package/sdk-core/sdk/Cargo.toml +1 -1
- package/sdk-core/sdk/src/lib.rs +21 -5
- package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
- package/sdk-core/sdk/src/workflow_context.rs +24 -17
- package/sdk-core/sdk/src/workflow_future.rs +9 -3
- package/sdk-core/sdk-core-protos/src/history_builder.rs +114 -89
- package/sdk-core/sdk-core-protos/src/history_info.rs +6 -1
- package/sdk-core/sdk-core-protos/src/lib.rs +205 -64
- package/sdk-core/test-utils/src/canned_histories.rs +106 -296
- package/sdk-core/test-utils/src/lib.rs +32 -5
- package/sdk-core/tests/heavy_tests.rs +10 -43
- package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
- package/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
- package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
- package/sdk-core/tests/integ_tests/polling_tests.rs +3 -8
- package/sdk-core/tests/integ_tests/queries_tests.rs +4 -2
- package/sdk-core/tests/integ_tests/visibility_tests.rs +34 -23
- package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +97 -81
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
- package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +5 -1
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +25 -3
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
- package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +30 -0
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +64 -0
- package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
- package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +4 -0
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -1
- package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +7 -2
- package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -7
- package/sdk-core/tests/integ_tests/workflow_tests.rs +8 -8
- package/sdk-core/tests/main.rs +1 -1
- package/sdk-core/tests/runner.rs +11 -9
- package/src/conversions.rs +14 -8
- package/src/runtime.rs +9 -8
- package/ts/index.ts +8 -6
- package/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
package/sdk-core/README.md
CHANGED
|
@@ -4,20 +4,20 @@ Core SDK that can be used as a base for other Temporal SDKs. It is currently use
|
|
|
4
4
|
|
|
5
5
|
- [TypeScript SDK](https://github.com/temporalio/sdk-typescript/)
|
|
6
6
|
- [Python SDK](https://github.com/temporalio/sdk-python/)
|
|
7
|
+
- [.NET SDK](https://github.com/temporalio/sdk-dotnet/)
|
|
8
|
+
- [Ruby SDK](https://github.com/temporalio/sdk-ruby/)
|
|
7
9
|
|
|
8
10
|
For the reasoning behind the Core SDK, see:
|
|
9
11
|
|
|
10
12
|
- [Why Rust powers Temporal’s new Core SDK](https://temporal.io/blog/why-rust-powers-core-sdk).
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
See the [Architecture](ARCHITECTURE.md) doc for some high-level information.
|
|
15
|
-
|
|
16
|
-
## Dependencies
|
|
17
|
-
* Protobuf compiler
|
|
14
|
+
See the [Architecture](ARCHITECTURE.md) doc for some high-level information about how Core works
|
|
15
|
+
and how language layers interact with it.
|
|
18
16
|
|
|
19
17
|
# Development
|
|
20
18
|
|
|
19
|
+
You will need the `protoc` protobuf compiler installed to build Core.
|
|
20
|
+
|
|
21
21
|
This repo is composed of multiple crates:
|
|
22
22
|
* temporal-sdk-core-protos `./sdk-core-protos` - Holds the generated proto code and extensions
|
|
23
23
|
* temporal-client `./client` - Defines client(s) for interacting with the Temporal gRPC service
|
|
@@ -31,7 +31,6 @@ Visualized (dev dependencies are in blue):
|
|
|
31
31
|
|
|
32
32
|

|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
All the following commands are enforced for each pull request:
|
|
36
35
|
|
|
37
36
|
## Building and testing
|
|
@@ -109,3 +108,10 @@ Any error which is returned from a public interface should be well-typed, and we
|
|
|
109
108
|
Errors returned from things only used in testing are free to use
|
|
110
109
|
[anyhow](https://github.com/dtolnay/anyhow) for less verbosity.
|
|
111
110
|
|
|
111
|
+
|
|
112
|
+
# The Rust "SDK"
|
|
113
|
+
This repo contains a *prototype* Rust sdk in the `sdk/` directory. This SDK should be considered
|
|
114
|
+
pre-alpha in terms of its API surface. Since it's still using Core underneath, it is generally
|
|
115
|
+
functional. We do not currently have any firm plans to productionize this SDK. If you want to write
|
|
116
|
+
workflows and activities in Rust, feel free to use it - but be aware that the API may change at any
|
|
117
|
+
time without warning and we do not provide any support guarantees.
|
|
@@ -13,6 +13,7 @@ mod retry;
|
|
|
13
13
|
mod workflow_handle;
|
|
14
14
|
|
|
15
15
|
pub use crate::retry::{CallType, RetryClient, RETRYABLE_ERROR_CODES};
|
|
16
|
+
pub use metrics::ClientMetricProvider;
|
|
16
17
|
pub use raw::{HealthService, OperatorService, TestService, WorkflowService};
|
|
17
18
|
pub use temporal_sdk_core_protos::temporal::api::{
|
|
18
19
|
enums::v1::ArchivalState,
|
|
@@ -34,7 +35,6 @@ use crate::{
|
|
|
34
35
|
use backoff::{exponential, ExponentialBackoff, SystemClock};
|
|
35
36
|
use http::uri::InvalidUri;
|
|
36
37
|
use once_cell::sync::OnceCell;
|
|
37
|
-
use opentelemetry::metrics::Meter;
|
|
38
38
|
use parking_lot::RwLock;
|
|
39
39
|
use std::{
|
|
40
40
|
collections::HashMap,
|
|
@@ -286,7 +286,7 @@ impl ClientOptions {
|
|
|
286
286
|
pub async fn connect(
|
|
287
287
|
&self,
|
|
288
288
|
namespace: impl Into<String>,
|
|
289
|
-
metrics_meter: Option<&
|
|
289
|
+
metrics_meter: Option<&dyn ClientMetricProvider>,
|
|
290
290
|
headers: Option<Arc<RwLock<HashMap<String, String>>>>,
|
|
291
291
|
) -> Result<RetryClient<Client>, ClientInitError> {
|
|
292
292
|
let client = self
|
|
@@ -304,7 +304,7 @@ impl ClientOptions {
|
|
|
304
304
|
/// See [RetryClient] for more
|
|
305
305
|
pub async fn connect_no_namespace(
|
|
306
306
|
&self,
|
|
307
|
-
metrics_meter: Option<&
|
|
307
|
+
metrics_meter: Option<&dyn ClientMetricProvider>,
|
|
308
308
|
headers: Option<Arc<RwLock<HashMap<String, String>>>>,
|
|
309
309
|
) -> Result<RetryClient<ConfiguredClient<TemporalServiceClientWithMetrics>>, ClientInitError>
|
|
310
310
|
{
|
|
@@ -1077,6 +1077,7 @@ impl WorkflowClientTrait for Client {
|
|
|
1077
1077
|
identity: self.inner.options.identity.clone(),
|
|
1078
1078
|
binary_checksum: self.bound_worker_build_id.clone().unwrap_or_default(),
|
|
1079
1079
|
namespace: self.namespace.clone(),
|
|
1080
|
+
messages: vec![],
|
|
1080
1081
|
};
|
|
1081
1082
|
Ok(self
|
|
1082
1083
|
.wf_svc()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use crate::{AttachMetricLabels, LONG_POLL_METHOD_NAMES};
|
|
2
2
|
use futures::{future::BoxFuture, FutureExt};
|
|
3
3
|
use opentelemetry::{
|
|
4
|
-
metrics::{Counter, Histogram
|
|
4
|
+
metrics::{Counter, Histogram},
|
|
5
5
|
KeyValue,
|
|
6
6
|
};
|
|
7
7
|
use std::{
|
|
@@ -30,18 +30,27 @@ pub struct MetricsContext {
|
|
|
30
30
|
long_svc_request_latency: Histogram<u64>,
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/// Things that can provide metrics for the client implement this. Trait exists to avoid having
|
|
34
|
+
/// to make a whole new lower-level crate just for a tiny shared wrapper around OTel meters.
|
|
35
|
+
pub trait ClientMetricProvider: Send + Sync {
|
|
36
|
+
/// Construct a counter metric
|
|
37
|
+
fn counter(&self, name: &'static str) -> Counter<u64>;
|
|
38
|
+
/// Construct a histogram metric
|
|
39
|
+
fn histogram(&self, name: &'static str) -> Histogram<u64>;
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
impl MetricsContext {
|
|
34
|
-
pub(crate) fn new(kvs: Vec<KeyValue>,
|
|
43
|
+
pub(crate) fn new(kvs: Vec<KeyValue>, metric_provider: &dyn ClientMetricProvider) -> Self {
|
|
35
44
|
Self {
|
|
36
45
|
ctx: opentelemetry::Context::current(),
|
|
37
46
|
kvs: Arc::new(kvs),
|
|
38
47
|
poll_is_long: false,
|
|
39
|
-
svc_request:
|
|
40
|
-
svc_request_failed:
|
|
41
|
-
long_svc_request:
|
|
42
|
-
long_svc_request_failed:
|
|
43
|
-
svc_request_latency:
|
|
44
|
-
long_svc_request_latency:
|
|
48
|
+
svc_request: metric_provider.counter("request"),
|
|
49
|
+
svc_request_failed: metric_provider.counter("request_failure"),
|
|
50
|
+
long_svc_request: metric_provider.counter("long_request"),
|
|
51
|
+
long_svc_request_failed: metric_provider.counter("long_request_failure"),
|
|
52
|
+
svc_request_latency: metric_provider.histogram("request_latency"),
|
|
53
|
+
long_svc_request_latency: metric_provider.histogram("long_request_latency"),
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
|
|
@@ -760,9 +760,9 @@ proxier! {
|
|
|
760
760
|
}
|
|
761
761
|
);
|
|
762
762
|
(
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
763
|
+
update_workflow_execution,
|
|
764
|
+
UpdateWorkflowExecutionRequest,
|
|
765
|
+
UpdateWorkflowExecutionResponse,
|
|
766
766
|
|r| {
|
|
767
767
|
let labels = AttachMetricLabels::namespace(r.get_ref().namespace.clone());
|
|
768
768
|
r.extensions_mut().insert(labels);
|
package/sdk-core/core/Cargo.toml
CHANGED
|
@@ -21,7 +21,6 @@ save_wf_inputs = ["rmp-serde", "temporal-sdk-core-protos/serde_serialize"]
|
|
|
21
21
|
[dependencies]
|
|
22
22
|
anyhow = "1.0"
|
|
23
23
|
arc-swap = "1.3"
|
|
24
|
-
async-channel = "1.6"
|
|
25
24
|
async-trait = "0.1"
|
|
26
25
|
base64 = "0.21"
|
|
27
26
|
crossbeam = "0.8"
|
|
@@ -37,7 +36,7 @@ http = "0.2"
|
|
|
37
36
|
hyper = "0.14"
|
|
38
37
|
itertools = "0.10"
|
|
39
38
|
lazy_static = "1.4"
|
|
40
|
-
lru = "0.
|
|
39
|
+
lru = "0.10"
|
|
41
40
|
mockall = "0.11"
|
|
42
41
|
nix = "0.26"
|
|
43
42
|
once_cell = "1.5"
|
|
@@ -59,7 +58,7 @@ siphasher = "0.3"
|
|
|
59
58
|
slotmap = "1.0"
|
|
60
59
|
tar = "0.4"
|
|
61
60
|
thiserror = "1.0"
|
|
62
|
-
tokio = { version = "1.
|
|
61
|
+
tokio = { version = "1.26", features = ["rt", "rt-multi-thread", "parking_lot", "time", "fs", "process"] }
|
|
63
62
|
tokio-util = { version = "0.7", features = ["io", "io-util"] }
|
|
64
63
|
tokio-stream = "0.1"
|
|
65
64
|
tonic = { version = "0.8", features = ["tls", "tls-roots"] }
|
|
@@ -94,7 +93,7 @@ assert_matches = "1.4"
|
|
|
94
93
|
bimap = "0.6.1"
|
|
95
94
|
clap = { version = "4.0", features = ["derive"] }
|
|
96
95
|
criterion = "0.4"
|
|
97
|
-
rstest = "0.
|
|
96
|
+
rstest = "0.17"
|
|
98
97
|
temporal-sdk-core-test-utils = { path = "../test-utils" }
|
|
99
98
|
temporal-sdk = { path = "../sdk" }
|
|
100
99
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
use parking_lot::Mutex;
|
|
2
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
3
|
+
|
|
4
|
+
/// Implements something a bit like a `OnceCell`, but starts already initialized and allows you
|
|
5
|
+
/// to take everything out of it only once in a thread-safe way. This isn't optimized for super
|
|
6
|
+
/// fast-path usage.
|
|
7
|
+
pub struct TakeCell<T> {
|
|
8
|
+
taken: AtomicBool,
|
|
9
|
+
data: Mutex<Option<T>>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
impl<T> TakeCell<T> {
|
|
13
|
+
pub fn new(val: T) -> Self {
|
|
14
|
+
Self {
|
|
15
|
+
taken: AtomicBool::new(false),
|
|
16
|
+
data: Mutex::new(Some(val)),
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// If the cell has not already been taken from, takes the value and returns it
|
|
21
|
+
pub fn take_once(&self) -> Option<T> {
|
|
22
|
+
if self.taken.load(Ordering::Acquire) {
|
|
23
|
+
return None;
|
|
24
|
+
}
|
|
25
|
+
self.taken.store(true, Ordering::Release);
|
|
26
|
+
self.data.lock().take()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
//! This module contains very generic helpers that can be used codebase-wide
|
|
2
2
|
|
|
3
|
+
pub mod take_cell;
|
|
4
|
+
|
|
3
5
|
use crate::MetricsContext;
|
|
6
|
+
use derive_more::DebugCustom;
|
|
4
7
|
use futures::{stream, Stream, StreamExt};
|
|
5
8
|
use std::{
|
|
6
9
|
fmt::{Debug, Formatter},
|
|
7
|
-
sync::
|
|
10
|
+
sync::{
|
|
11
|
+
atomic::{AtomicBool, AtomicUsize, Ordering},
|
|
12
|
+
Arc,
|
|
13
|
+
},
|
|
8
14
|
};
|
|
9
15
|
use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore, TryAcquireError};
|
|
16
|
+
use tokio_util::sync::CancellationToken;
|
|
10
17
|
|
|
11
18
|
/// Wraps a [Semaphore] with a function call that is fed the available permits any time a permit is
|
|
12
19
|
/// acquired or restored through the provided methods
|
|
13
20
|
#[derive(Clone)]
|
|
14
21
|
pub(crate) struct MeteredSemaphore {
|
|
15
22
|
sem: Arc<Semaphore>,
|
|
23
|
+
/// The number of permit owners who have acquired a permit from the semaphore, but are not yet
|
|
24
|
+
/// meaningfully using that permit. This is useful for giving a more semantically accurate count
|
|
25
|
+
/// of used task slots, since we typically wait for a permit first before polling, but that slot
|
|
26
|
+
/// isn't used in the sense the user expects until we actually also get the corresponding task.
|
|
27
|
+
unused_claimants: Arc<AtomicUsize>,
|
|
16
28
|
metrics_ctx: MetricsContext,
|
|
17
29
|
record_fn: fn(&MetricsContext, usize),
|
|
18
30
|
}
|
|
@@ -25,6 +37,7 @@ impl MeteredSemaphore {
|
|
|
25
37
|
) -> Self {
|
|
26
38
|
Self {
|
|
27
39
|
sem: Arc::new(Semaphore::new(inital_permits)),
|
|
40
|
+
unused_claimants: Arc::new(AtomicUsize::new(0)),
|
|
28
41
|
metrics_ctx,
|
|
29
42
|
record_fn,
|
|
30
43
|
}
|
|
@@ -36,42 +49,154 @@ impl MeteredSemaphore {
|
|
|
36
49
|
|
|
37
50
|
pub async fn acquire_owned(&self) -> Result<OwnedMeteredSemPermit, AcquireError> {
|
|
38
51
|
let res = self.sem.clone().acquire_owned().await?;
|
|
39
|
-
self.
|
|
40
|
-
Ok(OwnedMeteredSemPermit {
|
|
41
|
-
inner: res,
|
|
42
|
-
record_fn: self.record_drop_owned(),
|
|
43
|
-
})
|
|
52
|
+
Ok(self.build_owned(res))
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
pub fn try_acquire_owned(&self) -> Result<OwnedMeteredSemPermit, TryAcquireError> {
|
|
47
56
|
let res = self.sem.clone().try_acquire_owned()?;
|
|
57
|
+
Ok(self.build_owned(res))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
fn build_owned(&self, res: OwnedSemaphorePermit) -> OwnedMeteredSemPermit {
|
|
61
|
+
self.unused_claimants.fetch_add(1, Ordering::Release);
|
|
48
62
|
self.record();
|
|
49
|
-
|
|
63
|
+
OwnedMeteredSemPermit {
|
|
50
64
|
inner: res,
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
unused_claimants: Some(self.unused_claimants.clone()),
|
|
66
|
+
record_fn: self.record_owned(),
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
fn record(&self) {
|
|
56
|
-
(self.record_fn)(
|
|
71
|
+
(self.record_fn)(
|
|
72
|
+
&self.metrics_ctx,
|
|
73
|
+
self.sem.available_permits() + self.unused_claimants.load(Ordering::Acquire),
|
|
74
|
+
);
|
|
57
75
|
}
|
|
58
76
|
|
|
59
|
-
fn
|
|
77
|
+
fn record_owned(&self) -> Box<dyn Fn(bool) + Send + Sync> {
|
|
60
78
|
let rcf = self.record_fn;
|
|
61
79
|
let mets = self.metrics_ctx.clone();
|
|
62
80
|
let sem = self.sem.clone();
|
|
63
|
-
|
|
81
|
+
let uc = self.unused_claimants.clone();
|
|
82
|
+
// When being called from the drop impl, the semaphore permit isn't actually dropped yet,
|
|
83
|
+
// so account for that.
|
|
84
|
+
Box::new(move |add_one: bool| {
|
|
85
|
+
let extra = usize::from(add_one);
|
|
86
|
+
rcf(
|
|
87
|
+
&mets,
|
|
88
|
+
sem.available_permits() + uc.load(Ordering::Acquire) + extra,
|
|
89
|
+
)
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// A version of [MeteredSemaphore] that can be closed and supports waiting for close to complete.
|
|
95
|
+
/// Once closed, no permits will be handed out.
|
|
96
|
+
/// Close completes when all permits have been returned.
|
|
97
|
+
pub(crate) struct ClosableMeteredSemaphore {
|
|
98
|
+
inner: Arc<MeteredSemaphore>,
|
|
99
|
+
outstanding_permits: AtomicUsize,
|
|
100
|
+
close_requested: AtomicBool,
|
|
101
|
+
close_complete_token: CancellationToken,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl ClosableMeteredSemaphore {
|
|
105
|
+
pub fn new_arc(sem: Arc<MeteredSemaphore>) -> Arc<Self> {
|
|
106
|
+
Arc::new(Self {
|
|
107
|
+
inner: sem,
|
|
108
|
+
outstanding_permits: Default::default(),
|
|
109
|
+
close_requested: AtomicBool::new(false),
|
|
110
|
+
close_complete_token: CancellationToken::new(),
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl ClosableMeteredSemaphore {
|
|
116
|
+
#[cfg(test)]
|
|
117
|
+
pub fn available_permits(&self) -> usize {
|
|
118
|
+
self.inner.available_permits()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Request to close the semaphore and prevent new permits from being acquired.
|
|
122
|
+
pub fn close(&self) {
|
|
123
|
+
self.close_requested.store(true, Ordering::Release);
|
|
124
|
+
if self.outstanding_permits.load(Ordering::Acquire) == 0 {
|
|
125
|
+
self.close_complete_token.cancel();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// Returns after close has been requested and all outstanding permits have been returned.
|
|
130
|
+
pub async fn close_complete(&self) {
|
|
131
|
+
self.close_complete_token.cancelled().await;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Acquire a permit if one is available and close was not requested.
|
|
135
|
+
pub fn try_acquire_owned(
|
|
136
|
+
self: &Arc<Self>,
|
|
137
|
+
) -> Result<TrackedOwnedMeteredSemPermit, TryAcquireError> {
|
|
138
|
+
if self.close_requested.load(Ordering::Acquire) {
|
|
139
|
+
return Err(TryAcquireError::Closed);
|
|
140
|
+
}
|
|
141
|
+
self.outstanding_permits.fetch_add(1, Ordering::Release);
|
|
142
|
+
let res = self.inner.try_acquire_owned();
|
|
143
|
+
if res.is_err() {
|
|
144
|
+
self.outstanding_permits.fetch_sub(1, Ordering::Release);
|
|
145
|
+
}
|
|
146
|
+
res.map(|permit| TrackedOwnedMeteredSemPermit {
|
|
147
|
+
inner: Some(permit),
|
|
148
|
+
on_drop: self.on_permit_dropped(),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
fn on_permit_dropped(self: &Arc<Self>) -> Box<dyn Fn() + Send + Sync> {
|
|
153
|
+
let sem = self.clone();
|
|
154
|
+
Box::new(move || {
|
|
155
|
+
sem.outstanding_permits.fetch_sub(1, Ordering::Release);
|
|
156
|
+
if sem.close_requested.load(Ordering::Acquire)
|
|
157
|
+
&& sem.outstanding_permits.load(Ordering::Acquire) == 0
|
|
158
|
+
{
|
|
159
|
+
sem.close_complete_token.cancel();
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Tracks an OwnedMeteredSemPermit and calls on_drop when dropped.
|
|
166
|
+
#[derive(DebugCustom)]
|
|
167
|
+
#[debug(fmt = "Tracked({inner:?})")]
|
|
168
|
+
pub(crate) struct TrackedOwnedMeteredSemPermit {
|
|
169
|
+
inner: Option<OwnedMeteredSemPermit>,
|
|
170
|
+
on_drop: Box<dyn Fn() + Send + Sync>,
|
|
171
|
+
}
|
|
172
|
+
impl From<TrackedOwnedMeteredSemPermit> for OwnedMeteredSemPermit {
|
|
173
|
+
fn from(mut value: TrackedOwnedMeteredSemPermit) -> Self {
|
|
174
|
+
value
|
|
175
|
+
.inner
|
|
176
|
+
.take()
|
|
177
|
+
.expect("Inner permit should be available")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
impl Drop for TrackedOwnedMeteredSemPermit {
|
|
181
|
+
fn drop(&mut self) {
|
|
182
|
+
(self.on_drop)();
|
|
64
183
|
}
|
|
65
184
|
}
|
|
66
185
|
|
|
67
186
|
/// Wraps an [OwnedSemaphorePermit] to update metrics when it's dropped
|
|
68
187
|
pub(crate) struct OwnedMeteredSemPermit {
|
|
69
188
|
inner: OwnedSemaphorePermit,
|
|
70
|
-
|
|
189
|
+
/// See [MeteredSemaphore::unused_claimants]. If present when dropping, used to decrement the
|
|
190
|
+
/// count.
|
|
191
|
+
unused_claimants: Option<Arc<AtomicUsize>>,
|
|
192
|
+
record_fn: Box<dyn Fn(bool) + Send + Sync>,
|
|
71
193
|
}
|
|
72
194
|
impl Drop for OwnedMeteredSemPermit {
|
|
73
195
|
fn drop(&mut self) {
|
|
74
|
-
(self.
|
|
196
|
+
if let Some(uc) = self.unused_claimants.take() {
|
|
197
|
+
uc.fetch_sub(1, Ordering::Release);
|
|
198
|
+
}
|
|
199
|
+
(self.record_fn)(true)
|
|
75
200
|
}
|
|
76
201
|
}
|
|
77
202
|
impl Debug for OwnedMeteredSemPermit {
|
|
@@ -79,15 +204,30 @@ impl Debug for OwnedMeteredSemPermit {
|
|
|
79
204
|
self.inner.fmt(f)
|
|
80
205
|
}
|
|
81
206
|
}
|
|
82
|
-
#[cfg(feature = "save_wf_inputs")]
|
|
83
207
|
impl OwnedMeteredSemPermit {
|
|
208
|
+
/// Should be called once this permit is actually being "used" for the work it was meant to
|
|
209
|
+
/// permit.
|
|
210
|
+
pub(crate) fn into_used(mut self) -> UsedMeteredSemPermit {
|
|
211
|
+
if let Some(uc) = self.unused_claimants.take() {
|
|
212
|
+
uc.fetch_sub(1, Ordering::Release);
|
|
213
|
+
(self.record_fn)(false)
|
|
214
|
+
}
|
|
215
|
+
UsedMeteredSemPermit(self)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[derive(Debug)]
|
|
220
|
+
pub(crate) struct UsedMeteredSemPermit(OwnedMeteredSemPermit);
|
|
221
|
+
impl UsedMeteredSemPermit {
|
|
222
|
+
#[cfg(feature = "save_wf_inputs")]
|
|
84
223
|
pub(crate) fn fake_deserialized() -> Self {
|
|
85
224
|
let sem = Arc::new(Semaphore::new(1));
|
|
86
225
|
let inner = sem.try_acquire_owned().unwrap();
|
|
87
|
-
Self {
|
|
226
|
+
Self(OwnedMeteredSemPermit {
|
|
88
227
|
inner,
|
|
89
|
-
|
|
90
|
-
|
|
228
|
+
unused_claimants: None,
|
|
229
|
+
record_fn: Box::new(|_| {}),
|
|
230
|
+
})
|
|
91
231
|
}
|
|
92
232
|
}
|
|
93
233
|
|
|
@@ -174,4 +314,43 @@ mod tests {
|
|
|
174
314
|
allow_tx.send(()).unwrap();
|
|
175
315
|
assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Ready(None));
|
|
176
316
|
}
|
|
317
|
+
|
|
318
|
+
#[tokio::test]
|
|
319
|
+
async fn closable_semaphore_permit_drop_returns_permit() {
|
|
320
|
+
let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
|
|
321
|
+
let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
|
|
322
|
+
let perm = sem.try_acquire_owned().unwrap();
|
|
323
|
+
let permits = sem.outstanding_permits.load(Ordering::Acquire);
|
|
324
|
+
assert_eq!(permits, 1);
|
|
325
|
+
drop(perm);
|
|
326
|
+
let permits = sem.outstanding_permits.load(Ordering::Acquire);
|
|
327
|
+
assert_eq!(permits, 0);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
#[tokio::test]
|
|
331
|
+
async fn closable_semaphore_permit_drop_after_close_resolves_close_complete() {
|
|
332
|
+
let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
|
|
333
|
+
let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
|
|
334
|
+
let perm = sem.try_acquire_owned().unwrap();
|
|
335
|
+
sem.close();
|
|
336
|
+
drop(perm);
|
|
337
|
+
sem.close_complete().await;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[tokio::test]
|
|
341
|
+
async fn closable_semaphore_close_complete_ready_if_unused() {
|
|
342
|
+
let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
|
|
343
|
+
let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
|
|
344
|
+
sem.close();
|
|
345
|
+
sem.close_complete().await;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
#[tokio::test]
|
|
349
|
+
async fn closable_semaphore_does_not_hand_out_permits_after_closed() {
|
|
350
|
+
let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
|
|
351
|
+
let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
|
|
352
|
+
sem.close();
|
|
353
|
+
let perm = sem.try_acquire_owned().unwrap_err();
|
|
354
|
+
assert_matches!(perm, TryAcquireError::Closed);
|
|
355
|
+
}
|
|
177
356
|
}
|