@temporalio/core-bridge 1.5.2 → 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 (194) hide show
  1. package/Cargo.lock +304 -112
  2. package/lib/index.d.ts +8 -6
  3. package/lib/index.js.map +1 -1
  4. package/package.json +9 -4
  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 +2 -4
  13. package/sdk-core/.cargo/config.toml +5 -2
  14. package/sdk-core/.github/workflows/heavy.yml +29 -0
  15. package/sdk-core/Cargo.toml +1 -1
  16. package/sdk-core/README.md +20 -10
  17. package/sdk-core/client/src/lib.rs +215 -39
  18. package/sdk-core/client/src/metrics.rs +17 -8
  19. package/sdk-core/client/src/raw.rs +4 -4
  20. package/sdk-core/client/src/retry.rs +32 -20
  21. package/sdk-core/core/Cargo.toml +25 -12
  22. package/sdk-core/core/src/abstractions/take_cell.rs +28 -0
  23. package/sdk-core/core/src/abstractions.rs +204 -14
  24. package/sdk-core/core/src/core_tests/activity_tasks.rs +143 -50
  25. package/sdk-core/core/src/core_tests/child_workflows.rs +6 -5
  26. package/sdk-core/core/src/core_tests/determinism.rs +165 -2
  27. package/sdk-core/core/src/core_tests/local_activities.rs +431 -43
  28. package/sdk-core/core/src/core_tests/queries.rs +34 -16
  29. package/sdk-core/core/src/core_tests/workers.rs +8 -5
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +588 -55
  31. package/sdk-core/core/src/ephemeral_server/mod.rs +113 -12
  32. package/sdk-core/core/src/internal_flags.rs +155 -0
  33. package/sdk-core/core/src/lib.rs +16 -9
  34. package/sdk-core/core/src/protosext/mod.rs +1 -1
  35. package/sdk-core/core/src/replay/mod.rs +16 -27
  36. package/sdk-core/core/src/telemetry/log_export.rs +1 -1
  37. package/sdk-core/core/src/telemetry/metrics.rs +69 -35
  38. package/sdk-core/core/src/telemetry/mod.rs +60 -21
  39. package/sdk-core/core/src/telemetry/prometheus_server.rs +19 -13
  40. package/sdk-core/core/src/test_help/mod.rs +73 -14
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +119 -160
  42. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +89 -0
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +379 -129
  44. package/sdk-core/core/src/worker/activities.rs +350 -175
  45. package/sdk-core/core/src/worker/client/mocks.rs +22 -2
  46. package/sdk-core/core/src/worker/client.rs +18 -2
  47. package/sdk-core/core/src/worker/mod.rs +183 -64
  48. package/sdk-core/core/src/worker/workflow/bridge.rs +1 -3
  49. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -5
  50. package/sdk-core/core/src/worker/workflow/history_update.rs +916 -277
  51. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +216 -183
  52. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +9 -12
  53. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +7 -9
  54. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +160 -87
  55. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +13 -14
  56. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +7 -9
  57. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +14 -17
  58. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +242 -110
  59. package/sdk-core/core/src/worker/workflow/machines/mod.rs +27 -19
  60. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +9 -11
  61. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +321 -206
  62. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +13 -18
  63. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +20 -29
  64. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +2 -2
  65. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +257 -51
  66. package/sdk-core/core/src/worker/workflow/machines/workflow_machines/local_acts.rs +6 -17
  67. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +310 -150
  68. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +17 -20
  69. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +31 -15
  70. package/sdk-core/core/src/worker/workflow/managed_run.rs +1052 -380
  71. package/sdk-core/core/src/worker/workflow/mod.rs +598 -390
  72. package/sdk-core/core/src/worker/workflow/run_cache.rs +40 -57
  73. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +137 -0
  74. package/sdk-core/core/src/worker/workflow/wft_poller.rs +1 -4
  75. package/sdk-core/core/src/worker/workflow/workflow_stream/saved_wf_inputs.rs +117 -0
  76. package/sdk-core/core/src/worker/workflow/workflow_stream/tonic_status_serde.rs +24 -0
  77. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +469 -718
  78. package/sdk-core/core-api/Cargo.toml +2 -1
  79. package/sdk-core/core-api/src/errors.rs +1 -34
  80. package/sdk-core/core-api/src/lib.rs +19 -9
  81. package/sdk-core/core-api/src/telemetry.rs +4 -6
  82. package/sdk-core/core-api/src/worker.rs +19 -1
  83. package/sdk-core/etc/deps.svg +115 -140
  84. package/sdk-core/etc/regen-depgraph.sh +5 -0
  85. package/sdk-core/fsm/rustfsm_procmacro/src/lib.rs +86 -61
  86. package/sdk-core/fsm/rustfsm_trait/src/lib.rs +29 -71
  87. package/sdk-core/histories/ends_empty_wft_complete.bin +0 -0
  88. package/sdk-core/histories/evict_while_la_running_no_interference-16_history.bin +0 -0
  89. package/sdk-core/histories/old_change_marker_format.bin +0 -0
  90. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +2 -1
  91. package/sdk-core/protos/api_upstream/Makefile +6 -6
  92. package/sdk-core/protos/api_upstream/build/go.mod +7 -0
  93. package/sdk-core/protos/api_upstream/build/go.sum +5 -0
  94. package/sdk-core/protos/api_upstream/build/tools.go +29 -0
  95. package/sdk-core/protos/api_upstream/go.mod +6 -0
  96. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +9 -2
  97. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +7 -26
  98. package/sdk-core/protos/api_upstream/temporal/api/common/v1/message.proto +13 -2
  99. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +3 -2
  100. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +3 -7
  101. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/common.proto +3 -2
  102. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +8 -8
  103. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +25 -2
  104. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/namespace.proto +2 -2
  105. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/query.proto +2 -2
  106. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/reset.proto +2 -2
  107. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/schedule.proto +2 -2
  108. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +2 -2
  109. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +24 -19
  110. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/workflow.proto +2 -2
  111. package/sdk-core/protos/api_upstream/temporal/api/errordetails/v1/message.proto +2 -2
  112. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -2
  113. package/sdk-core/protos/api_upstream/temporal/api/filter/v1/message.proto +2 -2
  114. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +49 -26
  115. package/sdk-core/protos/api_upstream/temporal/api/namespace/v1/message.proto +4 -2
  116. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +5 -2
  117. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +2 -2
  118. package/sdk-core/protos/api_upstream/temporal/api/protocol/v1/message.proto +57 -0
  119. package/sdk-core/protos/api_upstream/temporal/api/query/v1/message.proto +2 -2
  120. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +2 -2
  121. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +2 -2
  122. package/sdk-core/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +63 -0
  123. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
  124. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +71 -6
  125. package/sdk-core/protos/api_upstream/temporal/api/version/v1/message.proto +2 -2
  126. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +2 -2
  127. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +64 -28
  128. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +4 -4
  129. package/sdk-core/protos/local/temporal/sdk/core/activity_result/activity_result.proto +7 -8
  130. package/sdk-core/protos/local/temporal/sdk/core/activity_task/activity_task.proto +10 -7
  131. package/sdk-core/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +19 -30
  132. package/sdk-core/protos/local/temporal/sdk/core/common/common.proto +1 -0
  133. package/sdk-core/protos/local/temporal/sdk/core/core_interface.proto +1 -0
  134. package/sdk-core/protos/local/temporal/sdk/core/external_data/external_data.proto +8 -0
  135. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +67 -60
  136. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +85 -84
  137. package/sdk-core/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +9 -3
  138. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +2 -2
  139. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +2 -2
  140. package/sdk-core/sdk/Cargo.toml +5 -4
  141. package/sdk-core/sdk/src/lib.rs +108 -26
  142. package/sdk-core/sdk/src/workflow_context/options.rs +7 -1
  143. package/sdk-core/sdk/src/workflow_context.rs +24 -17
  144. package/sdk-core/sdk/src/workflow_future.rs +16 -15
  145. package/sdk-core/sdk-core-protos/Cargo.toml +5 -2
  146. package/sdk-core/sdk-core-protos/build.rs +36 -2
  147. package/sdk-core/sdk-core-protos/src/history_builder.rs +138 -106
  148. package/sdk-core/sdk-core-protos/src/history_info.rs +10 -1
  149. package/sdk-core/sdk-core-protos/src/lib.rs +272 -87
  150. package/sdk-core/sdk-core-protos/src/task_token.rs +12 -2
  151. package/sdk-core/test-utils/Cargo.toml +3 -1
  152. package/sdk-core/test-utils/src/canned_histories.rs +106 -296
  153. package/sdk-core/test-utils/src/histfetch.rs +1 -1
  154. package/sdk-core/test-utils/src/lib.rs +82 -23
  155. package/sdk-core/test-utils/src/wf_input_saver.rs +50 -0
  156. package/sdk-core/test-utils/src/workflows.rs +29 -0
  157. package/sdk-core/tests/fuzzy_workflow.rs +130 -0
  158. package/sdk-core/tests/{load_tests.rs → heavy_tests.rs} +125 -51
  159. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +25 -3
  160. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +10 -5
  161. package/sdk-core/tests/integ_tests/metrics_tests.rs +218 -16
  162. package/sdk-core/tests/integ_tests/polling_tests.rs +4 -47
  163. package/sdk-core/tests/integ_tests/queries_tests.rs +5 -128
  164. package/sdk-core/tests/integ_tests/visibility_tests.rs +83 -25
  165. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +161 -72
  166. package/sdk-core/tests/integ_tests/workflow_tests/cancel_external.rs +1 -0
  167. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +6 -13
  168. package/sdk-core/tests/integ_tests/workflow_tests/child_workflows.rs +80 -3
  169. package/sdk-core/tests/integ_tests/workflow_tests/continue_as_new.rs +6 -2
  170. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +3 -10
  171. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +94 -200
  172. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +2 -4
  173. package/sdk-core/tests/integ_tests/workflow_tests/patches.rs +34 -28
  174. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +76 -7
  175. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -0
  176. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +18 -14
  177. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +6 -20
  178. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +10 -21
  179. package/sdk-core/tests/integ_tests/workflow_tests/upsert_search_attrs.rs +7 -8
  180. package/sdk-core/tests/integ_tests/workflow_tests.rs +13 -14
  181. package/sdk-core/tests/main.rs +3 -13
  182. package/sdk-core/tests/runner.rs +75 -36
  183. package/sdk-core/tests/wf_input_replay.rs +32 -0
  184. package/src/conversions.rs +14 -8
  185. package/src/runtime.rs +9 -8
  186. package/ts/index.ts +8 -6
  187. package/sdk-core/bridge-ffi/Cargo.toml +0 -24
  188. package/sdk-core/bridge-ffi/LICENSE.txt +0 -23
  189. package/sdk-core/bridge-ffi/build.rs +0 -25
  190. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -224
  191. package/sdk-core/bridge-ffi/src/lib.rs +0 -746
  192. package/sdk-core/bridge-ffi/src/wrappers.rs +0 -221
  193. package/sdk-core/protos/local/temporal/sdk/core/bridge/bridge.proto +0 -210
  194. package/sdk-core/sdk/src/conversions.rs +0 -8
@@ -1,4 +1,5 @@
1
1
  use crate::{
2
+ abstractions::take_cell::TakeCell,
2
3
  worker::{activities::PendingActivityCancel, client::WorkerClient},
3
4
  TaskToken,
4
5
  };
@@ -17,7 +18,7 @@ use temporal_sdk_core_protos::{
17
18
  use tokio::{
18
19
  sync::{
19
20
  mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
20
- Mutex, Notify,
21
+ Notify,
21
22
  },
22
23
  task::JoinHandle,
23
24
  };
@@ -26,12 +27,9 @@ use tokio_util::sync::CancellationToken;
26
27
  /// Used to supply new heartbeat events to the activity heartbeat manager, or to send a shutdown
27
28
  /// request.
28
29
  pub(crate) struct ActivityHeartbeatManager {
29
- /// Cancellations that have been received when heartbeating are queued here and can be consumed
30
- /// by [fetch_cancellations]
31
- incoming_cancels: Mutex<UnboundedReceiver<PendingActivityCancel>>,
32
30
  shutdown_token: CancellationToken,
33
31
  /// Used during `shutdown` to await until all inflight requests are sent.
34
- join_handle: Mutex<Option<JoinHandle<()>>>,
32
+ join_handle: TakeCell<JoinHandle<()>>,
35
33
  heartbeat_tx: UnboundedSender<HeartbeatAction>,
36
34
  }
37
35
 
@@ -74,15 +72,115 @@ pub enum ActivityHeartbeatError {
74
72
  /// to heartbeat.
75
73
  #[error("Unable to parse activity heartbeat timeout.")]
76
74
  InvalidHeartbeatTimeout,
77
- /// Core is shutting down and thus new heartbeats are not accepted
78
- #[error("New heartbeat requests are not accepted while shutting down")]
79
- ShuttingDown,
80
75
  }
81
76
 
82
77
  /// Manages activity heartbeating for a worker. Allows sending new heartbeats or requesting and
83
78
  /// awaiting for the shutdown. When shutdown is requested, signal gets sent to all processors, which
84
79
  /// allows them to complete gracefully.
85
80
  impl ActivityHeartbeatManager {
81
+ /// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
82
+ /// which allows to send new heartbeats and initiate the shutdown.
83
+ /// Returns the manager and a channel that buffers cancellation notifications to be sent to Lang.
84
+ pub(super) fn new(
85
+ client: Arc<dyn WorkerClient>,
86
+ cancels_tx: UnboundedSender<PendingActivityCancel>,
87
+ ) -> Self {
88
+ let (heartbeat_stream_state, heartbeat_tx_source, shutdown_token) =
89
+ HeartbeatStreamState::new();
90
+ let heartbeat_tx = heartbeat_tx_source.clone();
91
+
92
+ let join_handle = tokio::spawn(
93
+ // The stream of incoming heartbeats uses unfold to carry state across each item in the
94
+ // stream. The closure checks if, for any given activity, we should heartbeat or not
95
+ // depending on its delay and when we last issued a heartbeat for it.
96
+ futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
97
+ async move {
98
+ let hb = tokio::select! {
99
+ biased;
100
+
101
+ _ = hb_states.cancellation_token.cancelled() => {
102
+ return None
103
+ }
104
+ hb = hb_states.incoming_hbs.recv() => match hb {
105
+ None => return None,
106
+ Some(hb) => hb,
107
+ }
108
+ };
109
+
110
+ Some((
111
+ match hb {
112
+ HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
113
+ HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
114
+ HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
115
+ HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
116
+ },
117
+ hb_states,
118
+ ))
119
+ }
120
+ })
121
+ // Filters out `None`s
122
+ .filter_map(|opt| async { opt })
123
+ .for_each_concurrent(None, move |action| {
124
+ let heartbeat_tx = heartbeat_tx_source.clone();
125
+ let sg = client.clone();
126
+ let cancels_tx = cancels_tx.clone();
127
+ async move {
128
+ match action {
129
+ HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
130
+ tokio::select! {
131
+ _ = cancellation_token.cancelled() => (),
132
+ _ = tokio::time::sleep(duration) => {
133
+ let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
134
+ },
135
+ };
136
+ }
137
+ HeartbeatExecutorAction::Report { task_token: tt, details } => {
138
+ match sg
139
+ .record_activity_heartbeat(tt.clone(), details.into_payloads())
140
+ .await
141
+ {
142
+ Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
143
+ if cancel_requested {
144
+ cancels_tx
145
+ .send(PendingActivityCancel::new(
146
+ tt.clone(),
147
+ ActivityCancelReason::Cancelled,
148
+ ))
149
+ .expect(
150
+ "Receive half of heartbeat cancels not blocked",
151
+ );
152
+ }
153
+ }
154
+ // Send cancels for any activity that learns its workflow already
155
+ // finished (which is one thing not found implies - other reasons
156
+ // would seem equally valid).
157
+ Err(s) if s.code() == tonic::Code::NotFound => {
158
+ debug!(task_token = %tt,
159
+ "Activity not found when recording heartbeat");
160
+ cancels_tx
161
+ .send(PendingActivityCancel::new(
162
+ tt.clone(),
163
+ ActivityCancelReason::NotFound,
164
+ ))
165
+ .expect("Receive half of heartbeat cancels not blocked");
166
+ }
167
+ Err(e) => {
168
+ warn!("Error when recording heartbeat: {:?}", e);
169
+ }
170
+ };
171
+ let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
172
+ }
173
+ }
174
+ }
175
+ }),
176
+ );
177
+
178
+ Self {
179
+ join_handle: TakeCell::new(join_handle),
180
+ shutdown_token,
181
+ heartbeat_tx,
182
+ }
183
+ }
86
184
  /// Records a new heartbeat, the first call will result in an immediate call to the server,
87
185
  /// while rapid successive calls would accumulate for up to `delay` and then latest heartbeat
88
186
  /// details will be sent to the server.
@@ -95,9 +193,6 @@ impl ActivityHeartbeatManager {
95
193
  hb: ActivityHeartbeat,
96
194
  throttle_interval: Duration,
97
195
  ) -> Result<(), ActivityHeartbeatError> {
98
- if self.shutdown_token.is_cancelled() {
99
- return Err(ActivityHeartbeatError::ShuttingDown);
100
- }
101
196
  self.heartbeat_tx
102
197
  .send(HeartbeatAction::SendHeartbeat(ValidActivityHeartbeat {
103
198
  task_token: TaskToken(hb.task_token),
@@ -121,19 +216,11 @@ impl ActivityHeartbeatManager {
121
216
  completed.notified().await;
122
217
  }
123
218
 
124
- /// Returns a future that resolves any time there is a new activity cancel that must be
125
- /// dispatched to lang
126
- pub(super) async fn next_pending_cancel(&self) -> Option<PendingActivityCancel> {
127
- self.incoming_cancels.lock().await.recv().await
128
- }
129
-
130
- // TODO: Can own self now!
131
219
  /// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all in-flight
132
220
  /// heartbeat requests to be flushed to the server.
133
221
  pub(super) async fn shutdown(&self) {
134
222
  self.shutdown_token.cancel();
135
- let mut handle = self.join_handle.lock().await;
136
- if let Some(h) = handle.take() {
223
+ if let Some(h) = self.join_handle.take_once() {
137
224
  let handle_r = h.await;
138
225
  if let Err(e) = handle_r {
139
226
  if !e.is_cancelled() {
@@ -301,110 +388,6 @@ impl HeartbeatStreamState {
301
388
  }
302
389
  }
303
390
 
304
- impl ActivityHeartbeatManager {
305
- /// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
306
- /// which allows to send new heartbeats and initiate the shutdown.
307
- pub fn new(client: Arc<dyn WorkerClient>) -> Self {
308
- let (heartbeat_stream_state, heartbeat_tx_source, shutdown_token) =
309
- HeartbeatStreamState::new();
310
- let (cancels_tx, cancels_rx) = unbounded_channel();
311
- let heartbeat_tx = heartbeat_tx_source.clone();
312
-
313
- let join_handle = tokio::spawn(
314
- // The stream of incoming heartbeats uses unfold to carry state across each item in the
315
- // stream. The closure checks if, for any given activity, we should heartbeat or not
316
- // depending on its delay and when we last issued a heartbeat for it.
317
- futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
318
- async move {
319
- let hb = tokio::select! {
320
- biased;
321
-
322
- _ = hb_states.cancellation_token.cancelled() => {
323
- return None
324
- }
325
- hb = hb_states.incoming_hbs.recv() => match hb {
326
- None => return None,
327
- Some(hb) => hb,
328
- }
329
- };
330
-
331
- Some((
332
- match hb {
333
- HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
334
- HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
335
- HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
336
- HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
337
- },
338
- hb_states,
339
- ))
340
- }
341
- })
342
- // Filters out `None`s
343
- .filter_map(|opt| async { opt })
344
- .for_each_concurrent(None, move |action| {
345
- let heartbeat_tx = heartbeat_tx_source.clone();
346
- let sg = client.clone();
347
- let cancels_tx = cancels_tx.clone();
348
- async move {
349
- match action {
350
- HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
351
- tokio::select! {
352
- _ = cancellation_token.cancelled() => (),
353
- _ = tokio::time::sleep(duration) => {
354
- let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
355
- },
356
- };
357
- }
358
- HeartbeatExecutorAction::Report { task_token: tt, details } => {
359
- match sg
360
- .record_activity_heartbeat(tt.clone(), details.into_payloads())
361
- .await
362
- {
363
- Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
364
- if cancel_requested {
365
- cancels_tx
366
- .send(PendingActivityCancel::new(
367
- tt.clone(),
368
- ActivityCancelReason::Cancelled,
369
- ))
370
- .expect(
371
- "Receive half of heartbeat cancels not blocked",
372
- );
373
- }
374
- }
375
- // Send cancels for any activity that learns its workflow already
376
- // finished (which is one thing not found implies - other reasons
377
- // would seem equally valid).
378
- Err(s) if s.code() == tonic::Code::NotFound => {
379
- debug!(task_token = %tt,
380
- "Activity not found when recording heartbeat");
381
- cancels_tx
382
- .send(PendingActivityCancel::new(
383
- tt.clone(),
384
- ActivityCancelReason::NotFound,
385
- ))
386
- .expect("Receive half of heartbeat cancels not blocked");
387
- }
388
- Err(e) => {
389
- warn!("Error when recording heartbeat: {:?}", e);
390
- }
391
- };
392
- let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
393
- }
394
- }
395
- }
396
- }),
397
- );
398
-
399
- Self {
400
- incoming_cancels: Mutex::new(cancels_rx),
401
- join_handle: Mutex::new(Some(join_handle)),
402
- shutdown_token,
403
- heartbeat_tx,
404
- }
405
- }
406
- }
407
-
408
391
  #[cfg(test)]
409
392
  mod test {
410
393
  use super::*;
@@ -425,7 +408,8 @@ mod test {
425
408
  .expect_record_activity_heartbeat()
426
409
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
427
410
  .times(2);
428
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
411
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
412
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
429
413
  let fake_task_token = vec![1, 2, 3];
430
414
  // Send 2 heartbeat requests for 20ms apart.
431
415
  // The first heartbeat should be sent right away, and
@@ -446,14 +430,14 @@ mod test {
446
430
  .expect_record_activity_heartbeat()
447
431
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
448
432
  .times(3);
449
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
433
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
434
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
450
435
  let fake_task_token = vec![1, 2, 3];
451
436
  // Heartbeats always get sent if recorded less frequently than the throttle interval
452
437
  for i in 0_u8..3 {
453
438
  record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(10));
454
439
  sleep(Duration::from_millis(20)).await;
455
440
  }
456
- // sleep again to let heartbeats be flushed
457
441
  hm.shutdown().await;
458
442
  }
459
443
 
@@ -466,7 +450,8 @@ mod test {
466
450
  .expect_record_activity_heartbeat()
467
451
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
468
452
  .times(1);
469
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
453
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
454
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
470
455
  let fake_task_token = vec![1, 2, 3];
471
456
  // Send a whole bunch of heartbeats very fast. We should still only send one total.
472
457
  for i in 0_u8..50 {
@@ -485,7 +470,8 @@ mod test {
485
470
  .expect_record_activity_heartbeat()
486
471
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
487
472
  .times(2);
488
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
473
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
474
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
489
475
  let fake_task_token = vec![1, 2, 3];
490
476
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
491
477
  sleep(Duration::from_millis(500)).await;
@@ -502,7 +488,8 @@ mod test {
502
488
  .expect_record_activity_heartbeat()
503
489
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
504
490
  .times(2);
505
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
491
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
492
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
506
493
  let fake_task_token = vec![1, 2, 3];
507
494
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
508
495
  // Let it propagate
@@ -522,42 +509,14 @@ mod test {
522
509
  .expect_record_activity_heartbeat()
523
510
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
524
511
  .times(1);
525
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
512
+ let (cancel_tx, _cancel_rx) = unbounded_channel();
513
+ let hm = ActivityHeartbeatManager::new(Arc::new(mock_client), cancel_tx);
526
514
  let fake_task_token = vec![1, 2, 3];
527
515
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
528
516
  hm.evict(fake_task_token.clone().into()).await;
529
517
  hm.shutdown().await;
530
518
  }
531
519
 
532
- /// Recording new heartbeats after shutdown is not allowed, and will result in error.
533
- #[tokio::test]
534
- async fn record_after_shutdown() {
535
- let mut mock_client = mock_workflow_client();
536
- mock_client
537
- .expect_record_activity_heartbeat()
538
- .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
539
- .times(0);
540
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
541
- hm.shutdown().await;
542
- match hm.record(
543
- ActivityHeartbeat {
544
- task_token: vec![1, 2, 3],
545
- details: vec![Payload {
546
- // payload doesn't matter in this case, as it shouldn't get sent anyways.
547
- ..Default::default()
548
- }],
549
- },
550
- Duration::from_millis(1000),
551
- ) {
552
- Ok(_) => {
553
- unreachable!("heartbeat should not be recorded after the shutdown");
554
- }
555
- Err(e) => {
556
- matches!(e, ActivityHeartbeatError::ShuttingDown);
557
- }
558
- }
559
- }
560
-
561
520
  fn record_heartbeat(
562
521
  hm: &ActivityHeartbeatManager,
563
522
  task_token: Vec<u8>,
@@ -0,0 +1,89 @@
1
+ use crate::abstractions::MeteredSemaphore;
2
+ use crate::worker::activities::PermittedTqResp;
3
+ use crate::{pollers::BoxedActPoller, MetricsContext};
4
+ use futures::{stream, Stream};
5
+ use governor::clock::DefaultClock;
6
+ use governor::middleware::NoOpMiddleware;
7
+ use governor::state::{InMemoryState, NotKeyed};
8
+ use governor::RateLimiter;
9
+ use std::sync::Arc;
10
+ use temporal_sdk_core_protos::temporal::api::workflowservice::v1::PollActivityTaskQueueResponse;
11
+ use tokio::select;
12
+ use tokio_util::sync::CancellationToken;
13
+
14
+ struct StreamState {
15
+ poller: BoxedActPoller,
16
+ semaphore: Arc<MeteredSemaphore>,
17
+ rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
18
+ metrics: MetricsContext,
19
+ shutdown_token: CancellationToken,
20
+ poller_was_shutdown: bool,
21
+ }
22
+
23
+ pub(crate) fn new_activity_task_poller(
24
+ poller: BoxedActPoller,
25
+ semaphore: Arc<MeteredSemaphore>,
26
+ rate_limiter: Option<RateLimiter<NotKeyed, InMemoryState, DefaultClock, NoOpMiddleware>>,
27
+ metrics: MetricsContext,
28
+ shutdown_token: CancellationToken,
29
+ ) -> impl Stream<Item = Result<PermittedTqResp, tonic::Status>> {
30
+ let state = StreamState {
31
+ poller,
32
+ semaphore,
33
+ rate_limiter,
34
+ metrics,
35
+ shutdown_token,
36
+ poller_was_shutdown: false,
37
+ };
38
+ stream::unfold(state, |mut state| async move {
39
+ loop {
40
+ let poll = async {
41
+ let permit = state
42
+ .semaphore
43
+ .acquire_owned()
44
+ .await
45
+ .expect("outstanding activity semaphore not closed");
46
+ if !state.poller_was_shutdown {
47
+ if let Some(ref rl) = state.rate_limiter {
48
+ rl.until_ready().await;
49
+ }
50
+ }
51
+ loop {
52
+ return match state.poller.poll().await {
53
+ Some(Ok(resp)) => {
54
+ if resp == PollActivityTaskQueueResponse::default() {
55
+ // We get the default proto in the event that the long poll times out.
56
+ debug!("Poll activity task timeout");
57
+ state.metrics.act_poll_timeout();
58
+ continue;
59
+ }
60
+ Some(Ok(PermittedTqResp { permit, resp }))
61
+ }
62
+ Some(Err(e)) => {
63
+ warn!(error=?e, "Error while polling for activity tasks");
64
+ Some(Err(e))
65
+ }
66
+ // If poller returns None, it's dead, thus we also return None to terminate this
67
+ // stream.
68
+ None => None,
69
+ };
70
+ }
71
+ };
72
+ if state.poller_was_shutdown {
73
+ return poll.await.map(|res| (res, state));
74
+ }
75
+ select! {
76
+ biased;
77
+
78
+ _ = state.shutdown_token.cancelled() => {
79
+ state.poller.notify_shutdown();
80
+ state.poller_was_shutdown = true;
81
+ continue;
82
+ }
83
+ res = poll => {
84
+ return res.map(|res| (res, state));
85
+ }
86
+ }
87
+ }
88
+ })
89
+ }