@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.
Files changed (138) hide show
  1. package/Cargo.lock +83 -98
  2. package/lib/index.d.ts +8 -6
  3. package/lib/index.js.map +1 -1
  4. package/package.json +8 -3
  5. package/releases/aarch64-apple-darwin/index.node +0 -0
  6. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  7. package/releases/x86_64-apple-darwin/index.node +0 -0
  8. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  9. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  10. package/sdk-core/.buildkite/docker/Dockerfile +2 -2
  11. package/sdk-core/.buildkite/docker/docker-compose.yaml +1 -1
  12. package/sdk-core/.buildkite/pipeline.yml +1 -1
  13. package/sdk-core/.github/workflows/heavy.yml +1 -0
  14. package/sdk-core/README.md +13 -7
  15. package/sdk-core/client/src/lib.rs +4 -3
  16. package/sdk-core/client/src/metrics.rs +17 -8
  17. package/sdk-core/client/src/raw.rs +3 -3
  18. package/sdk-core/core/Cargo.toml +3 -4
  19. package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
  20. package/sdk-core/core/src/abstractions.rs +197 -18
  21. package/sdk-core/core/src/core_tests/activity_tasks.rs +137 -45
  22. package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
  23. package/sdk-core/core/src/core_tests/determinism.rs +165 -2
  24. package/sdk-core/core/src/core_tests/local_activities.rs +183 -36
  25. package/sdk-core/core/src/core_tests/queries.rs +32 -14
  26. package/sdk-core/core/src/core_tests/workers.rs +8 -5
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +340 -51
  28. package/sdk-core/core/src/ephemeral_server/mod.rs +110 -8
  29. package/sdk-core/core/src/internal_flags.rs +155 -0
  30. package/sdk-core/core/src/lib.rs +14 -9
  31. package/sdk-core/core/src/replay/mod.rs +16 -27
  32. package/sdk-core/core/src/telemetry/metrics.rs +69 -35
  33. package/sdk-core/core/src/telemetry/mod.rs +38 -14
  34. package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
  35. package/sdk-core/core/src/test_help/mod.rs +65 -13
  36. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
  37. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  38. package/sdk-core/core/src/worker/activities/local_activities.rs +122 -6
  39. package/sdk-core/core/src/worker/activities.rs +347 -173
  40. package/sdk-core/core/src/worker/client/mocks.rs +22 -2
  41. package/sdk-core/core/src/worker/client.rs +18 -2
  42. package/sdk-core/core/src/worker/mod.rs +136 -44
  43. package/sdk-core/core/src/worker/workflow/history_update.rs +132 -51
  44. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +207 -166
  45. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +6 -7
  46. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +6 -7
  47. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +157 -82
  48. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +12 -12
  49. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +6 -7
  50. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +13 -15
  51. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +170 -60
  52. package/sdk-core/core/src/worker/workflow/machines/mod.rs +24 -16
  53. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +6 -8
  54. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +320 -204
  55. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +10 -13
  56. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +15 -23
  57. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +256 -50
  58. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +240 -111
  59. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +13 -13
  60. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +10 -6
  61. package/sdk-core/core/src/worker/workflow/managed_run.rs +71 -60
  62. package/sdk-core/core/src/worker/workflow/mod.rs +321 -73
  63. package/sdk-core/core/src/worker/workflow/run_cache.rs +18 -11
  64. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +15 -3
  65. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +2 -0
  66. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +46 -25
  67. package/sdk-core/core-api/Cargo.toml +0 -1
  68. package/sdk-core/core-api/src/lib.rs +13 -7
  69. package/sdk-core/core-api/src/telemetry.rs +4 -6
  70. package/sdk-core/core-api/src/worker.rs +5 -0
  71. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +80 -55
  72. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +22 -68
  73. package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
  74. package/sdk-core/histories/old_change_marker_format.bin +0 -0
  75. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
  76. package/sdk-core/protos/api_upstream/Makefile +1 -1
  77. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
  78. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
  79. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
  80. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
  81. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
  83. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
  84. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
  85. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
  87. package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  88. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
  89. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
  90. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
  91. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
  92. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
  93. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
  94. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  95. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  96. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
  97. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +65 -60
  98. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
  99. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
  100. package/sdk-core/sdk/Cargo.toml +1 -1
  101. package/sdk-core/sdk/src/lib.rs +21 -5
  102. package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
  103. package/sdk-core/sdk/src/workflow_context.rs +24 -17
  104. package/sdk-core/sdk/src/workflow_future.rs +9 -3
  105. package/sdk-core/sdk-core-protos/src/history_builder.rs +114 -89
  106. package/sdk-core/sdk-core-protos/src/history_info.rs +6 -1
  107. package/sdk-core/sdk-core-protos/src/lib.rs +205 -64
  108. package/sdk-core/test-utils/src/canned_histories.rs +106 -296
  109. package/sdk-core/test-utils/src/lib.rs +32 -5
  110. package/sdk-core/tests/heavy_tests.rs +10 -43
  111. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  112. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
  113. package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  114. package/sdk-core/tests/integ_tests/polling_tests.rs +3 -8
  115. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -2
  116. package/sdk-core/tests/integ_tests/visibility_tests.rs +34 -23
  117. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +97 -81
  118. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  119. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -0
  120. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
  121. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +5 -1
  122. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +1 -0
  123. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +25 -3
  124. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
  125. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +30 -0
  126. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +64 -0
  127. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  128. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +4 -0
  129. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -1
  130. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +7 -2
  131. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -7
  132. package/sdk-core/tests/integ_tests/workflow_tests.rs +8 -8
  133. package/sdk-core/tests/main.rs +1 -1
  134. package/sdk-core/tests/runner.rs +11 -9
  135. package/src/conversions.rs +14 -8
  136. package/src/runtime.rs +9 -8
  137. package/ts/index.ts +8 -6
  138. package/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
@@ -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
- # Getting started
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
  ![Crate dependency graph](./etc/deps.svg)
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<&Meter>,
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<&Meter>,
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, Meter},
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>, meter: &Meter) -> Self {
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: meter.u64_counter("request").init(),
40
- svc_request_failed: meter.u64_counter("request_failure").init(),
41
- long_svc_request: meter.u64_counter("long_request").init(),
42
- long_svc_request_failed: meter.u64_counter("long_request_failure").init(),
43
- svc_request_latency: meter.u64_histogram("request_latency").init(),
44
- long_svc_request_latency: meter.u64_histogram("long_request_latency").init(),
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
- update_workflow,
764
- UpdateWorkflowRequest,
765
- UpdateWorkflowResponse,
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);
@@ -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.9"
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.1", features = ["rt", "rt-multi-thread", "parking_lot", "time", "fs", "process"] }
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.16"
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::Arc,
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.record();
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
- Ok(OwnedMeteredSemPermit {
63
+ OwnedMeteredSemPermit {
50
64
  inner: res,
51
- record_fn: self.record_drop_owned(),
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)(&self.metrics_ctx, self.sem.available_permits());
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 record_drop_owned(&self) -> Box<dyn Fn() + Send + Sync> {
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
- Box::new(move || rcf(&mets, sem.available_permits() + 1))
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
- record_fn: Box<dyn Fn() + Send + Sync>,
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.record_fn)()
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
- record_fn: Box::new(|| {}),
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
  }