@temporalio/core-bridge 1.6.0 → 1.7.1

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 +520 -456
  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 +27 -9
  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 +212 -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 +141 -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 +137 -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 +187 -46
  58. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +237 -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 +81 -62
  62. package/sdk-core/core/src/worker/workflow/mod.rs +341 -79
  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 +75 -52
  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 +16 -25
  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
@@ -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
  }
@@ -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,
@@ -13,7 +13,8 @@ use futures::FutureExt;
13
13
  use itertools::Itertools;
14
14
  use std::{
15
15
  cell::RefCell,
16
- collections::{hash_map::Entry, HashMap, VecDeque},
16
+ collections::{hash_map::Entry, HashMap, HashSet, VecDeque},
17
+ future,
17
18
  rc::Rc,
18
19
  sync::{
19
20
  atomic::{AtomicUsize, Ordering},
@@ -23,14 +24,17 @@ 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::{
30
34
  activity_execution_result, activity_resolution, ActivityExecutionResult,
31
35
  ActivityResolution, Success,
32
36
  },
33
- activity_task::{activity_task, ActivityTask},
37
+ activity_task::{activity_task, ActivityCancelReason, ActivityTask, Cancel},
34
38
  workflow_activation::{workflow_activation_job, ResolveActivity, WorkflowActivationJob},
35
39
  workflow_commands::{
36
40
  ActivityCancellationType, CompleteWorkflowExecution, RequestCancelActivity,
@@ -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,11 +59,10 @@ 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
- #[tokio::test]
57
- async fn max_activities_respected() {
58
- let _task_q = "q";
59
- let mut tasks = VecDeque::from(vec![
64
+ fn three_tasks() -> VecDeque<PollActivityTaskQueueResponse> {
65
+ VecDeque::from(vec![
60
66
  PollActivityTaskQueueResponse {
61
67
  task_token: vec![1],
62
68
  activity_id: "act1".to_string(),
@@ -72,7 +78,13 @@ async fn max_activities_respected() {
72
78
  activity_id: "act3".to_string(),
73
79
  ..Default::default()
74
80
  },
75
- ]);
81
+ ])
82
+ }
83
+
84
+ #[tokio::test]
85
+ async fn max_activities_respected() {
86
+ let _task_q = "q";
87
+ let mut tasks = three_tasks();
76
88
  let mut mock_client = mock_workflow_client();
77
89
  mock_client
78
90
  .expect_poll_activity_task()
@@ -121,7 +133,7 @@ async fn activity_not_found_returns_ok() {
121
133
  })
122
134
  .await
123
135
  .unwrap();
124
- core.shutdown().await;
136
+ core.drain_activity_poller_and_shutdown().await;
125
137
  }
126
138
 
127
139
  #[tokio::test]
@@ -217,12 +229,14 @@ async fn heartbeats_report_cancels_only_once() {
217
229
  })
218
230
  .await
219
231
  .unwrap();
220
- core.shutdown().await;
232
+ core.drain_activity_poller_and_shutdown().await;
221
233
  }
222
234
 
223
235
  #[tokio::test]
224
236
  async fn activity_cancel_interrupts_poll() {
225
237
  let mut mock_poller = mock_manual_poller();
238
+ let shutdown_token = CancellationToken::new();
239
+ let shutdown_token_clone = shutdown_token.clone();
226
240
  let mut poll_resps = VecDeque::from(vec![
227
241
  async {
228
242
  Some(Ok(PollActivityTaskQueueResponse {
@@ -237,10 +251,15 @@ async fn activity_cancel_interrupts_poll() {
237
251
  Some(Ok(Default::default()))
238
252
  }
239
253
  .boxed(),
254
+ async move {
255
+ shutdown_token.cancelled().await;
256
+ None
257
+ }
258
+ .boxed(),
240
259
  ]);
241
260
  mock_poller
242
261
  .expect_poll()
243
- .times(2)
262
+ .times(3)
244
263
  .returning(move || poll_resps.pop_front().unwrap());
245
264
 
246
265
  let mut mock_client = mock_manual_workflow_client();
@@ -289,11 +308,12 @@ async fn activity_cancel_interrupts_poll() {
289
308
  }
290
309
  ).await.unwrap();
291
310
  last_finisher.store(2, Ordering::SeqCst);
311
+ shutdown_token_clone.cancel();
292
312
  }
293
313
  };
294
314
  // So that we know we blocked
295
315
  assert_eq!(last_finisher.load(Ordering::Acquire), 2);
296
- core.shutdown().await;
316
+ core.drain_activity_poller_and_shutdown().await;
297
317
  }
298
318
 
299
319
  #[tokio::test]
@@ -342,13 +362,10 @@ async fn many_concurrent_heartbeat_cancels() {
342
362
  })
343
363
  .collect::<Vec<_>>(),
344
364
  );
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
365
  poll_resps.push_back(
349
366
  async {
350
- sleep(Duration::from_secs(10)).await;
351
- unreachable!("Long poll")
367
+ future::pending::<()>().await;
368
+ unreachable!()
352
369
  }
353
370
  .boxed(),
354
371
  );
@@ -431,7 +448,7 @@ async fn many_concurrent_heartbeat_cancels() {
431
448
  })
432
449
  .await;
433
450
 
434
- worker.shutdown().await;
451
+ worker.drain_activity_poller_and_shutdown().await;
435
452
  }
436
453
 
437
454
  #[tokio::test]
@@ -483,7 +500,7 @@ async fn activity_timeout_no_double_resolve() {
483
500
  )
484
501
  .await;
485
502
 
486
- core.shutdown().await;
503
+ core.drain_pollers_and_shutdown().await;
487
504
  }
488
505
 
489
506
  #[tokio::test]
@@ -529,7 +546,7 @@ async fn can_heartbeat_acts_during_shutdown() {
529
546
  })
530
547
  .await
531
548
  .unwrap();
532
- shutdown_fut.await;
549
+ core.drain_activity_poller_and_shutdown().await;
533
550
  }
534
551
 
535
552
  /// Verifies that if a user has tried to record a heartbeat and then immediately after failed the
@@ -580,7 +597,7 @@ async fn complete_act_with_fail_flushes_heartbeat() {
580
597
  })
581
598
  .await
582
599
  .unwrap();
583
- core.shutdown().await;
600
+ core.drain_activity_poller_and_shutdown().await;
584
601
 
585
602
  // Verify the last seen call to record a heartbeat had the last detail payload
586
603
  let last_seen_payload = &last_seen_payload.take().unwrap().payloads[0];
@@ -686,7 +703,7 @@ async fn no_eager_activities_requested_when_worker_options_disable_remote_activi
686
703
  .await
687
704
  .unwrap();
688
705
 
689
- core.shutdown().await;
706
+ core.drain_pollers_and_shutdown().await;
690
707
 
691
708
  assert_eq!(num_eager_requested.load(Ordering::Relaxed), 0);
692
709
  }
@@ -753,11 +770,8 @@ async fn activity_tasks_from_completion_are_delivered() {
753
770
  .times(3)
754
771
  .returning(|_, _| Ok(RespondActivityTaskCompletedResponse::default()));
755
772
  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));
773
+ let act_tasks: Vec<QueueResponse<PollActivityTaskQueueResponse>> = vec![];
774
+ mock.set_act_poller(mock_poller_from_resps(act_tasks));
761
775
  mock.worker_cfg(|wc| wc.max_cached_workflows = 2);
762
776
  let core = mock_worker(mock);
763
777
 
@@ -816,7 +830,7 @@ async fn activity_tasks_from_completion_are_delivered() {
816
830
  .unwrap();
817
831
  }
818
832
 
819
- core.shutdown().await;
833
+ core.drain_pollers_and_shutdown().await;
820
834
 
821
835
  // Verify only a single eager activity was scheduled (the one on our worker's task queue)
822
836
  assert_eq!(num_eager_requested.load(Ordering::Relaxed), 3);
@@ -828,11 +842,23 @@ async fn activity_tasks_from_completion_reserve_slots() {
828
842
  let mut t = TestHistoryBuilder::default();
829
843
  t.add_by_type(EventType::WorkflowExecutionStarted);
830
844
  t.add_full_wf_task();
831
- let schedid = t.add_activity_task_scheduled("1");
845
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
846
+ ActivityTaskScheduledEventAttributes {
847
+ activity_id: "1".to_string(),
848
+ activity_type: Some("act1".into()),
849
+ ..Default::default()
850
+ },
851
+ ));
832
852
  let startid = t.add_activity_task_started(schedid);
833
853
  t.add_activity_task_completed(schedid, startid, b"hi".into());
834
854
  t.add_full_wf_task();
835
- let schedid = t.add_activity_task_scheduled("2");
855
+ let schedid = t.add(EventAttributes::ActivityTaskScheduledEventAttributes(
856
+ ActivityTaskScheduledEventAttributes {
857
+ activity_id: "2".to_string(),
858
+ activity_type: Some("act2".into()),
859
+ ..Default::default()
860
+ },
861
+ ));
836
862
  let startid = t.add_activity_task_started(schedid);
837
863
  t.add_activity_task_completed(schedid, startid, b"hi".into());
838
864
  t.add_full_wf_task();
@@ -902,19 +928,25 @@ async fn activity_tasks_from_completion_reserve_slots() {
902
928
  // First poll for activities twice, occupying both slots
903
929
  let at1 = core.poll_activity_task().await.unwrap();
904
930
  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())
931
+ let workflow_complete_token = CancellationToken::new();
932
+ let workflow_complete_token_clone = workflow_complete_token.clone();
933
+
934
+ worker.register_wf(DEFAULT_WORKFLOW_TYPE, move |ctx: WfContext| {
935
+ let complete_token = workflow_complete_token.clone();
936
+ async move {
937
+ ctx.activity(ActivityOptions {
938
+ activity_type: "act1".to_string(),
939
+ ..Default::default()
940
+ })
941
+ .await;
942
+ ctx.activity(ActivityOptions {
943
+ activity_type: "act2".to_string(),
944
+ ..Default::default()
945
+ })
946
+ .await;
947
+ complete_token.cancel();
948
+ Ok(().into())
949
+ }
918
950
  });
919
951
 
920
952
  worker
@@ -941,6 +973,13 @@ async fn activity_tasks_from_completion_reserve_slots() {
941
973
  .await
942
974
  .unwrap();
943
975
  barr.wait().await;
976
+ // Wait for workflow to complete in order for all eager activities to be requested before shutting down.
977
+ // After shutdown, no eager activities slots can be allocated.
978
+ workflow_complete_token_clone.cancelled().await;
979
+ core.initiate_shutdown();
980
+ // Even though this test requests eager activity tasks, none are returned in poll responses.
981
+ let err = core.poll_activity_task().await.unwrap_err();
982
+ assert_matches!(err, PollActivityError::ShutDown);
944
983
  };
945
984
  // This wf poll should *not* set the flag that it wants tasks back since both slots are
946
985
  // occupied
@@ -974,7 +1013,7 @@ async fn retryable_net_error_exhaustion_is_nonfatal() {
974
1013
  })
975
1014
  .await
976
1015
  .unwrap();
977
- core.shutdown().await;
1016
+ core.drain_activity_poller_and_shutdown().await;
978
1017
  }
979
1018
 
980
1019
  #[tokio::test]
@@ -1012,3 +1051,56 @@ async fn cant_complete_activity_with_unset_result_payload() {
1012
1051
  Err(CompleteActivityError::MalformedActivityCompletion { .. })
1013
1052
  )
1014
1053
  }
1054
+
1055
+ #[tokio::test]
1056
+ async fn graceful_shutdown() {
1057
+ let _task_q = "q";
1058
+ let mut tasks = three_tasks();
1059
+ let mut mock_client = mock_workflow_client();
1060
+ mock_client
1061
+ .expect_poll_activity_task()
1062
+ .times(3)
1063
+ .returning(move |_, _| Ok(tasks.pop_front().unwrap()));
1064
+ // They shall all be reported as failed
1065
+ mock_client
1066
+ .expect_fail_activity_task()
1067
+ .times(3)
1068
+ .returning(|_, _| Ok(Default::default()));
1069
+
1070
+ let worker = Worker::new_test(
1071
+ test_worker_cfg()
1072
+ .graceful_shutdown_period(Duration::from_millis(500))
1073
+ .build()
1074
+ .unwrap(),
1075
+ mock_client,
1076
+ );
1077
+
1078
+ let _1 = worker.poll_activity_task().await.unwrap();
1079
+ let _2 = worker.poll_activity_task().await.unwrap();
1080
+ let _3 = worker.poll_activity_task().await.unwrap();
1081
+
1082
+ worker.initiate_shutdown();
1083
+ let expected_tts = HashSet::from([vec![1], vec![2], vec![3]]);
1084
+ let mut seen_tts = HashSet::new();
1085
+ for _ in 1..=3 {
1086
+ let cancel = worker.poll_activity_task().await.unwrap();
1087
+ assert_matches!(
1088
+ cancel.variant,
1089
+ Some(activity_task::Variant::Cancel(Cancel {
1090
+ reason: r
1091
+ })) if r == ActivityCancelReason::WorkerShutdown as i32
1092
+ );
1093
+ seen_tts.insert(cancel.task_token);
1094
+ }
1095
+ assert_eq!(expected_tts, seen_tts);
1096
+ for tt in seen_tts {
1097
+ worker
1098
+ .complete_activity_task(ActivityTaskCompletion {
1099
+ task_token: tt,
1100
+ result: Some(ActivityExecutionResult::cancel_from_details(None)),
1101
+ })
1102
+ .await
1103
+ .unwrap();
1104
+ }
1105
+ worker.drain_pollers_and_shutdown().await;
1106
+ }
@@ -88,11 +88,12 @@ async fn parent_cancels_child_wf(ctx: WfContext) -> WorkflowResult<()> {
88
88
  .await
89
89
  .into_started()
90
90
  .expect("Child should get started");
91
- let cancel_fut = start_res.cancel(&ctx);
92
- let resfut = start_res.result();
93
- let (cancel_res, res) = join!(cancel_fut, resfut);
94
- cancel_res.expect("cancel result is ok");
95
- let stat = res.status.expect("child wf result is ok");
91
+ start_res.cancel(&ctx);
92
+ let stat = start_res
93
+ .result()
94
+ .await
95
+ .status
96
+ .expect("child wf result is ok");
96
97
  assert_matches!(stat, child_workflow_result::Status::Cancelled(_));
97
98
  Ok(().into())
98
99
  }