@temporalio/core-bridge 1.4.4 → 1.5.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 (123) hide show
  1. package/Cargo.lock +327 -419
  2. package/Cargo.toml +1 -1
  3. package/index.js +25 -2
  4. package/lib/errors.d.ts +22 -0
  5. package/lib/errors.js +65 -0
  6. package/lib/errors.js.map +1 -0
  7. package/lib/index.d.ts +440 -0
  8. package/lib/index.js +8 -0
  9. package/lib/index.js.map +1 -0
  10. package/package.json +11 -5
  11. package/releases/aarch64-apple-darwin/index.node +0 -0
  12. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  13. package/releases/x86_64-apple-darwin/index.node +0 -0
  14. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  15. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  16. package/sdk-core/.buildkite/docker/Dockerfile +1 -1
  17. package/sdk-core/.buildkite/docker/docker-compose.yaml +2 -2
  18. package/sdk-core/bridge-ffi/Cargo.toml +1 -1
  19. package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -25
  20. package/sdk-core/bridge-ffi/src/lib.rs +29 -108
  21. package/sdk-core/bridge-ffi/src/wrappers.rs +35 -25
  22. package/sdk-core/client/Cargo.toml +1 -1
  23. package/sdk-core/client/src/lib.rs +12 -20
  24. package/sdk-core/client/src/raw.rs +9 -8
  25. package/sdk-core/client/src/retry.rs +100 -23
  26. package/sdk-core/core/Cargo.toml +5 -5
  27. package/sdk-core/core/benches/workflow_replay.rs +13 -10
  28. package/sdk-core/core/src/abstractions.rs +22 -22
  29. package/sdk-core/core/src/core_tests/activity_tasks.rs +1 -1
  30. package/sdk-core/core/src/core_tests/local_activities.rs +228 -6
  31. package/sdk-core/core/src/core_tests/queries.rs +247 -89
  32. package/sdk-core/core/src/core_tests/workers.rs +2 -2
  33. package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
  34. package/sdk-core/core/src/core_tests/workflow_tasks.rs +46 -27
  35. package/sdk-core/core/src/lib.rs +139 -32
  36. package/sdk-core/core/src/replay/mod.rs +185 -41
  37. package/sdk-core/core/src/telemetry/log_export.rs +190 -0
  38. package/sdk-core/core/src/telemetry/metrics.rs +184 -139
  39. package/sdk-core/core/src/telemetry/mod.rs +296 -318
  40. package/sdk-core/core/src/telemetry/prometheus_server.rs +4 -3
  41. package/sdk-core/core/src/test_help/mod.rs +9 -7
  42. package/sdk-core/core/src/worker/activities/local_activities.rs +2 -1
  43. package/sdk-core/core/src/worker/activities.rs +40 -23
  44. package/sdk-core/core/src/worker/client/mocks.rs +1 -1
  45. package/sdk-core/core/src/worker/client.rs +30 -4
  46. package/sdk-core/core/src/worker/mod.rs +22 -18
  47. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +10 -19
  48. package/sdk-core/core/src/worker/workflow/history_update.rs +99 -25
  49. package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -5
  50. package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -5
  51. package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +1 -5
  52. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -5
  53. package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +1 -5
  54. package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +2 -6
  55. package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +1 -5
  56. package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +18 -21
  57. package/sdk-core/core/src/worker/workflow/machines/mod.rs +12 -38
  58. package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +178 -0
  59. package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +1 -5
  60. package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -5
  61. package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -5
  62. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +8 -2
  63. package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +1 -5
  64. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +232 -216
  65. package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +1 -6
  66. package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +4 -4
  67. package/sdk-core/core/src/worker/workflow/managed_run.rs +13 -5
  68. package/sdk-core/core/src/worker/workflow/mod.rs +61 -9
  69. package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
  70. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +56 -11
  71. package/sdk-core/core-api/Cargo.toml +4 -3
  72. package/sdk-core/core-api/src/lib.rs +1 -43
  73. package/sdk-core/core-api/src/telemetry.rs +147 -0
  74. package/sdk-core/core-api/src/worker.rs +13 -0
  75. package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +1 -1
  76. package/sdk-core/histories/evict_while_la_running_no_interference-23_history.bin +0 -0
  77. package/sdk-core/histories/evict_while_la_running_no_interference-85_history.bin +0 -0
  78. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +1 -1
  79. package/sdk-core/protos/api_upstream/buf.yaml +0 -3
  80. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +3 -7
  81. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +8 -0
  82. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -2
  83. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +2 -0
  84. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
  85. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +13 -0
  86. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +19 -59
  87. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -19
  88. package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +108 -29
  89. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
  90. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +1 -0
  91. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +47 -8
  92. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +15 -1
  93. package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
  94. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +8 -1
  95. package/sdk-core/sdk/src/interceptors.rs +36 -3
  96. package/sdk-core/sdk/src/lib.rs +7 -4
  97. package/sdk-core/sdk/src/workflow_context.rs +13 -2
  98. package/sdk-core/sdk-core-protos/src/history_builder.rs +47 -1
  99. package/sdk-core/sdk-core-protos/src/history_info.rs +22 -22
  100. package/sdk-core/sdk-core-protos/src/lib.rs +49 -27
  101. package/sdk-core/test-utils/Cargo.toml +1 -0
  102. package/sdk-core/test-utils/src/lib.rs +81 -29
  103. package/sdk-core/tests/integ_tests/metrics_tests.rs +37 -0
  104. package/sdk-core/tests/integ_tests/polling_tests.rs +0 -13
  105. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +145 -4
  106. package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +53 -0
  107. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +106 -20
  108. package/sdk-core/tests/integ_tests/workflow_tests.rs +18 -8
  109. package/sdk-core/tests/main.rs +6 -4
  110. package/src/conversions.rs +52 -47
  111. package/src/errors.rs +28 -86
  112. package/src/helpers.rs +3 -4
  113. package/src/lib.rs +2 -2
  114. package/src/runtime.rs +132 -61
  115. package/src/testing.rs +7 -4
  116. package/src/worker.rs +67 -50
  117. package/ts/errors.ts +55 -0
  118. package/{index.d.ts → ts/index.ts} +121 -15
  119. package/sdk-core/core/src/log_export.rs +0 -62
  120. package/sdk-core/core/src/worker/workflow/machines/mutable_side_effect_state_machine.rs +0 -127
  121. package/sdk-core/core/src/worker/workflow/machines/side_effect_state_machine.rs +0 -71
  122. package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +0 -83
  123. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +0 -40
@@ -6,7 +6,7 @@ extern crate tracing;
6
6
 
7
7
  pub mod canned_histories;
8
8
 
9
- use crate::stream::TryStreamExt;
9
+ use crate::stream::{Stream, TryStreamExt};
10
10
  use futures::{future, stream, stream::FuturesUnordered, StreamExt};
11
11
  use parking_lot::Mutex;
12
12
  use prost::Message;
@@ -18,14 +18,23 @@ use std::{
18
18
  use temporal_client::{
19
19
  Client, RetryClient, WorkflowClientTrait, WorkflowExecutionInfo, WorkflowOptions,
20
20
  };
21
- use temporal_sdk::{interceptors::WorkerInterceptor, IntoActivityFunc, Worker, WorkflowFunction};
21
+ use temporal_sdk::{
22
+ interceptors::{FailOnNondeterminismInterceptor, WorkerInterceptor},
23
+ IntoActivityFunc, Worker, WorkflowFunction,
24
+ };
22
25
  use temporal_sdk_core::{
23
26
  ephemeral_server::{EphemeralExe, EphemeralExeVersion},
24
- init_replay_worker, init_worker, telemetry_init, ClientOptions, ClientOptionsBuilder, Logger,
25
- MetricsExporter, OtelCollectorOptions, TelemetryOptions, TelemetryOptionsBuilder,
26
- TraceExporter, WorkerConfig, WorkerConfigBuilder,
27
+ init_replay_worker, init_worker,
28
+ replay::HistoryForReplay,
29
+ ClientOptions, ClientOptionsBuilder, CoreRuntime, WorkerConfig, WorkerConfigBuilder,
30
+ };
31
+ use temporal_sdk_core_api::{
32
+ telemetry::{
33
+ Logger, MetricsExporter, OtelCollectorOptions, TelemetryOptions, TelemetryOptionsBuilder,
34
+ TraceExportConfig, TraceExporter,
35
+ },
36
+ Worker as CoreWorker,
27
37
  };
28
- use temporal_sdk_core_api::Worker as CoreWorker;
29
38
  use temporal_sdk_core_protos::{
30
39
  coresdk::{
31
40
  workflow_commands::{
@@ -61,21 +70,43 @@ pub async fn init_core_and_create_wf(test_name: &str) -> CoreWfStarter {
61
70
  starter
62
71
  }
63
72
 
64
- /// Create a worker replay instance preloaded with a provided history. Returns the worker impl
65
- /// and the task queue name as in [init_core_and_create_wf].
66
- pub fn init_core_replay_preloaded(
67
- test_name: &str,
68
- history: &History,
69
- ) -> (Arc<dyn CoreWorker>, String) {
70
- telemetry_init(&get_integ_telem_options()).expect("Telemetry inits cleanly");
73
+ /// Create a worker replay instance preloaded with provided histories. Returns the worker impl.
74
+ pub fn init_core_replay_preloaded<I>(test_name: &str, histories: I) -> Arc<dyn CoreWorker>
75
+ where
76
+ I: IntoIterator<Item = HistoryForReplay> + 'static,
77
+ <I as IntoIterator>::IntoIter: Send,
78
+ {
79
+ init_core_replay_stream(test_name, stream::iter(histories))
80
+ }
81
+ pub fn init_core_replay_stream<I>(test_name: &str, histories: I) -> Arc<dyn CoreWorker>
82
+ where
83
+ I: Stream<Item = HistoryForReplay> + Send + 'static,
84
+ {
71
85
  let worker_cfg = WorkerConfigBuilder::default()
72
86
  .namespace(NAMESPACE)
73
87
  .task_queue(test_name)
74
88
  .worker_build_id("test_bin_id")
75
89
  .build()
76
90
  .expect("Configuration options construct properly");
77
- let worker = init_replay_worker(worker_cfg, history).expect("Replay worker must init properly");
78
- (Arc::new(worker), test_name.to_string())
91
+ let worker =
92
+ init_replay_worker(worker_cfg, histories).expect("Replay worker must init properly");
93
+ Arc::new(worker)
94
+ }
95
+ pub fn replay_sdk_worker<I>(histories: I) -> Worker
96
+ where
97
+ I: IntoIterator<Item = HistoryForReplay> + 'static,
98
+ <I as IntoIterator>::IntoIter: Send,
99
+ {
100
+ replay_sdk_worker_stream(stream::iter(histories))
101
+ }
102
+ pub fn replay_sdk_worker_stream<I>(histories: I) -> Worker
103
+ where
104
+ I: Stream<Item = HistoryForReplay> + Send + 'static,
105
+ {
106
+ let core = init_core_replay_stream("replay_worker_test", histories);
107
+ let mut worker = Worker::new_from_core(core, "replay_q".to_string());
108
+ worker.set_worker_interceptor(Box::new(FailOnNondeterminismInterceptor {}));
109
+ worker
79
110
  }
80
111
 
81
112
  /// Load history from a file containing the protobuf serialization of it
@@ -87,11 +118,21 @@ pub async fn history_from_proto_binary(path_from_root: &str) -> Result<History,
87
118
  Ok(History::decode(&*bytes)?)
88
119
  }
89
120
 
121
+ static INTEG_TESTS_RT: once_cell::sync::OnceCell<CoreRuntime> = once_cell::sync::OnceCell::new();
122
+ pub fn init_integ_telem() {
123
+ INTEG_TESTS_RT.get_or_init(|| {
124
+ let telemetry_options = get_integ_telem_options();
125
+ let rt =
126
+ CoreRuntime::new_assume_tokio(telemetry_options).expect("Core runtime inits cleanly");
127
+ let _ = tracing::subscriber::set_global_default(rt.trace_subscriber());
128
+ rt
129
+ });
130
+ }
131
+
90
132
  /// Implements a builder pattern to help integ tests initialize core and create workflows
91
133
  pub struct CoreWfStarter {
92
134
  /// Used for both the task queue and workflow id
93
135
  task_queue_name: String,
94
- telemetry_options: TelemetryOptions,
95
136
  pub worker_config: WorkerConfig,
96
137
  wft_timeout: Option<Duration>,
97
138
  initted_worker: OnceCell<InitializedWorker>,
@@ -110,9 +151,9 @@ impl CoreWfStarter {
110
151
  }
111
152
 
112
153
  pub fn new_tq_name(task_queue: &str) -> Self {
154
+ init_integ_telem();
113
155
  Self {
114
156
  task_queue_name: task_queue.to_owned(),
115
- telemetry_options: get_integ_telem_options(),
116
157
  worker_config: WorkerConfigBuilder::default()
117
158
  .namespace(NAMESPACE)
118
159
  .task_queue(task_queue)
@@ -181,19 +222,21 @@ impl CoreWfStarter {
181
222
  &mut self,
182
223
  wf_id: impl Into<String>,
183
224
  run_id: impl Into<String>,
184
- // TODO: Need not be passed in
185
225
  worker: &mut Worker,
186
226
  ) -> Result<(), anyhow::Error> {
227
+ let wf_id = wf_id.into();
187
228
  // Fetch history and replay it
188
229
  let history = self
189
230
  .get_client()
190
231
  .await
191
- .get_workflow_execution_history(wf_id.into(), Some(run_id.into()), vec![])
232
+ .get_workflow_execution_history(wf_id.clone(), Some(run_id.into()), vec![])
192
233
  .await?
193
234
  .history
194
235
  .expect("history field must be populated");
195
- let (replay_worker, _) = init_core_replay_preloaded(worker.task_queue(), &history);
236
+ let with_id = HistoryForReplay::new(history, wf_id);
237
+ let replay_worker = init_core_replay_preloaded(worker.task_queue(), [with_id]);
196
238
  worker.with_new_core_worker(replay_worker);
239
+ worker.set_worker_interceptor(Box::new(FailOnNondeterminismInterceptor {}));
197
240
  worker.run().await.unwrap();
198
241
  Ok(())
199
242
  }
@@ -239,14 +282,18 @@ impl CoreWfStarter {
239
282
  async fn get_or_init(&mut self) -> &InitializedWorker {
240
283
  self.initted_worker
241
284
  .get_or_init(|| async {
242
- telemetry_init(&self.telemetry_options).expect("Telemetry inits cleanly");
243
285
  let client = Arc::new(
244
286
  get_integ_server_options()
245
287
  .connect(self.worker_config.namespace.clone(), None, None)
246
288
  .await
247
289
  .expect("Must connect"),
248
290
  );
249
- let worker = init_worker(self.worker_config.clone(), client.clone());
291
+ let worker = init_worker(
292
+ INTEG_TESTS_RT.get().unwrap(),
293
+ self.worker_config.clone(),
294
+ client.clone(),
295
+ )
296
+ .expect("Worker inits cleanly");
250
297
  InitializedWorker {
251
298
  worker: Arc::new(worker),
252
299
  client,
@@ -442,7 +489,6 @@ impl WorkerInterceptor for TestWorkerCompletionIceptor {
442
489
 
443
490
  /// Returns the client options used to connect to the server used for integration tests.
444
491
  pub fn get_integ_server_options() -> ClientOptions {
445
- telemetry_init(&get_integ_telem_options()).expect("Telemetry inits cleanly");
446
492
  let temporal_server_address = match env::var(INTEG_SERVER_TARGET_ENV_VAR) {
447
493
  Ok(addr) => addr,
448
494
  Err(_) => "http://localhost:7233".to_owned(),
@@ -459,6 +505,8 @@ pub fn get_integ_server_options() -> ClientOptions {
459
505
 
460
506
  pub fn get_integ_telem_options() -> TelemetryOptions {
461
507
  let mut ob = TelemetryOptionsBuilder::default();
508
+ let filter_string =
509
+ env::var("RUST_LOG").unwrap_or_else(|_| "temporal_sdk_core=INFO".to_string());
462
510
  if let Some(url) = env::var(OTEL_URL_ENV_VAR)
463
511
  .ok()
464
512
  .map(|x| x.parse::<Url>().unwrap())
@@ -466,8 +514,12 @@ pub fn get_integ_telem_options() -> TelemetryOptions {
466
514
  let opts = OtelCollectorOptions {
467
515
  url,
468
516
  headers: Default::default(),
517
+ metric_periodicity: None,
469
518
  };
470
- ob.tracing(TraceExporter::Otel(opts.clone()));
519
+ ob.tracing(TraceExportConfig {
520
+ filter: filter_string.clone(),
521
+ exporter: TraceExporter::Otel(opts.clone()),
522
+ });
471
523
  ob.metrics(MetricsExporter::Otel(opts));
472
524
  }
473
525
  if let Some(addr) = env::var(PROM_ENABLE_ENV_VAR)
@@ -476,10 +528,11 @@ pub fn get_integ_telem_options() -> TelemetryOptions {
476
528
  {
477
529
  ob.metrics(MetricsExporter::Prometheus(addr));
478
530
  }
479
- ob.tracing_filter(env::var("RUST_LOG").unwrap_or_else(|_| "temporal_sdk_core=INFO".to_string()))
480
- .logging(Logger::Console)
481
- .build()
482
- .unwrap()
531
+ ob.logging(Logger::Console {
532
+ filter: filter_string,
533
+ })
534
+ .build()
535
+ .unwrap()
483
536
  }
484
537
 
485
538
  pub fn default_cached_download() -> EphemeralExe {
@@ -504,7 +557,6 @@ pub fn schedule_activity_cmd(
504
557
  seq,
505
558
  activity_id: activity_id.to_string(),
506
559
  activity_type: "test_activity".to_string(),
507
- namespace: NAMESPACE.to_owned(),
508
560
  task_queue: task_q.to_owned(),
509
561
  schedule_to_start_timeout: Some(activity_timeout.try_into().expect("duration fits")),
510
562
  start_to_close_timeout: Some(activity_timeout.try_into().expect("duration fits")),
@@ -0,0 +1,37 @@
1
+ use temporal_client::WorkflowService;
2
+ use temporal_sdk_core::CoreRuntime;
3
+ use temporal_sdk_core_api::telemetry::MetricsExporter;
4
+ use temporal_sdk_core_protos::temporal::api::workflowservice::v1::ListNamespacesRequest;
5
+ use temporal_sdk_core_test_utils::{get_integ_server_options, get_integ_telem_options};
6
+
7
+ #[tokio::test]
8
+ async fn prometheus_metrics_exported() {
9
+ let mut telemopts = get_integ_telem_options();
10
+ let addr = "127.0.0.1:10919";
11
+ telemopts.metrics = Some(MetricsExporter::Prometheus(addr.parse().unwrap()));
12
+ let rt = CoreRuntime::new_assume_tokio(telemopts).unwrap();
13
+ let opts = get_integ_server_options();
14
+ let mut raw_client = opts
15
+ .connect_no_namespace(rt.metric_meter(), None)
16
+ .await
17
+ .unwrap();
18
+ assert!(raw_client.get_client().capabilities().is_some());
19
+
20
+ let _ = raw_client
21
+ .list_namespaces(ListNamespacesRequest::default())
22
+ .await
23
+ .unwrap();
24
+
25
+ let body = reqwest::get(format!("http://{}/metrics", addr))
26
+ .await
27
+ .unwrap()
28
+ .text()
29
+ .await
30
+ .unwrap();
31
+ assert!(body.contains(
32
+ "request_latency_count{operation=\"ListNamespaces\",service_name=\"temporal-core-sdk\"} 1"
33
+ ));
34
+ assert!(body.contains(
35
+ "request_latency_count{operation=\"GetSystemInfo\",service_name=\"temporal-core-sdk\"} 1"
36
+ ));
37
+ }
@@ -3,7 +3,6 @@ use futures::future::join_all;
3
3
  use std::time::Duration;
4
4
  use temporal_client::WorkflowOptions;
5
5
  use temporal_sdk::{WfContext, WorkflowResult};
6
- use temporal_sdk_core_api::errors::PollWfError;
7
6
  use temporal_sdk_core_protos::coresdk::{
8
7
  activity_task::activity_task as act_task,
9
8
  workflow_activation::{workflow_activation_job, FireTimer, WorkflowActivationJob},
@@ -132,15 +131,3 @@ async fn can_paginate_long_history() {
132
131
  .unwrap();
133
132
  worker.run_until_done().await.unwrap();
134
133
  }
135
-
136
- // TODO: Takes ages now, fix somehow
137
- #[tokio::test]
138
- async fn poll_of_nonexistent_namespace_is_fatal() {
139
- let mut starter = CoreWfStarter::new("whatever_yo");
140
- starter.worker_config.namespace = "I do not exist".to_string();
141
- let worker = starter.get_worker().await;
142
- assert_matches!(
143
- worker.poll_workflow_activation().await,
144
- Err(PollWfError::TonicError(_))
145
- );
146
- }
@@ -1,19 +1,24 @@
1
1
  use anyhow::anyhow;
2
2
  use futures::future::join_all;
3
+ use futures_util::stream::{FuturesUnordered, StreamExt};
3
4
  use std::time::Duration;
4
- use temporal_client::WorkflowOptions;
5
+ use temporal_client::{WorkflowClientTrait, WorkflowOptions};
5
6
  use temporal_sdk::{
6
- interceptors::WorkerInterceptor, ActContext, ActivityCancelledError, CancellableFuture,
7
- LocalActivityOptions, WfContext, WorkflowResult,
7
+ interceptors::WorkerInterceptor, ActContext, ActivityCancelledError, ActivityOptions,
8
+ CancellableFuture, LocalActivityOptions, WfContext, WorkflowResult,
8
9
  };
10
+ use temporal_sdk_core::replay::HistoryForReplay;
9
11
  use temporal_sdk_core_protos::{
10
12
  coresdk::{
11
13
  workflow_commands::ActivityCancellationType,
12
14
  workflow_completion::WorkflowActivationCompletion, AsJsonPayloadExt,
13
15
  },
14
16
  temporal::api::common::v1::RetryPolicy,
17
+ TestHistoryBuilder,
18
+ };
19
+ use temporal_sdk_core_test_utils::{
20
+ history_from_proto_binary, init_integ_telem, replay_sdk_worker, CoreWfStarter,
15
21
  };
16
- use temporal_sdk_core_test_utils::CoreWfStarter;
17
22
  use tokio_util::sync::CancellationToken;
18
23
 
19
24
  pub async fn echo(_ctx: ActContext, e: String) -> anyhow::Result<String> {
@@ -632,3 +637,139 @@ async fn repro_nondeterminism_with_timer_bug() {
632
637
  .await
633
638
  .unwrap();
634
639
  }
640
+
641
+ async fn la_problem_workflow(ctx: WfContext) -> WorkflowResult<()> {
642
+ ctx.local_activity(LocalActivityOptions {
643
+ activity_type: "delay".to_string(),
644
+ input: "hi".as_json_payload().expect("serializes fine"),
645
+ retry_policy: RetryPolicy {
646
+ initial_interval: Some(prost_dur!(from_micros(15))),
647
+ backoff_coefficient: 1_000.,
648
+ maximum_interval: Some(prost_dur!(from_millis(1500))),
649
+ maximum_attempts: 4,
650
+ non_retryable_error_types: vec![],
651
+ },
652
+ timer_backoff_threshold: Some(Duration::from_secs(1)),
653
+ ..Default::default()
654
+ })
655
+ .await;
656
+ ctx.activity(ActivityOptions {
657
+ activity_type: "delay".to_string(),
658
+ start_to_close_timeout: Some(Duration::from_secs(20)),
659
+ input: "hi!".as_json_payload().expect("serializes fine"),
660
+ ..Default::default()
661
+ })
662
+ .await;
663
+ Ok(().into())
664
+ }
665
+
666
+ // Expensive to run - worth enabling on a stress/regression pipeline.
667
+ #[ignore]
668
+ #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
669
+ async fn evict_while_la_running_no_interference() {
670
+ let wf_name = "evict_while_la_running_no_interference";
671
+ let mut starter = CoreWfStarter::new(wf_name);
672
+ starter.max_local_at(20);
673
+ starter.max_cached_workflows(20);
674
+ let mut worker = starter.worker().await;
675
+
676
+ worker.register_wf(wf_name.to_owned(), la_problem_workflow);
677
+ worker.register_activity("delay", |_: ActContext, _: String| async {
678
+ tokio::time::sleep(Duration::from_secs(15)).await;
679
+ Ok(())
680
+ });
681
+
682
+ let client = starter.get_client().await;
683
+ let subfs = FuturesUnordered::new();
684
+ for i in 1..100 {
685
+ let wf_id = format!("{}-{}", wf_name, i);
686
+ let run_id = worker
687
+ .submit_wf(
688
+ &wf_id,
689
+ wf_name.to_owned(),
690
+ vec![],
691
+ WorkflowOptions::default(),
692
+ )
693
+ .await
694
+ .unwrap();
695
+ let cw = worker.core_worker.clone();
696
+ let client = client.clone();
697
+ subfs.push(async move {
698
+ // Evict the workflow
699
+ tokio::time::sleep(Duration::from_secs(1)).await;
700
+ cw.request_workflow_eviction(&run_id);
701
+ // Wake up workflow by sending signal
702
+ client
703
+ .signal_workflow_execution(
704
+ wf_id,
705
+ run_id.clone(),
706
+ "whaatever".to_string(),
707
+ None,
708
+ None,
709
+ )
710
+ .await
711
+ .unwrap();
712
+ });
713
+ }
714
+ let runf = async {
715
+ worker.run_until_done().await.unwrap();
716
+ };
717
+ tokio::join!(subfs.collect::<Vec<_>>(), runf);
718
+ }
719
+
720
+ #[rstest::rstest]
721
+ #[tokio::test]
722
+ async fn weird_la_nondeterminism_repro(#[values(true, false)] fix_hist: bool) {
723
+ init_integ_telem();
724
+ let mut hist = history_from_proto_binary(
725
+ "histories/evict_while_la_running_no_interference-85_history.bin",
726
+ )
727
+ .await
728
+ .unwrap();
729
+ if fix_hist {
730
+ // Replace broken ending with accurate ending
731
+ hist.events.truncate(20);
732
+ let mut thb = TestHistoryBuilder::from_history(hist.events);
733
+ thb.add_workflow_task_completed();
734
+ thb.add_workflow_execution_completed();
735
+ hist = thb.get_full_history_info().unwrap().into();
736
+ }
737
+
738
+ let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
739
+ worker.register_wf(
740
+ "evict_while_la_running_no_interference",
741
+ la_problem_workflow,
742
+ );
743
+ worker.register_activity("delay", |_: ActContext, _: String| async {
744
+ tokio::time::sleep(Duration::from_secs(15)).await;
745
+ Ok(())
746
+ });
747
+ worker.run().await.unwrap();
748
+ }
749
+
750
+ #[tokio::test]
751
+ async fn second_weird_la_nondeterminism_repro() {
752
+ init_integ_telem();
753
+ let mut hist = history_from_proto_binary(
754
+ "histories/evict_while_la_running_no_interference-23_history.bin",
755
+ )
756
+ .await
757
+ .unwrap();
758
+ // Chop off uninteresting ending
759
+ hist.events.truncate(24);
760
+ let mut thb = TestHistoryBuilder::from_history(hist.events);
761
+ // thb.add_workflow_task_completed();
762
+ thb.add_workflow_execution_completed();
763
+ hist = thb.get_full_history_info().unwrap().into();
764
+
765
+ let mut worker = replay_sdk_worker([HistoryForReplay::new(hist, "fake".to_owned())]);
766
+ worker.register_wf(
767
+ "evict_while_la_running_no_interference",
768
+ la_problem_workflow,
769
+ );
770
+ worker.register_activity("delay", |_: ActContext, _: String| async {
771
+ tokio::time::sleep(Duration::from_secs(15)).await;
772
+ Ok(())
773
+ });
774
+ worker.run().await.unwrap();
775
+ }
@@ -0,0 +1,53 @@
1
+ use temporal_client::WorkflowClientTrait;
2
+ use temporal_sdk::{WfContext, WorkflowResult};
3
+ use temporal_sdk_core_protos::coresdk::{AsJsonPayloadExt, FromJsonPayloadExt};
4
+ use temporal_sdk_core_test_utils::CoreWfStarter;
5
+ use uuid::Uuid;
6
+
7
+ static FIELD_A: &str = "cat_name";
8
+ static FIELD_B: &str = "cute_level";
9
+
10
+ async fn memo_upserter(ctx: WfContext) -> WorkflowResult<()> {
11
+ ctx.upsert_memo([
12
+ (FIELD_A.to_string(), "enchi".as_json_payload().unwrap()),
13
+ (FIELD_B.to_string(), 9001.as_json_payload().unwrap()),
14
+ ]);
15
+ Ok(().into())
16
+ }
17
+
18
+ #[tokio::test]
19
+ async fn sends_modify_wf_props() {
20
+ let wf_name = "can_upsert_memo";
21
+ let wf_id = Uuid::new_v4();
22
+ let mut starter = CoreWfStarter::new(wf_name);
23
+ let mut worker = starter.worker().await;
24
+
25
+ worker.register_wf(wf_name, memo_upserter);
26
+ let run_id = worker
27
+ .submit_wf(wf_id.to_string(), wf_name, vec![], Default::default())
28
+ .await
29
+ .unwrap();
30
+ worker.run_until_done().await.unwrap();
31
+
32
+ let memo = starter
33
+ .get_client()
34
+ .await
35
+ .describe_workflow_execution(wf_id.to_string(), Some(run_id))
36
+ .await
37
+ .unwrap()
38
+ .workflow_execution_info
39
+ .unwrap()
40
+ .memo
41
+ .unwrap()
42
+ .fields;
43
+ let catname = memo.get(FIELD_A).unwrap();
44
+ let cuteness = memo.get(FIELD_B).unwrap();
45
+ for payload in [catname, cuteness] {
46
+ assert_eq!(
47
+ &b"json/plain".to_vec(),
48
+ payload.metadata.get("encoding").unwrap()
49
+ );
50
+ }
51
+ assert_eq!("enchi", String::from_json_payload(catname).unwrap());
52
+ assert_eq!(9001, usize::from_json_payload(cuteness).unwrap());
53
+ }
@@ -1,6 +1,8 @@
1
1
  use assert_matches::assert_matches;
2
- use std::time::Duration;
3
- use temporal_sdk::{WfContext, Worker, WorkflowFunction};
2
+ use parking_lot::Mutex;
3
+ use std::{collections::HashSet, sync::Arc, time::Duration};
4
+ use temporal_sdk::{interceptors::WorkerInterceptor, WfContext, Worker, WorkflowFunction};
5
+ use temporal_sdk_core::replay::{HistoryFeeder, HistoryForReplay};
4
6
  use temporal_sdk_core_api::errors::{PollActivityError, PollWfError};
5
7
  use temporal_sdk_core_protos::{
6
8
  coresdk::{
@@ -8,20 +10,29 @@ use temporal_sdk_core_protos::{
8
10
  workflow_commands::{ScheduleActivity, StartTimer},
9
11
  workflow_completion::WorkflowActivationCompletion,
10
12
  },
11
- DEFAULT_WORKFLOW_TYPE,
13
+ TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
12
14
  };
13
15
  use temporal_sdk_core_test_utils::{
14
- canned_histories, history_from_proto_binary, init_core_replay_preloaded, WorkerTestHelpers,
16
+ canned_histories, history_from_proto_binary, init_core_replay_preloaded, replay_sdk_worker,
17
+ replay_sdk_worker_stream, WorkerTestHelpers,
15
18
  };
16
19
  use tokio::join;
17
20
 
21
+ fn test_hist_to_replay(t: TestHistoryBuilder) -> HistoryForReplay {
22
+ let hi = t.get_full_history_info().unwrap().into();
23
+ HistoryForReplay::new(hi, "fake".to_string())
24
+ }
25
+
18
26
  #[tokio::test]
19
27
  async fn timer_workflow_replay() {
20
- let (core, _) = init_core_replay_preloaded(
28
+ let core = init_core_replay_preloaded(
21
29
  "timer_workflow_replay",
22
- &history_from_proto_binary("histories/timer_workflow_history.bin")
23
- .await
24
- .unwrap(),
30
+ [HistoryForReplay::new(
31
+ history_from_proto_binary("histories/timer_workflow_history.bin")
32
+ .await
33
+ .unwrap(),
34
+ "fake".to_owned(),
35
+ )],
25
36
  );
26
37
  let task = core.poll_workflow_activation().await.unwrap();
27
38
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
@@ -43,6 +54,14 @@ async fn timer_workflow_replay() {
43
54
  );
44
55
  };
45
56
  let poll_fut = async {
57
+ let evict_task = core
58
+ .poll_workflow_activation()
59
+ .await
60
+ .expect("Should be an eviction activation");
61
+ assert!(evict_task.eviction_reason().is_some());
62
+ core.complete_workflow_activation(WorkflowActivationCompletion::empty(evict_task.run_id))
63
+ .await
64
+ .unwrap();
46
65
  assert_matches!(
47
66
  core.poll_workflow_activation().await,
48
67
  Err(PollWfError::ShutDown)
@@ -64,11 +83,14 @@ async fn timer_workflow_replay() {
64
83
 
65
84
  #[tokio::test]
66
85
  async fn workflow_nondeterministic_replay() {
67
- let (core, _) = init_core_replay_preloaded(
86
+ let core = init_core_replay_preloaded(
68
87
  "timer_workflow_replay",
69
- &history_from_proto_binary("histories/timer_workflow_history.bin")
70
- .await
71
- .unwrap(),
88
+ [HistoryForReplay::new(
89
+ history_from_proto_binary("histories/timer_workflow_history.bin")
90
+ .await
91
+ .unwrap(),
92
+ "fake".to_owned(),
93
+ )],
72
94
  );
73
95
  let task = core.poll_workflow_activation().await.unwrap();
74
96
  core.complete_workflow_activation(WorkflowActivationCompletion::from_cmds(
@@ -102,9 +124,7 @@ async fn replay_using_wf_function() {
102
124
  let num_timers = 10;
103
125
  let t = canned_histories::long_sequential_timers(num_timers as usize);
104
126
  let func = timers_wf(num_timers);
105
- let (worker, _) =
106
- init_core_replay_preloaded("replay_bench", &t.get_full_history_info().unwrap().into());
107
- let mut worker = Worker::new_from_core(worker, "replay_bench".to_string());
127
+ let mut worker = replay_sdk_worker([test_hist_to_replay(t)]);
108
128
  worker.register_wf(DEFAULT_WORKFLOW_TYPE, func);
109
129
  worker.run().await.unwrap();
110
130
  }
@@ -117,16 +137,69 @@ async fn replay_ok_ending_with_terminated_or_timed_out() {
117
137
  t2.add_workflow_execution_timed_out();
118
138
  for t in [t1, t2] {
119
139
  let func = timers_wf(1);
120
- let (worker, _) = init_core_replay_preloaded(
121
- "replay_ok_terminate",
122
- &t.get_full_history_info().unwrap().into(),
123
- );
124
- let mut worker = Worker::new_from_core(worker, "replay_ok_terminate".to_string());
140
+ let mut worker = replay_sdk_worker([test_hist_to_replay(t)]);
125
141
  worker.register_wf(DEFAULT_WORKFLOW_TYPE, func);
126
142
  worker.run().await.unwrap();
127
143
  }
128
144
  }
129
145
 
146
+ #[rstest::rstest]
147
+ #[tokio::test]
148
+ async fn multiple_histories_replay(#[values(false, true)] use_feeder: bool) {
149
+ let num_timers = 10;
150
+ let seq_timer_wf = timers_wf(num_timers);
151
+ let one_timer_wf = timers_wf(1);
152
+ let mut one_timer_hist = canned_histories::single_timer("1");
153
+ one_timer_hist.set_wf_type("onetimer");
154
+ let mut seq_timer_hist = canned_histories::long_sequential_timers(num_timers as usize);
155
+ seq_timer_hist.set_wf_type("seqtimer");
156
+ let (feeder, stream) = HistoryFeeder::new(1);
157
+ let mut worker = if use_feeder {
158
+ replay_sdk_worker_stream(stream)
159
+ } else {
160
+ replay_sdk_worker([
161
+ test_hist_to_replay(one_timer_hist.clone()),
162
+ test_hist_to_replay(seq_timer_hist.clone()),
163
+ ])
164
+ };
165
+ let runs_ctr_i = UniqueRunsCounter::default();
166
+ let runs_ctr = runs_ctr_i.runs.clone();
167
+ worker.set_worker_interceptor(Box::new(runs_ctr_i));
168
+ worker.register_wf("onetimer", one_timer_wf);
169
+ worker.register_wf("seqtimer", seq_timer_wf);
170
+
171
+ if use_feeder {
172
+ let feed_fut = async move {
173
+ feeder
174
+ .feed(test_hist_to_replay(one_timer_hist))
175
+ .await
176
+ .unwrap();
177
+ feeder
178
+ .feed(test_hist_to_replay(seq_timer_hist))
179
+ .await
180
+ .unwrap();
181
+ };
182
+ let (_, runr) = join!(feed_fut, worker.run());
183
+ runr.unwrap();
184
+ } else {
185
+ worker.run().await.unwrap();
186
+ }
187
+ assert_eq!(runs_ctr.lock().len(), 2);
188
+ }
189
+
190
+ #[tokio::test]
191
+ async fn multiple_histories_can_handle_dupe_run_ids() {
192
+ let mut hist1 = canned_histories::single_timer("1");
193
+ hist1.set_wf_type("onetimer");
194
+ let mut worker = replay_sdk_worker([
195
+ test_hist_to_replay(hist1.clone()),
196
+ test_hist_to_replay(hist1.clone()),
197
+ test_hist_to_replay(hist1),
198
+ ]);
199
+ worker.register_wf("onetimer", timers_wf(1));
200
+ worker.run().await.unwrap();
201
+ }
202
+
130
203
  fn timers_wf(num_timers: u32) -> WorkflowFunction {
131
204
  WorkflowFunction::new(move |ctx: WfContext| async move {
132
205
  for _ in 1..=num_timers {
@@ -135,3 +208,16 @@ fn timers_wf(num_timers: u32) -> WorkflowFunction {
135
208
  Ok(().into())
136
209
  })
137
210
  }
211
+
212
+ #[derive(Default)]
213
+ struct UniqueRunsCounter {
214
+ runs: Arc<Mutex<HashSet<String>>>,
215
+ }
216
+ #[async_trait::async_trait(?Send)]
217
+ impl WorkerInterceptor for UniqueRunsCounter {
218
+ async fn on_workflow_activation_completion(&self, completion: &WorkflowActivationCompletion) {
219
+ self.runs.lock().insert(completion.run_id.clone());
220
+ }
221
+
222
+ fn on_shutdown(&self, _: &Worker) {}
223
+ }