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.
Files changed (202) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +25 -23
  3. data/bridge/Cargo.lock +168 -59
  4. data/bridge/Cargo.toml +4 -2
  5. data/bridge/sdk-core/README.md +19 -6
  6. data/bridge/sdk-core/client/src/lib.rs +215 -39
  7. data/bridge/sdk-core/client/src/metrics.rs +17 -8
  8. data/bridge/sdk-core/client/src/raw.rs +4 -4
  9. data/bridge/sdk-core/client/src/retry.rs +32 -20
  10. data/bridge/sdk-core/core/Cargo.toml +22 -9
  11. data/bridge/sdk-core/core/src/abstractions.rs +203 -14
  12. data/bridge/sdk-core/core/src/core_tests/activity_tasks.rs +76 -41
  13. data/bridge/sdk-core/core/src/core_tests/determinism.rs +165 -2
  14. data/bridge/sdk-core/core/src/core_tests/local_activities.rs +204 -83
  15. data/bridge/sdk-core/core/src/core_tests/queries.rs +3 -4
  16. data/bridge/sdk-core/core/src/core_tests/workers.rs +1 -3
  17. data/bridge/sdk-core/core/src/core_tests/workflow_tasks.rs +397 -54
  18. data/bridge/sdk-core/core/src/ephemeral_server/mod.rs +106 -12
  19. data/bridge/sdk-core/core/src/internal_flags.rs +136 -0
  20. data/bridge/sdk-core/core/src/lib.rs +16 -9
  21. data/bridge/sdk-core/core/src/telemetry/log_export.rs +1 -1
  22. data/bridge/sdk-core/core/src/telemetry/metrics.rs +69 -35
  23. data/bridge/sdk-core/core/src/telemetry/mod.rs +29 -13
  24. data/bridge/sdk-core/core/src/telemetry/prometheus_server.rs +17 -12
  25. data/bridge/sdk-core/core/src/test_help/mod.rs +62 -12
  26. data/bridge/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +112 -156
  27. data/bridge/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  28. data/bridge/sdk-core/core/src/worker/activities/local_activities.rs +352 -122
  29. data/bridge/sdk-core/core/src/worker/activities.rs +233 -157
  30. data/bridge/sdk-core/core/src/worker/client/mocks.rs +22 -2
  31. data/bridge/sdk-core/core/src/worker/client.rs +18 -2
  32. data/bridge/sdk-core/core/src/worker/mod.rs +165 -58
  33. data/bridge/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
  34. data/bridge/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
  35. data/bridge/sdk-core/core/src/worker/workflow/history_update.rs +856 -277
  36. data/bridge/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +100 -43
  37. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +7 -7
  38. data/bridge/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +5 -4
  39. data/bridge/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +87 -27
  40. data/bridge/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +5 -4
  41. data/bridge/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +5 -4
  42. data/bridge/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +5 -4
  43. data/bridge/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +137 -62
  44. data/bridge/sdk-core/core/src/worker/workflow/machines/mod.rs +25 -17
  45. data/bridge/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +7 -6
  46. data/bridge/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +103 -152
  47. data/bridge/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +7 -7
  48. data/bridge/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +9 -9
  49. data/bridge/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
  50. data/bridge/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +14 -7
  51. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +5 -16
  52. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +201 -121
  53. data/bridge/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +11 -14
  54. data/bridge/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +30 -15
  55. data/bridge/sdk-core/core/src/worker/workflow/managed_run.rs +1026 -376
  56. data/bridge/sdk-core/core/src/worker/workflow/mod.rs +460 -384
  57. data/bridge/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
  58. data/bridge/sdk-core/core/src/worker/workflow/wft_extraction.rs +125 -0
  59. data/bridge/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
  60. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
  61. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
  62. data/bridge/sdk-core/core/src/worker/workflow/workflow_stream.rs +448 -718
  63. data/bridge/sdk-core/core-api/Cargo.toml +2 -1
  64. data/bridge/sdk-core/core-api/src/errors.rs +1 -34
  65. data/bridge/sdk-core/core-api/src/lib.rs +6 -2
  66. data/bridge/sdk-core/core-api/src/telemetry.rs +0 -6
  67. data/bridge/sdk-core/core-api/src/worker.rs +14 -1
  68. data/bridge/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +18 -15
  69. data/bridge/sdk-core/fsm/rustfsm_trait/src/lib.rs +8 -3
  70. data/bridge/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
  71. data/bridge/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +5 -17
  72. data/bridge/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +11 -0
  73. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -6
  74. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -6
  75. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +5 -0
  76. data/bridge/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +22 -6
  77. data/bridge/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +48 -19
  78. data/bridge/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +2 -0
  79. data/bridge/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +3 -0
  80. data/bridge/sdk-core/protos/api_upstream/temporal/api/{enums/v1/interaction_type.proto → protocol/v1/message.proto} +29 -11
  81. data/bridge/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  82. data/bridge/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +111 -0
  83. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +59 -28
  84. data/bridge/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +2 -2
  85. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -0
  86. data/bridge/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -0
  87. data/bridge/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -0
  88. data/bridge/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  89. data/bridge/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  90. data/bridge/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -0
  91. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +7 -0
  92. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -0
  93. data/bridge/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +6 -0
  94. data/bridge/sdk-core/sdk/Cargo.toml +3 -2
  95. data/bridge/sdk-core/sdk/src/lib.rs +87 -20
  96. data/bridge/sdk-core/sdk/src/workflow_future.rs +9 -8
  97. data/bridge/sdk-core/sdk-core-protos/Cargo.toml +5 -2
  98. data/bridge/sdk-core/sdk-core-protos/build.rs +36 -1
  99. data/bridge/sdk-core/sdk-core-protos/src/history_builder.rs +100 -87
  100. data/bridge/sdk-core/sdk-core-protos/src/history_info.rs +5 -1
  101. data/bridge/sdk-core/sdk-core-protos/src/lib.rs +175 -57
  102. data/bridge/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
  103. data/bridge/sdk-core/test-utils/Cargo.toml +3 -1
  104. data/bridge/sdk-core/test-utils/src/canned_histories.rs +106 -296
  105. data/bridge/sdk-core/test-utils/src/histfetch.rs +1 -1
  106. data/bridge/sdk-core/test-utils/src/lib.rs +82 -23
  107. data/bridge/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
  108. data/bridge/sdk-core/test-utils/src/workflows.rs +29 -0
  109. data/bridge/sdk-core/tests/fuzzy_workflow.rs +130 -0
  110. data/bridge/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
  111. data/bridge/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  112. data/bridge/sdk-core/tests/integ_tests/heartbeat_tests.rs +5 -3
  113. data/bridge/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  114. data/bridge/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
  115. data/bridge/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
  116. data/bridge/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
  117. data/bridge/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -69
  118. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  119. data/bridge/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
  120. data/bridge/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +1 -0
  121. data/bridge/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
  122. data/bridge/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
  123. data/bridge/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +72 -191
  124. data/bridge/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +1 -0
  125. data/bridge/sdk-core/tests/integ_tests/workflow_tests/patches.rs +7 -28
  126. data/bridge/sdk-core/tests/integ_tests/workflow_tests/replay.rs +12 -7
  127. data/bridge/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  128. data/bridge/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
  129. data/bridge/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
  130. data/bridge/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
  131. data/bridge/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +6 -4
  132. data/bridge/sdk-core/tests/integ_tests/workflow_tests.rs +10 -11
  133. data/bridge/sdk-core/tests/main.rs +3 -13
  134. data/bridge/sdk-core/tests/runner.rs +75 -36
  135. data/bridge/sdk-core/tests/wf_input_replay.rs +32 -0
  136. data/bridge/src/connection.rs +41 -25
  137. data/bridge/src/lib.rs +269 -14
  138. data/bridge/src/runtime.rs +1 -1
  139. data/bridge/src/test_server.rs +153 -0
  140. data/bridge/src/worker.rs +89 -16
  141. data/lib/gen/temporal/api/command/v1/message_pb.rb +4 -18
  142. data/lib/gen/temporal/api/common/v1/message_pb.rb +4 -0
  143. data/lib/gen/temporal/api/enums/v1/command_type_pb.rb +1 -3
  144. data/lib/gen/temporal/api/enums/v1/event_type_pb.rb +3 -3
  145. data/lib/gen/temporal/api/enums/v1/failed_cause_pb.rb +2 -0
  146. data/lib/gen/temporal/api/enums/v1/update_pb.rb +6 -4
  147. data/lib/gen/temporal/api/history/v1/message_pb.rb +27 -19
  148. data/lib/gen/temporal/api/namespace/v1/message_pb.rb +1 -0
  149. data/lib/gen/temporal/api/operatorservice/v1/request_response_pb.rb +3 -0
  150. data/lib/gen/temporal/api/protocol/v1/message_pb.rb +30 -0
  151. data/lib/gen/temporal/api/sdk/v1/task_complete_metadata_pb.rb +23 -0
  152. data/lib/gen/temporal/api/testservice/v1/request_response_pb.rb +49 -0
  153. data/lib/gen/temporal/api/testservice/v1/service_pb.rb +21 -0
  154. data/lib/gen/temporal/api/update/v1/message_pb.rb +72 -0
  155. data/lib/gen/temporal/api/workflowservice/v1/request_response_pb.rb +26 -16
  156. data/lib/gen/temporal/sdk/core/activity_result/activity_result_pb.rb +13 -9
  157. data/lib/gen/temporal/sdk/core/activity_task/activity_task_pb.rb +10 -6
  158. data/lib/gen/temporal/sdk/core/child_workflow/child_workflow_pb.rb +13 -9
  159. data/lib/gen/temporal/sdk/core/common/common_pb.rb +7 -3
  160. data/lib/gen/temporal/sdk/core/core_interface_pb.rb +9 -3
  161. data/lib/gen/temporal/sdk/core/external_data/external_data_pb.rb +7 -3
  162. data/lib/gen/temporal/sdk/core/workflow_activation/workflow_activation_pb.rb +27 -21
  163. data/lib/gen/temporal/sdk/core/workflow_commands/workflow_commands_pb.rb +28 -24
  164. data/lib/gen/temporal/sdk/core/workflow_completion/workflow_completion_pb.rb +12 -5
  165. data/lib/temporalio/activity/context.rb +13 -8
  166. data/lib/temporalio/activity/info.rb +1 -1
  167. data/lib/temporalio/bridge/connect_options.rb +15 -0
  168. data/lib/temporalio/bridge/retry_config.rb +24 -0
  169. data/lib/temporalio/bridge/tls_options.rb +19 -0
  170. data/lib/temporalio/client/implementation.rb +8 -8
  171. data/lib/temporalio/connection/retry_config.rb +44 -0
  172. data/lib/temporalio/connection/service.rb +20 -0
  173. data/lib/temporalio/connection/test_service.rb +92 -0
  174. data/lib/temporalio/connection/tls_options.rb +51 -0
  175. data/lib/temporalio/connection/workflow_service.rb +731 -0
  176. data/lib/temporalio/connection.rb +55 -720
  177. data/lib/temporalio/interceptor/activity_inbound.rb +22 -0
  178. data/lib/temporalio/interceptor/activity_outbound.rb +24 -0
  179. data/lib/temporalio/interceptor/chain.rb +5 -5
  180. data/lib/temporalio/interceptor/client.rb +8 -4
  181. data/lib/temporalio/interceptor.rb +22 -0
  182. data/lib/temporalio/retry_policy.rb +13 -3
  183. data/lib/temporalio/testing/time_skipping_handle.rb +32 -0
  184. data/lib/temporalio/testing/time_skipping_interceptor.rb +23 -0
  185. data/lib/temporalio/testing/workflow_environment.rb +112 -0
  186. data/lib/temporalio/testing.rb +175 -0
  187. data/lib/temporalio/version.rb +1 -1
  188. data/lib/temporalio/worker/activity_runner.rb +26 -4
  189. data/lib/temporalio/worker/activity_worker.rb +44 -18
  190. data/lib/temporalio/worker/sync_worker.rb +47 -11
  191. data/lib/temporalio/worker.rb +27 -21
  192. data/lib/temporalio/workflow/async.rb +46 -0
  193. data/lib/temporalio/workflow/future.rb +138 -0
  194. data/lib/temporalio/workflow/info.rb +76 -0
  195. data/temporalio.gemspec +4 -3
  196. metadata +67 -17
  197. data/bridge/sdk-core/Cargo.lock +0 -2606
  198. data/bridge/sdk-core/protos/api_upstream/temporal/api/interaction/v1/message.proto +0 -87
  199. data/lib/bridge.so +0 -0
  200. data/lib/gen/temporal/api/enums/v1/interaction_type_pb.rb +0 -25
  201. data/lib/gen/temporal/api/interaction/v1/message_pb.rb +0 -49
  202. data/lib/gen/temporal/sdk/core/bridge/bridge_pb.rb +0 -222
@@ -1,18 +1,29 @@
1
1
  //! This module contains very generic helpers that can be used codebase-wide
2
2
 
3
3
  use crate::MetricsContext;
4
+ use derive_more::DebugCustom;
4
5
  use futures::{stream, Stream, StreamExt};
6
+ use std::sync::atomic::AtomicBool;
5
7
  use std::{
6
8
  fmt::{Debug, Formatter},
7
- sync::Arc,
9
+ sync::{
10
+ atomic::{AtomicUsize, Ordering},
11
+ Arc,
12
+ },
8
13
  };
9
14
  use tokio::sync::{AcquireError, OwnedSemaphorePermit, Semaphore, TryAcquireError};
15
+ use tokio_util::sync::CancellationToken;
10
16
 
11
17
  /// Wraps a [Semaphore] with a function call that is fed the available permits any time a permit is
12
18
  /// acquired or restored through the provided methods
13
19
  #[derive(Clone)]
14
20
  pub(crate) struct MeteredSemaphore {
15
21
  sem: Arc<Semaphore>,
22
+ /// The number of permit owners who have acquired a permit from the semaphore, but are not yet
23
+ /// meaningfully using that permit. This is useful for giving a more semantically accurate count
24
+ /// of used task slots, since we typically wait for a permit first before polling, but that slot
25
+ /// isn't used in the sense the user expects until we actually also get the corresponding task.
26
+ unused_claimants: Arc<AtomicUsize>,
16
27
  metrics_ctx: MetricsContext,
17
28
  record_fn: fn(&MetricsContext, usize),
18
29
  }
@@ -25,6 +36,7 @@ impl MeteredSemaphore {
25
36
  ) -> Self {
26
37
  Self {
27
38
  sem: Arc::new(Semaphore::new(inital_permits)),
39
+ unused_claimants: Arc::new(AtomicUsize::new(0)),
28
40
  metrics_ctx,
29
41
  record_fn,
30
42
  }
@@ -36,42 +48,154 @@ impl MeteredSemaphore {
36
48
 
37
49
  pub async fn acquire_owned(&self) -> Result<OwnedMeteredSemPermit, AcquireError> {
38
50
  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
- })
51
+ Ok(self.build_owned(res))
44
52
  }
45
53
 
46
54
  pub fn try_acquire_owned(&self) -> Result<OwnedMeteredSemPermit, TryAcquireError> {
47
55
  let res = self.sem.clone().try_acquire_owned()?;
56
+ Ok(self.build_owned(res))
57
+ }
58
+
59
+ fn build_owned(&self, res: OwnedSemaphorePermit) -> OwnedMeteredSemPermit {
60
+ self.unused_claimants.fetch_add(1, Ordering::Release);
48
61
  self.record();
49
- Ok(OwnedMeteredSemPermit {
62
+ OwnedMeteredSemPermit {
50
63
  inner: res,
51
- record_fn: self.record_drop_owned(),
52
- })
64
+ unused_claimants: Some(self.unused_claimants.clone()),
65
+ record_fn: self.record_owned(),
66
+ }
53
67
  }
54
68
 
55
69
  fn record(&self) {
56
- (self.record_fn)(&self.metrics_ctx, self.sem.available_permits());
70
+ (self.record_fn)(
71
+ &self.metrics_ctx,
72
+ self.sem.available_permits() + self.unused_claimants.load(Ordering::Acquire),
73
+ );
57
74
  }
58
75
 
59
- fn record_drop_owned(&self) -> Box<dyn Fn() + Send + Sync> {
76
+ fn record_owned(&self) -> Box<dyn Fn(bool) + Send + Sync> {
60
77
  let rcf = self.record_fn;
61
78
  let mets = self.metrics_ctx.clone();
62
79
  let sem = self.sem.clone();
63
- Box::new(move || rcf(&mets, sem.available_permits() + 1))
80
+ let uc = self.unused_claimants.clone();
81
+ // When being called from the drop impl, the semaphore permit isn't actually dropped yet,
82
+ // so account for that.
83
+ Box::new(move |add_one: bool| {
84
+ let extra = usize::from(add_one);
85
+ rcf(
86
+ &mets,
87
+ sem.available_permits() + uc.load(Ordering::Acquire) + extra,
88
+ )
89
+ })
90
+ }
91
+ }
92
+
93
+ /// A version of [MeteredSemaphore] that can be closed and supports waiting for close to complete.
94
+ /// Once closed, no permits will be handed out.
95
+ /// Close completes when all permits have been returned.
96
+ pub(crate) struct ClosableMeteredSemaphore {
97
+ inner: Arc<MeteredSemaphore>,
98
+ outstanding_permits: AtomicUsize,
99
+ close_requested: AtomicBool,
100
+ close_complete_token: CancellationToken,
101
+ }
102
+
103
+ impl ClosableMeteredSemaphore {
104
+ pub fn new_arc(sem: Arc<MeteredSemaphore>) -> Arc<Self> {
105
+ Arc::new(Self {
106
+ inner: sem,
107
+ outstanding_permits: Default::default(),
108
+ close_requested: AtomicBool::new(false),
109
+ close_complete_token: CancellationToken::new(),
110
+ })
111
+ }
112
+ }
113
+
114
+ impl ClosableMeteredSemaphore {
115
+ #[cfg(test)]
116
+ pub fn available_permits(&self) -> usize {
117
+ self.inner.available_permits()
118
+ }
119
+
120
+ /// Request to close the semaphore and prevent new permits from being acquired.
121
+ pub fn close(&self) {
122
+ self.close_requested.store(true, Ordering::Release);
123
+ if self.outstanding_permits.load(Ordering::Acquire) == 0 {
124
+ self.close_complete_token.cancel();
125
+ }
126
+ }
127
+
128
+ /// Returns after close has been requested and all outstanding permits have been returned.
129
+ pub async fn close_complete(&self) {
130
+ self.close_complete_token.cancelled().await;
131
+ }
132
+
133
+ /// Acquire a permit if one is available and close was not requested.
134
+ pub fn try_acquire_owned(
135
+ self: &Arc<Self>,
136
+ ) -> Result<TrackedOwnedMeteredSemPermit, TryAcquireError> {
137
+ if self.close_requested.load(Ordering::Acquire) {
138
+ return Err(TryAcquireError::Closed);
139
+ }
140
+ self.outstanding_permits.fetch_add(1, Ordering::Release);
141
+ let res = self.inner.try_acquire_owned();
142
+ if res.is_err() {
143
+ self.outstanding_permits.fetch_sub(1, Ordering::Release);
144
+ }
145
+ res.map(|permit| TrackedOwnedMeteredSemPermit {
146
+ inner: Some(permit),
147
+ on_drop: self.on_permit_dropped(),
148
+ })
149
+ }
150
+
151
+ fn on_permit_dropped(self: &Arc<Self>) -> Box<dyn Fn() + Send + Sync> {
152
+ let sem = self.clone();
153
+ Box::new(move || {
154
+ sem.outstanding_permits.fetch_sub(1, Ordering::Release);
155
+ if sem.close_requested.load(Ordering::Acquire)
156
+ && sem.outstanding_permits.load(Ordering::Acquire) == 0
157
+ {
158
+ sem.close_complete_token.cancel();
159
+ }
160
+ })
161
+ }
162
+ }
163
+
164
+ /// Tracks an OwnedMeteredSemPermit and calls on_drop when dropped.
165
+ #[derive(DebugCustom)]
166
+ #[debug(fmt = "Tracked({inner:?})")]
167
+ pub(crate) struct TrackedOwnedMeteredSemPermit {
168
+ inner: Option<OwnedMeteredSemPermit>,
169
+ on_drop: Box<dyn Fn() + Send + Sync>,
170
+ }
171
+ impl From<TrackedOwnedMeteredSemPermit> for OwnedMeteredSemPermit {
172
+ fn from(mut value: TrackedOwnedMeteredSemPermit) -> Self {
173
+ value
174
+ .inner
175
+ .take()
176
+ .expect("Inner permit should be available")
177
+ }
178
+ }
179
+ impl Drop for TrackedOwnedMeteredSemPermit {
180
+ fn drop(&mut self) {
181
+ (self.on_drop)();
64
182
  }
65
183
  }
66
184
 
67
185
  /// Wraps an [OwnedSemaphorePermit] to update metrics when it's dropped
68
186
  pub(crate) struct OwnedMeteredSemPermit {
69
187
  inner: OwnedSemaphorePermit,
70
- record_fn: Box<dyn Fn() + Send + Sync>,
188
+ /// See [MeteredSemaphore::unused_claimants]. If present when dropping, used to decrement the
189
+ /// count.
190
+ unused_claimants: Option<Arc<AtomicUsize>>,
191
+ record_fn: Box<dyn Fn(bool) + Send + Sync>,
71
192
  }
72
193
  impl Drop for OwnedMeteredSemPermit {
73
194
  fn drop(&mut self) {
74
- (self.record_fn)()
195
+ if let Some(uc) = self.unused_claimants.take() {
196
+ uc.fetch_sub(1, Ordering::Release);
197
+ }
198
+ (self.record_fn)(true)
75
199
  }
76
200
  }
77
201
  impl Debug for OwnedMeteredSemPermit {
@@ -79,6 +203,32 @@ impl Debug for OwnedMeteredSemPermit {
79
203
  self.inner.fmt(f)
80
204
  }
81
205
  }
206
+ impl OwnedMeteredSemPermit {
207
+ /// Should be called once this permit is actually being "used" for the work it was meant to
208
+ /// permit.
209
+ pub(crate) fn into_used(mut self) -> UsedMeteredSemPermit {
210
+ if let Some(uc) = self.unused_claimants.take() {
211
+ uc.fetch_sub(1, Ordering::Release);
212
+ (self.record_fn)(false)
213
+ }
214
+ UsedMeteredSemPermit(self)
215
+ }
216
+ }
217
+
218
+ #[derive(Debug)]
219
+ pub(crate) struct UsedMeteredSemPermit(OwnedMeteredSemPermit);
220
+ impl UsedMeteredSemPermit {
221
+ #[cfg(feature = "save_wf_inputs")]
222
+ pub(crate) fn fake_deserialized() -> Self {
223
+ let sem = Arc::new(Semaphore::new(1));
224
+ let inner = sem.try_acquire_owned().unwrap();
225
+ Self(OwnedMeteredSemPermit {
226
+ inner,
227
+ unused_claimants: None,
228
+ record_fn: Box::new(|_| {}),
229
+ })
230
+ }
231
+ }
82
232
 
83
233
  /// From the input stream, create a new stream which only pulls from the input stream when allowed.
84
234
  /// When allowed is determined by the passed in `proceeder` emitting an item. The input stream is
@@ -163,4 +313,43 @@ mod tests {
163
313
  allow_tx.send(()).unwrap();
164
314
  assert_eq!(when_allowed.poll_next_unpin(&mut cx), Poll::Ready(None));
165
315
  }
316
+
317
+ #[tokio::test]
318
+ async fn closable_semaphore_permit_drop_returns_permit() {
319
+ let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
320
+ let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
321
+ let perm = sem.try_acquire_owned().unwrap();
322
+ let permits = sem.outstanding_permits.load(Ordering::Acquire);
323
+ assert_eq!(permits, 1);
324
+ drop(perm);
325
+ let permits = sem.outstanding_permits.load(Ordering::Acquire);
326
+ assert_eq!(permits, 0);
327
+ }
328
+
329
+ #[tokio::test]
330
+ async fn closable_semaphore_permit_drop_after_close_resolves_close_complete() {
331
+ let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
332
+ let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
333
+ let perm = sem.try_acquire_owned().unwrap();
334
+ sem.close();
335
+ drop(perm);
336
+ sem.close_complete().await;
337
+ }
338
+
339
+ #[tokio::test]
340
+ async fn closable_semaphore_close_complete_ready_if_unused() {
341
+ let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
342
+ let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
343
+ sem.close();
344
+ sem.close_complete().await;
345
+ }
346
+
347
+ #[tokio::test]
348
+ async fn closable_semaphore_does_not_hand_out_permits_after_closed() {
349
+ let inner = MeteredSemaphore::new(2, MetricsContext::no_op(), |_, _| {});
350
+ let sem = ClosableMeteredSemaphore::new_arc(Arc::new(inner));
351
+ sem.close();
352
+ let perm = sem.try_acquire_owned().unwrap_err();
353
+ assert_matches!(perm, TryAcquireError::Closed);
354
+ }
166
355
  }
@@ -4,7 +4,7 @@ use crate::{
4
4
  build_fake_worker, build_mock_pollers, canned_histories, gen_assert_and_reply,
5
5
  mock_manual_poller, mock_poller, mock_poller_from_resps, mock_worker, poll_and_reply,
6
6
  single_hist_mock_sg, test_worker_cfg, MockPollCfg, MockWorkerInputs, MocksHolder,
7
- ResponseType, WorkflowCachingPolicy, TEST_Q,
7
+ QueueResponse, ResponseType, WorkerExt, WorkflowCachingPolicy, TEST_Q,
8
8
  },
9
9
  worker::client::mocks::{mock_manual_workflow_client, mock_workflow_client},
10
10
  ActivityHeartbeat, Worker, WorkerConfigBuilder,
@@ -14,6 +14,7 @@ use itertools::Itertools;
14
14
  use std::{
15
15
  cell::RefCell,
16
16
  collections::{hash_map::Entry, HashMap, VecDeque},
17
+ future,
17
18
  rc::Rc,
18
19
  sync::{
19
20
  atomic::{AtomicUsize, Ordering},
@@ -23,7 +24,10 @@ use std::{
23
24
  };
24
25
  use temporal_client::WorkflowOptions;
25
26
  use temporal_sdk::{ActivityOptions, WfContext};
26
- use temporal_sdk_core_api::{errors::CompleteActivityError, Worker as WorkerTrait};
27
+ use temporal_sdk_core_api::{
28
+ errors::{CompleteActivityError, PollActivityError},
29
+ Worker as WorkerTrait,
30
+ };
27
31
  use temporal_sdk_core_protos::{
28
32
  coresdk::{
29
33
  activity_result::{
@@ -42,6 +46,9 @@ use temporal_sdk_core_protos::{
42
46
  temporal::api::{
43
47
  command::v1::{command::Attributes, ScheduleActivityTaskCommandAttributes},
44
48
  enums::v1::EventType,
49
+ history::v1::{
50
+ history_event::Attributes as EventAttributes, ActivityTaskScheduledEventAttributes,
51
+ },
45
52
  workflowservice::v1::{
46
53
  PollActivityTaskQueueResponse, RecordActivityTaskHeartbeatResponse,
47
54
  RespondActivityTaskCanceledResponse, RespondActivityTaskCompletedResponse,
@@ -52,6 +59,7 @@ use temporal_sdk_core_protos::{
52
59
  };
53
60
  use temporal_sdk_core_test_utils::{fanout_tasks, start_timer_cmd, TestWorker};
54
61
  use tokio::{sync::Barrier, time::sleep};
62
+ use tokio_util::sync::CancellationToken;
55
63
 
56
64
  #[tokio::test]
57
65
  async fn max_activities_respected() {
@@ -121,7 +129,7 @@ async fn activity_not_found_returns_ok() {
121
129
  })
122
130
  .await
123
131
  .unwrap();
124
- core.shutdown().await;
132
+ core.drain_activity_poller_and_shutdown().await;
125
133
  }
126
134
 
127
135
  #[tokio::test]
@@ -217,12 +225,14 @@ async fn heartbeats_report_cancels_only_once() {
217
225
  })
218
226
  .await
219
227
  .unwrap();
220
- core.shutdown().await;
228
+ core.drain_activity_poller_and_shutdown().await;
221
229
  }
222
230
 
223
231
  #[tokio::test]
224
232
  async fn activity_cancel_interrupts_poll() {
225
233
  let mut mock_poller = mock_manual_poller();
234
+ let shutdown_token = CancellationToken::new();
235
+ let shutdown_token_clone = shutdown_token.clone();
226
236
  let mut poll_resps = VecDeque::from(vec![
227
237
  async {
228
238
  Some(Ok(PollActivityTaskQueueResponse {
@@ -237,10 +247,15 @@ async fn activity_cancel_interrupts_poll() {
237
247
  Some(Ok(Default::default()))
238
248
  }
239
249
  .boxed(),
250
+ async move {
251
+ shutdown_token.cancelled().await;
252
+ None
253
+ }
254
+ .boxed(),
240
255
  ]);
241
256
  mock_poller
242
257
  .expect_poll()
243
- .times(2)
258
+ .times(3)
244
259
  .returning(move || poll_resps.pop_front().unwrap());
245
260
 
246
261
  let mut mock_client = mock_manual_workflow_client();
@@ -289,11 +304,12 @@ async fn activity_cancel_interrupts_poll() {
289
304
  }
290
305
  ).await.unwrap();
291
306
  last_finisher.store(2, Ordering::SeqCst);
307
+ shutdown_token_clone.cancel();
292
308
  }
293
309
  };
294
310
  // So that we know we blocked
295
311
  assert_eq!(last_finisher.load(Ordering::Acquire), 2);
296
- core.shutdown().await;
312
+ core.drain_activity_poller_and_shutdown().await;
297
313
  }
298
314
 
299
315
  #[tokio::test]
@@ -342,13 +358,10 @@ async fn many_concurrent_heartbeat_cancels() {
342
358
  })
343
359
  .collect::<Vec<_>>(),
344
360
  );
345
- // Because the mock is so fast, it's possible it can return before the cancel channel in
346
- // the activity task poll selector. So, the final poll when there are no more tasks must
347
- // take a while.
348
361
  poll_resps.push_back(
349
362
  async {
350
- sleep(Duration::from_secs(10)).await;
351
- unreachable!("Long poll")
363
+ future::pending::<()>().await;
364
+ unreachable!()
352
365
  }
353
366
  .boxed(),
354
367
  );
@@ -431,7 +444,7 @@ async fn many_concurrent_heartbeat_cancels() {
431
444
  })
432
445
  .await;
433
446
 
434
- worker.shutdown().await;
447
+ worker.drain_activity_poller_and_shutdown().await;
435
448
  }
436
449
 
437
450
  #[tokio::test]
@@ -483,7 +496,7 @@ async fn activity_timeout_no_double_resolve() {
483
496
  )
484
497
  .await;
485
498
 
486
- core.shutdown().await;
499
+ core.drain_pollers_and_shutdown().await;
487
500
  }
488
501
 
489
502
  #[tokio::test]
@@ -529,7 +542,7 @@ async fn can_heartbeat_acts_during_shutdown() {
529
542
  })
530
543
  .await
531
544
  .unwrap();
532
- shutdown_fut.await;
545
+ core.drain_activity_poller_and_shutdown().await;
533
546
  }
534
547
 
535
548
  /// Verifies that if a user has tried to record a heartbeat and then immediately after failed the
@@ -580,7 +593,7 @@ async fn complete_act_with_fail_flushes_heartbeat() {
580
593
  })
581
594
  .await
582
595
  .unwrap();
583
- core.shutdown().await;
596
+ core.drain_activity_poller_and_shutdown().await;
584
597
 
585
598
  // Verify the last seen call to record a heartbeat had the last detail payload
586
599
  let last_seen_payload = &last_seen_payload.take().unwrap().payloads[0];
@@ -686,7 +699,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_remote_activi
686
699
  .await
687
700
  .unwrap();
688
701
 
689
- core.shutdown().await;
702
+ core.drain_pollers_and_shutdown().await;
690
703
 
691
704
  assert_eq!(num_eager_requested.load(Ordering::Relaxed), 0);
692
705
  }
@@ -703,7 +716,7 @@ async fn activity_tasks_from_completion_are_delivered() {
703
716
  t.add_by_type(EventType::WorkflowExecutionStarted);
704
717
  t.add_full_wf_task();
705
718
  let act_same_queue_scheduled_ids = (1..4)
706
- .map(|i| t.add_activity_task_scheduled(format!("act_id_{}_same_queue", i)))
719
+ .map(|i| t.add_activity_task_scheduled(format!("act_id_{i}_same_queue")))
707
720
  .collect_vec();
708
721
  t.add_activity_task_scheduled("act_id_same_queue_not_eager");
709
722
  t.add_activity_task_scheduled("act_id_different_queue");
@@ -742,7 +755,7 @@ async fn activity_tasks_from_completion_are_delivered() {
742
755
  activity_tasks: (1..4)
743
756
  .map(|i| PollActivityTaskQueueResponse {
744
757
  task_token: vec![i],
745
- activity_id: format!("act_id_{}_same_queue", i),
758
+ activity_id: format!("act_id_{i}_same_queue"),
746
759
  ..Default::default()
747
760
  })
748
761
  .collect_vec(),
@@ -753,11 +766,8 @@ async fn activity_tasks_from_completion_are_delivered() {
753
766
  .times(3)
754
767
  .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
755
768
  let mut mock = single_hist_mock_sg(wfid, t, [1], mock, true);
756
- let mut mock_poller = mock_manual_poller();
757
- mock_poller
758
- .expect_poll()
759
- .returning(|| futures::future::pending().boxed());
760
- mock.set_act_poller(Box::new(mock_poller));
769
+ let act_tasks: Vec<QueueResponse<PollActivityTaskQueueResponse>> = vec![];
770
+ mock.set_act_poller(mock_poller_from_resps(act_tasks));
761
771
  mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
762
772
  let core = mock_worker(mock);
763
773
 
@@ -767,7 +777,7 @@ async fn activity_tasks_from_completion_are_delivered() {
767
777
  .map(|seq| {
768
778
  ScheduleActivity {
769
779
  seq,
770
- activity_id: format!("act_id_{}_same_queue", seq),
780
+ activity_id: format!("act_id_{seq}_same_queue"),
771
781
  task_queue: TEST_Q.to_string(),
772
782
  cancellation_type: ActivityCancellationType::TryCancel as i32,
773
783
  ..Default::default()
@@ -816,7 +826,7 @@ async fn activity_tasks_from_completion_are_delivered() {
816
826
  .unwrap();
817
827
  }
818
828
 
819
- core.shutdown().await;
829
+ core.drain_activity_poller_and_shutdown().await;
820
830
 
821
831
  // Verify only a single eager activity was scheduled (the one on our worker's task queue)
822
832
  assert_eq!(num_eager_requested.load(Ordering::Relaxed), 3);
@@ -828,11 +838,23 @@ async fn activity_tasks_from_completion_reserve_slots() {
828
838
  let mut t = TestHistoryBuilder::default();
829
839
  t.add_by_type(EventType::WorkflowExecutionStarted);
830
840
  t.add_full_wf_task();
831
- let schedid = t.add_activity_task_scheduled("1");
841
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
842
+ ActivityTaskScheduledEventAttributes {
843
+ activity_id: "1".to_string(),
844
+ activity_type: Some("act1".into()),
845
+ ..Default::default()
846
+ },
847
+ ));
832
848
  let startid = t.add_activity_task_started(schedid);
833
849
  t.add_activity_task_completed(schedid, startid, b"hi".into());
834
850
  t.add_full_wf_task();
835
- let schedid = t.add_activity_task_scheduled("2");
851
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
852
+ ActivityTaskScheduledEventAttributes {
853
+ activity_id: "2".to_string(),
854
+ activity_type: Some("act2".into()),
855
+ ..Default::default()
856
+ },
857
+ ));
836
858
  let startid = t.add_activity_task_started(schedid);
837
859
  t.add_activity_task_completed(schedid, startid, b"hi".into());
838
860
  t.add_full_wf_task();
@@ -902,19 +924,25 @@ async fn activity_tasks_from_completion_reserve_slots() {
902
924
  // First poll for activities twice, occupying both slots
903
925
  let at1 = core.poll_activity_task().await.unwrap();
904
926
  let at2 = core.poll_activity_task().await.unwrap();
905
-
906
- worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| async move {
907
- ctx.activity(ActivityOptions {
908
- activity_type: "act1".to_string(),
909
- ..Default::default()
910
- })
911
- .await;
912
- ctx.activity(ActivityOptions {
913
- activity_type: "act2".to_string(),
914
- ..Default::default()
915
- })
916
- .await;
917
- Ok(().into())
927
+ let workflow_complete_token = CancellationToken::new();
928
+ let workflow_complete_token_clone = workflow_complete_token.clone();
929
+
930
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| {
931
+ let complete_token = workflow_complete_token.clone();
932
+ async move {
933
+ ctx.activity(ActivityOptions {
934
+ activity_type: "act1".to_string(),
935
+ ..Default::default()
936
+ })
937
+ .await;
938
+ ctx.activity(ActivityOptions {
939
+ activity_type: "act2".to_string(),
940
+ ..Default::default()
941
+ })
942
+ .await;
943
+ complete_token.cancel();
944
+ Ok(().into())
945
+ }
918
946
  });
919
947
 
920
948
  worker
@@ -941,6 +969,13 @@ async fn activity_tasks_from_completion_reserve_slots() {
941
969
  .await
942
970
  .unwrap();
943
971
  barr.wait().await;
972
+ // Wait for workflow to complete in order for all eager activities to be requested before shutting down.
973
+ // After shutdown, no eager activities slots can be allocated.
974
+ workflow_complete_token_clone.cancelled().await;
975
+ core.initiate_shutdown();
976
+ // Even though this test requests eager activity tasks, none are returned in poll responses.
977
+ let err = core.poll_activity_task().await.unwrap_err();
978
+ assert_matches!(err, PollActivityError::ShutDown);
944
979
  };
945
980
  // This wf poll should *not* set the flag that it wants tasks back since both slots are
946
981
  // occupied
@@ -974,7 +1009,7 @@ async fn retryable_net_error_exhaustion_is_nonfatal() {
974
1009
  })
975
1010
  .await
976
1011
  .unwrap();
977
- core.shutdown().await;
1012
+ core.drain_activity_poller_and_shutdown().await;
978
1013
  }
979
1014
 
980
1015
  #[tokio::test]