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,23 +1,21 @@
1
1
  use crate::telemetry::default_resource;
2
2
  use hyper::{
3
3
  header::CONTENT_TYPE,
4
+ server::conn::AddrIncoming,
4
5
  service::{make_service_fn, service_fn},
5
6
  Body, Method, Request, Response, Server,
6
7
  };
7
- use opentelemetry::{
8
- metrics::MetricsError,
9
- sdk::{
10
- export::metrics::{aggregation::TemporalitySelector, AggregatorSelector},
11
- metrics::{controllers, processors},
12
- },
8
+ use opentelemetry::sdk::{
9
+ export::metrics::{aggregation::TemporalitySelector, AggregatorSelector},
10
+ metrics::{controllers, processors},
13
11
  };
14
12
  use opentelemetry_prometheus::{ExporterBuilder, PrometheusExporter};
15
13
  use prometheus::{Encoder, TextEncoder};
16
- use std::{convert::Infallible, net::SocketAddr, sync::Arc};
14
+ use std::{convert::Infallible, net::SocketAddr, sync::Arc, time::Duration};
17
15
 
18
16
  /// Exposes prometheus metrics for scraping
19
17
  pub(super) struct PromServer {
20
- addr: SocketAddr,
18
+ bound_addr: AddrIncoming,
21
19
  pub exporter: Arc<PrometheusExporter>,
22
20
  }
23
21
 
@@ -26,19 +24,22 @@ impl PromServer {
26
24
  addr: SocketAddr,
27
25
  aggregation: impl AggregatorSelector + Send + Sync + 'static,
28
26
  temporality: impl TemporalitySelector + Send + Sync + 'static,
29
- ) -> Result<Self, MetricsError> {
27
+ ) -> Result<Self, anyhow::Error> {
30
28
  let controller =
31
29
  controllers::basic(processors::factory(aggregation, temporality).with_memory(true))
30
+ // Because Prom is pull-based, make this always refresh
31
+ .with_collect_period(Duration::from_secs(0))
32
32
  .with_resource(default_resource())
33
33
  .build();
34
34
  let exporter = ExporterBuilder::new(controller).try_init()?;
35
+ let bound_addr = AddrIncoming::bind(&addr)?;
35
36
  Ok(Self {
36
37
  exporter: Arc::new(exporter),
37
- addr,
38
+ bound_addr,
38
39
  })
39
40
  }
40
41
 
41
- pub async fn run(&self) -> hyper::Result<()> {
42
+ pub async fn run(self) -> hyper::Result<()> {
42
43
  // Spin up hyper server to serve metrics for scraping. We use hyper since we already depend
43
44
  // on it via Tonic.
44
45
  let expclone = self.exporter.clone();
@@ -46,9 +47,13 @@ impl PromServer {
46
47
  let expclone = expclone.clone();
47
48
  async move { Ok::<_, Infallible>(service_fn(move |req| metrics_req(req, expclone.clone()))) }
48
49
  });
49
- let server = Server::bind(&self.addr).serve(svc);
50
+ let server = Server::builder(self.bound_addr).serve(svc);
50
51
  server.await
51
52
  }
53
+
54
+ pub fn bound_addr(&self) -> SocketAddr {
55
+ self.bound_addr.local_addr()
56
+ }
52
57
  }
53
58
 
54
59
  /// Serves prometheus metrics in the expected format for scraping
@@ -14,6 +14,7 @@ use crate::{
14
14
  },
15
15
  TaskToken, Worker, WorkerConfig, WorkerConfigBuilder,
16
16
  };
17
+ use async_trait::async_trait;
17
18
  use bimap::BiMap;
18
19
  use futures::{future::BoxFuture, stream, stream::BoxStream, FutureExt, Stream, StreamExt};
19
20
  use mockall::TimesRange;
@@ -29,6 +30,7 @@ use std::{
29
30
  task::{Context, Poll},
30
31
  time::Duration,
31
32
  };
33
+ use temporal_sdk_core_api::errors::{PollActivityError, PollWfError};
32
34
  use temporal_sdk_core_api::Worker as WorkerTrait;
33
35
  use temporal_sdk_core_protos::{
34
36
  coresdk::{
@@ -52,7 +54,6 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
52
54
  use tokio_util::sync::CancellationToken;
53
55
 
54
56
  pub const TEST_Q: &str = "q";
55
- pub static NO_MORE_WORK_ERROR_MSG: &str = "No more work to do";
56
57
 
57
58
  pub fn test_worker_cfg() -> WorkerConfigBuilder {
58
59
  let mut wcb = WorkerConfigBuilder::default();
@@ -148,6 +149,7 @@ pub(crate) fn mock_worker(mocks: MocksHolder) -> Worker {
148
149
  mocks.inputs.wft_stream,
149
150
  act_poller,
150
151
  MetricsContext::no_op(),
152
+ None,
151
153
  CancellationToken::new(),
152
154
  )
153
155
  }
@@ -301,7 +303,7 @@ where
301
303
  }
302
304
  .boxed()
303
305
  } else {
304
- async { Some(Err(tonic::Status::cancelled(NO_MORE_WORK_ERROR_MSG))) }.boxed()
306
+ async { None }.boxed()
305
307
  }
306
308
  });
307
309
  Box::new(mock_poller) as BoxedPoller<T>
@@ -375,6 +377,7 @@ pub(crate) struct MockPollCfg {
375
377
  pub expect_fail_wft_matcher:
376
378
  Box<dyn Fn(&TaskToken, &WorkflowTaskFailedCause, &Option<Failure>) -> bool + Send>,
377
379
  pub completion_asserts: Option<Box<dyn Fn(&WorkflowTaskCompletion) + Send>>,
380
+ pub num_expected_completions: Option<TimesRange>,
378
381
  /// If being used with the Rust SDK, this is set true. It ensures pollers will not error out
379
382
  /// early with no work, since we cannot know the exact number of times polling will happen.
380
383
  /// Instead, they will just block forever.
@@ -396,6 +399,7 @@ impl MockPollCfg {
396
399
  mock_client: mock_workflow_client(),
397
400
  expect_fail_wft_matcher: Box::new(|_, _, _| true),
398
401
  completion_asserts: None,
402
+ num_expected_completions: None,
399
403
  using_rust_sdk: false,
400
404
  make_poll_stream_interminable: false,
401
405
  }
@@ -418,6 +422,7 @@ impl MockPollCfg {
418
422
  mock_client,
419
423
  expect_fail_wft_matcher: Box::new(|_, _, _| true),
420
424
  completion_asserts: None,
425
+ num_expected_completions: None,
421
426
  using_rust_sdk: false,
422
427
  make_poll_stream_interminable: false,
423
428
  }
@@ -587,16 +592,20 @@ pub(crate) fn build_mock_pollers(mut cfg: MockPollCfg) -> MocksHolder {
587
592
  );
588
593
 
589
594
  let outstanding = outstanding_wf_task_tokens.clone();
590
- cfg.mock_client
591
- .expect_complete_workflow_task()
592
- .returning(move |comp| {
593
- if let Some(ass) = cfg.completion_asserts.as_ref() {
594
- // tee hee
595
- ass(&comp)
596
- }
597
- outstanding.release_token(&comp.task_token);
598
- Ok(RespondWorkflowTaskCompletedResponse::default())
599
- });
595
+ let expect_completes = cfg.mock_client.expect_complete_workflow_task();
596
+ if let Some(range) = cfg.num_expected_completions {
597
+ expect_completes.times(range);
598
+ } else if cfg.completion_asserts.is_some() {
599
+ expect_completes.times(1..);
600
+ }
601
+ expect_completes.returning(move |comp| {
602
+ if let Some(ass) = cfg.completion_asserts.as_ref() {
603
+ // tee hee
604
+ ass(&comp)
605
+ }
606
+ outstanding.release_token(&comp.task_token);
607
+ Ok(RespondWorkflowTaskCompletedResponse::default())
608
+ });
600
609
  let outstanding = outstanding_wf_task_tokens.clone();
601
610
  cfg.mock_client
602
611
  .expect_fail_workflow_task()
@@ -842,6 +851,7 @@ pub(crate) fn gen_assert_and_fail(asserter: &dyn Fn(&WorkflowActivation)) -> Ass
842
851
  message: "Intentional test failure".to_string(),
843
852
  ..Default::default()
844
853
  }),
854
+ ..Default::default()
845
855
  }
846
856
  .into(),
847
857
  )
@@ -887,3 +897,43 @@ macro_rules! prost_dur {
887
897
  .expect("test duration fits")
888
898
  };
889
899
  }
900
+
901
+ #[async_trait]
902
+ pub(crate) trait WorkerExt {
903
+ /// Initiate shutdown, drain the pollers, and wait for shutdown to complete.
904
+ async fn drain_pollers_and_shutdown(self);
905
+ /// Initiate shutdown, drain the *activity* poller, and wait for shutdown to complete.
906
+ /// Takes a ref because of that one test that needs it.
907
+ async fn drain_activity_poller_and_shutdown(&self);
908
+ }
909
+
910
+ #[async_trait]
911
+ impl WorkerExt for Worker {
912
+ async fn drain_pollers_and_shutdown(self) {
913
+ self.initiate_shutdown();
914
+ tokio::join!(
915
+ async {
916
+ assert_matches!(
917
+ self.poll_activity_task().await.unwrap_err(),
918
+ PollActivityError::ShutDown
919
+ );
920
+ },
921
+ async {
922
+ assert_matches!(
923
+ self.poll_workflow_activation().await.unwrap_err(),
924
+ PollWfError::ShutDown
925
+ );
926
+ }
927
+ );
928
+ self.finalize_shutdown().await;
929
+ }
930
+
931
+ async fn drain_activity_poller_and_shutdown(&self) {
932
+ self.initiate_shutdown();
933
+ assert_matches!(
934
+ self.poll_activity_task().await.unwrap_err(),
935
+ PollActivityError::ShutDown
936
+ );
937
+ self.shutdown().await;
938
+ }
939
+ }
@@ -26,9 +26,6 @@ use tokio_util::sync::CancellationToken;
26
26
  /// Used to supply new heartbeat events to the activity heartbeat manager, or to send a shutdown
27
27
  /// request.
28
28
  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
29
  shutdown_token: CancellationToken,
33
30
  /// Used during `shutdown` to await until all inflight requests are sent.
34
31
  join_handle: Mutex<Option<JoinHandle<()>>>,
@@ -74,15 +71,118 @@ pub enum ActivityHeartbeatError {
74
71
  /// to heartbeat.
75
72
  #[error("Unable to parse activity heartbeat timeout.")]
76
73
  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
74
  }
81
75
 
82
76
  /// Manages activity heartbeating for a worker. Allows sending new heartbeats or requesting and
83
77
  /// awaiting for the shutdown. When shutdown is requested, signal gets sent to all processors, which
84
78
  /// allows them to complete gracefully.
85
79
  impl ActivityHeartbeatManager {
80
+ /// Creates a new instance of an activity heartbeat manager and returns a handle to the user,
81
+ /// which allows to send new heartbeats and initiate the shutdown.
82
+ /// Returns the manager and a channel that buffers cancellation notifications to be sent to Lang.
83
+ pub(super) fn new(
84
+ client: Arc<dyn WorkerClient>,
85
+ ) -> (Self, UnboundedReceiver<PendingActivityCancel>) {
86
+ let (heartbeat_stream_state, heartbeat_tx_source, shutdown_token) =
87
+ HeartbeatStreamState::new();
88
+ let (cancels_tx, cancels_rx) = unbounded_channel();
89
+ let heartbeat_tx = heartbeat_tx_source.clone();
90
+
91
+ let join_handle = tokio::spawn(
92
+ // The stream of incoming heartbeats uses unfold to carry state across each item in the
93
+ // stream. The closure checks if, for any given activity, we should heartbeat or not
94
+ // depending on its delay and when we last issued a heartbeat for it.
95
+ futures::stream::unfold(heartbeat_stream_state, move |mut hb_states| {
96
+ async move {
97
+ let hb = tokio::select! {
98
+ biased;
99
+
100
+ _ = hb_states.cancellation_token.cancelled() => {
101
+ return None
102
+ }
103
+ hb = hb_states.incoming_hbs.recv() => match hb {
104
+ None => return None,
105
+ Some(hb) => hb,
106
+ }
107
+ };
108
+
109
+ Some((
110
+ match hb {
111
+ HeartbeatAction::SendHeartbeat(hb) => hb_states.record(hb),
112
+ HeartbeatAction::CompleteReport(tt) => hb_states.handle_report_completed(tt),
113
+ HeartbeatAction::CompleteThrottle(tt) => hb_states.handle_throttle_completed(tt),
114
+ HeartbeatAction::Evict{ token, on_complete } => hb_states.evict(token, on_complete),
115
+ },
116
+ hb_states,
117
+ ))
118
+ }
119
+ })
120
+ // Filters out `None`s
121
+ .filter_map(|opt| async { opt })
122
+ .for_each_concurrent(None, move |action| {
123
+ let heartbeat_tx = heartbeat_tx_source.clone();
124
+ let sg = client.clone();
125
+ let cancels_tx = cancels_tx.clone();
126
+ async move {
127
+ match action {
128
+ HeartbeatExecutorAction::Sleep(tt, duration, cancellation_token) => {
129
+ tokio::select! {
130
+ _ = cancellation_token.cancelled() => (),
131
+ _ = tokio::time::sleep(duration) => {
132
+ let _ = heartbeat_tx.send(HeartbeatAction::CompleteThrottle(tt));
133
+ },
134
+ };
135
+ }
136
+ HeartbeatExecutorAction::Report { task_token: tt, details } => {
137
+ match sg
138
+ .record_activity_heartbeat(tt.clone(), details.into_payloads())
139
+ .await
140
+ {
141
+ Ok(RecordActivityTaskHeartbeatResponse { cancel_requested }) => {
142
+ if cancel_requested {
143
+ cancels_tx
144
+ .send(PendingActivityCancel::new(
145
+ tt.clone(),
146
+ ActivityCancelReason::Cancelled,
147
+ ))
148
+ .expect(
149
+ "Receive half of heartbeat cancels not blocked",
150
+ );
151
+ }
152
+ }
153
+ // Send cancels for any activity that learns its workflow already
154
+ // finished (which is one thing not found implies - other reasons
155
+ // would seem equally valid).
156
+ Err(s) if s.code() == tonic::Code::NotFound => {
157
+ debug!(task_token = %tt,
158
+ "Activity not found when recording heartbeat");
159
+ cancels_tx
160
+ .send(PendingActivityCancel::new(
161
+ tt.clone(),
162
+ ActivityCancelReason::NotFound,
163
+ ))
164
+ .expect("Receive half of heartbeat cancels not blocked");
165
+ }
166
+ Err(e) => {
167
+ warn!("Error when recording heartbeat: {:?}", e);
168
+ }
169
+ };
170
+ let _ = heartbeat_tx.send(HeartbeatAction::CompleteReport(tt));
171
+ }
172
+ }
173
+ }
174
+ }),
175
+ );
176
+
177
+ (
178
+ Self {
179
+ join_handle: Mutex::new(Some(join_handle)),
180
+ shutdown_token,
181
+ heartbeat_tx,
182
+ },
183
+ cancels_rx,
184
+ )
185
+ }
86
186
  /// Records a new heartbeat, the first call will result in an immediate call to the server,
87
187
  /// while rapid successive calls would accumulate for up to `delay` and then latest heartbeat
88
188
  /// details will be sent to the server.
@@ -95,9 +195,6 @@ impl ActivityHeartbeatManager {
95
195
  hb: ActivityHeartbeat,
96
196
  throttle_interval: Duration,
97
197
  ) -> Result<(), ActivityHeartbeatError> {
98
- if self.shutdown_token.is_cancelled() {
99
- return Err(ActivityHeartbeatError::ShuttingDown);
100
- }
101
198
  self.heartbeat_tx
102
199
  .send(HeartbeatAction::SendHeartbeat(ValidActivityHeartbeat {
103
200
  task_token: TaskToken(hb.task_token),
@@ -121,13 +218,6 @@ impl ActivityHeartbeatManager {
121
218
  completed.notified().await;
122
219
  }
123
220
 
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
221
  /// Initiates shutdown procedure by stopping lifecycle loop and awaiting for all in-flight
132
222
  /// heartbeat requests to be flushed to the server.
133
223
  pub(super) async fn shutdown(&self) {
@@ -301,110 +391,6 @@ impl HeartbeatStreamState {
301
391
  }
302
392
  }
303
393
 
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
394
  #[cfg(test)]
409
395
  mod test {
410
396
  use super::*;
@@ -425,7 +411,7 @@ mod test {
425
411
  .expect_record_activity_heartbeat()
426
412
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
427
413
  .times(2);
428
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
414
+ let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
429
415
  let fake_task_token = vec![1, 2, 3];
430
416
  // Send 2 heartbeat requests for 20ms apart.
431
417
  // The first heartbeat should be sent right away, and
@@ -446,14 +432,13 @@ mod test {
446
432
  .expect_record_activity_heartbeat()
447
433
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
448
434
  .times(3);
449
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
435
+ let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
450
436
  let fake_task_token = vec![1, 2, 3];
451
437
  // Heartbeats always get sent if recorded less frequently than the throttle interval
452
438
  for i in 0_u8..3 {
453
439
  record_heartbeat(&hm, fake_task_token.clone(), i, Duration::from_millis(10));
454
440
  sleep(Duration::from_millis(20)).await;
455
441
  }
456
- // sleep again to let heartbeats be flushed
457
442
  hm.shutdown().await;
458
443
  }
459
444
 
@@ -466,7 +451,7 @@ mod test {
466
451
  .expect_record_activity_heartbeat()
467
452
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
468
453
  .times(1);
469
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
454
+ let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
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,7 @@ 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 (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
489
474
  let fake_task_token = vec![1, 2, 3];
490
475
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
491
476
  sleep(Duration::from_millis(500)).await;
@@ -502,7 +487,7 @@ mod test {
502
487
  .expect_record_activity_heartbeat()
503
488
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
504
489
  .times(2);
505
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
490
+ let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
506
491
  let fake_task_token = vec![1, 2, 3];
507
492
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
508
493
  // Let it propagate
@@ -522,42 +507,13 @@ mod test {
522
507
  .expect_record_activity_heartbeat()
523
508
  .returning(|_, _| Ok(RecordActivityTaskHeartbeatResponse::default()))
524
509
  .times(1);
525
- let hm = ActivityHeartbeatManager::new(Arc::new(mock_client));
510
+ let (hm, _) = ActivityHeartbeatManager::new(Arc::new(mock_client));
526
511
  let fake_task_token = vec![1, 2, 3];
527
512
  record_heartbeat(&hm, fake_task_token.clone(), 0, Duration::from_millis(100));
528
513
  hm.evict(fake_task_token.clone().into()).await;
529
514
  hm.shutdown().await;
530
515
  }
531
516
 
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
517
  fn record_heartbeat(
562
518
  hm: &ActivityHeartbeatManager,
563
519
  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
+ }