@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
@@ -13,12 +13,11 @@ extern crate core;
13
13
 
14
14
  mod abstractions;
15
15
  pub mod ephemeral_server;
16
- mod log_export;
17
16
  mod pollers;
18
17
  mod protosext;
19
18
  pub mod replay;
20
19
  pub(crate) mod retry_logic;
21
- pub(crate) mod telemetry;
20
+ pub mod telemetry;
22
21
  mod worker;
23
22
 
24
23
  #[cfg(test)]
@@ -33,10 +32,6 @@ pub use pollers::{
33
32
  Client, ClientOptions, ClientOptionsBuilder, ClientTlsConfig, RetryClient, RetryConfig,
34
33
  TlsConfig, WorkflowClientTrait,
35
34
  };
36
- pub use telemetry::{
37
- fetch_global_buffered_logs, telemetry_init, Logger, MetricTemporality, MetricsExporter,
38
- OtelCollectorOptions, TelemetryOptions, TelemetryOptionsBuilder, TraceExporter,
39
- };
40
35
  pub use temporal_sdk_core_api as api;
41
36
  pub use temporal_sdk_core_protos as protos;
42
37
  pub use temporal_sdk_core_protos::TaskToken;
@@ -44,33 +39,39 @@ pub use url::Url;
44
39
  pub use worker::{Worker, WorkerConfig, WorkerConfigBuilder};
45
40
 
46
41
  use crate::{
47
- replay::mock_client_from_history,
48
- telemetry::metrics::{MetricsContext, METRIC_METER},
42
+ replay::{mock_client_from_histories, Historator, HistoryForReplay},
43
+ telemetry::{
44
+ metrics::MetricsContext, remove_trace_subscriber_for_current_thread,
45
+ set_trace_subscriber_for_current_thread, telemetry_init, TelemetryInstance,
46
+ },
49
47
  worker::client::WorkerClientBag,
50
48
  };
49
+ use futures::Stream;
51
50
  use std::sync::Arc;
52
51
  use temporal_client::{ConfiguredClient, TemporalServiceClientWithMetrics};
53
52
  use temporal_sdk_core_api::{
54
53
  errors::{CompleteActivityError, PollActivityError, PollWfError},
55
- CoreLog, Worker as WorkerTrait,
54
+ telemetry::{CoreTelemetry, TelemetryOptions},
55
+ Worker as WorkerTrait,
56
56
  };
57
- use temporal_sdk_core_protos::{coresdk::ActivityHeartbeat, temporal::api::history::v1::History};
58
-
59
- lazy_static::lazy_static! {
60
- /// A process-wide unique string, which will be different on every startup
61
- static ref PROCCESS_UNIQ_ID: String = {
62
- uuid::Uuid::new_v4().simple().to_string()
63
- };
64
- }
57
+ use temporal_sdk_core_protos::coresdk::ActivityHeartbeat;
65
58
 
66
59
  /// Initialize a worker bound to a task queue.
67
60
  ///
61
+ /// You will need to have already initialized a [CoreRuntime] which will be used for this worker.
62
+ /// After the worker is initialized, you should use [CoreRuntime::tokio_handle] to run the worker's
63
+ /// async functions.
64
+ ///
68
65
  /// Lang implementations may pass in a [temporal_client::ConfiguredClient] directly (or a
69
66
  /// [RetryClient] wrapping one, or a handful of other variants of the same idea). When they do so,
70
67
  /// this function will always overwrite the client retry configuration, force the client to use the
71
68
  /// namespace defined in the worker config, and set the client identity appropriately. IE: Use
72
69
  /// [ClientOptions::connect_no_namespace], not [ClientOptions::connect].
73
- pub fn init_worker<CT>(worker_config: WorkerConfig, client: CT) -> Worker
70
+ pub fn init_worker<CT>(
71
+ runtime: &CoreRuntime,
72
+ worker_config: WorkerConfig,
73
+ client: CT,
74
+ ) -> Result<Worker, anyhow::Error>
74
75
  where
75
76
  CT: Into<sealed::AnyClient>,
76
77
  {
@@ -96,18 +97,24 @@ where
96
97
  worker_config.use_worker_versioning,
97
98
  ));
98
99
 
99
- let metrics = MetricsContext::top_level(worker_config.namespace.clone())
100
+ let metrics = MetricsContext::top_level(worker_config.namespace.clone(), &runtime.telemetry)
100
101
  .with_task_q(worker_config.task_queue.clone());
101
- Worker::new(worker_config, sticky_q, client_bag, metrics)
102
+ Ok(Worker::new(worker_config, sticky_q, client_bag, metrics))
102
103
  }
103
104
 
104
105
  /// Create a worker for replaying a specific history. It will auto-shutdown as soon as the history
105
- /// has finished being replayed. The provided client should be a mock, and this should only be used
106
- /// for workflow testing purposes.
107
- pub fn init_replay_worker(
106
+ /// has finished being replayed.
107
+ ///
108
+ /// You do not necessarily need a [CoreRuntime] for replay workers, but it's advisable to create
109
+ /// one and use it to run the replay worker's async functions the same way you would for a normal
110
+ /// worker.
111
+ pub fn init_replay_worker<I>(
108
112
  mut config: WorkerConfig,
109
- history: &History,
110
- ) -> Result<Worker, anyhow::Error> {
113
+ histories: I,
114
+ ) -> Result<Worker, anyhow::Error>
115
+ where
116
+ I: Stream<Item = HistoryForReplay> + Send + 'static,
117
+ {
111
118
  info!(
112
119
  task_queue = config.task_queue.as_str(),
113
120
  "Registering replay worker"
@@ -115,15 +122,18 @@ pub fn init_replay_worker(
115
122
  config.max_cached_workflows = 1;
116
123
  config.max_concurrent_wft_polls = 1;
117
124
  config.no_remote_activities = true;
118
- // Could possibly just use mocked pollers here?
119
- let client = mock_client_from_history(history, config.task_queue.clone());
120
- let run_id = history.extract_run_id_from_start()?.to_string();
121
- let last_event = history.last_event_id();
122
- let mut worker = Worker::new(config, None, Arc::new(client), MetricsContext::default());
123
- worker.set_shutdown_on_run_reaches_event(run_id, last_event);
125
+ let historator = Historator::new(histories);
126
+ let post_activate = historator.get_post_activate_hook();
127
+ let shutdown_tok = historator.get_shutdown_setter();
128
+ let client = mock_client_from_histories(historator);
129
+ let mut worker = Worker::new(config, None, Arc::new(client), MetricsContext::no_op());
130
+ worker.set_post_activate_hook(post_activate);
131
+ shutdown_tok(worker.shutdown_token());
124
132
  Ok(worker)
125
133
  }
126
134
 
135
+ /// Creates a unique sticky queue name for a worker, iff the config allows for 1 or more cached
136
+ /// workflows.
127
137
  pub(crate) fn sticky_q_name_for_worker(
128
138
  process_identity: &str,
129
139
  config: &WorkerConfig,
@@ -131,7 +141,9 @@ pub(crate) fn sticky_q_name_for_worker(
131
141
  if config.max_cached_workflows > 0 {
132
142
  Some(format!(
133
143
  "{}-{}-{}",
134
- &process_identity, &config.task_queue, *PROCCESS_UNIQ_ID
144
+ &process_identity,
145
+ &config.task_queue,
146
+ uuid::Uuid::new_v4().simple()
135
147
  ))
136
148
  } else {
137
149
  None
@@ -173,3 +185,98 @@ mod sealed {
173
185
  }
174
186
  }
175
187
  }
188
+
189
+ /// Holds shared state/components needed to back instances of workers and clients. More than one
190
+ /// may be instantiated, but typically only one is needed. More than one runtime instance may be
191
+ /// useful if multiple different telemetry settings are required.
192
+ pub struct CoreRuntime {
193
+ telemetry: TelemetryInstance,
194
+ runtime: Option<tokio::runtime::Runtime>,
195
+ runtime_handle: tokio::runtime::Handle,
196
+ }
197
+
198
+ impl CoreRuntime {
199
+ /// Create a new core runtime with the provided telemetry options and tokio runtime builder.
200
+ /// Also initialize telemetry for the thread this is being called on.
201
+ ///
202
+ /// Note that this function will call the [tokio::runtime::Builder::enable_all] builder option
203
+ /// on the Tokio runtime builder, and will call [tokio::runtime::Builder::on_thread_start] to
204
+ /// ensure telemetry subscribers are set on every tokio thread.
205
+ ///
206
+ /// **Important**: You need to call this *before* calling any async functions on workers or
207
+ /// clients, otherwise the tracing subscribers will not be properly attached.
208
+ ///
209
+ /// # Panics
210
+ /// If a tokio runtime has already been initialized. To re-use an existing runtime, call
211
+ /// [CoreRuntime::new_assume_tokio].
212
+ pub fn new(
213
+ telemetry_options: TelemetryOptions,
214
+ mut tokio_builder: tokio::runtime::Builder,
215
+ ) -> Result<Self, anyhow::Error> {
216
+ let telemetry = telemetry_init(telemetry_options)?;
217
+ let subscriber = telemetry.trace_subscriber();
218
+ let runtime = tokio_builder
219
+ .enable_all()
220
+ .on_thread_start(move || {
221
+ set_trace_subscriber_for_current_thread(subscriber.clone());
222
+ })
223
+ .build()?;
224
+ let _rg = runtime.enter();
225
+ let mut me = Self::new_assume_tokio_initialized_telem(telemetry);
226
+ me.runtime = Some(runtime);
227
+ Ok(me)
228
+ }
229
+
230
+ /// Initialize telemetry for the thread this is being called on, assuming a tokio runtime is
231
+ /// already active and this call exists in its context. See [Self::new] for more.
232
+ ///
233
+ /// # Panics
234
+ /// If there is no currently active Tokio runtime
235
+ pub fn new_assume_tokio(telemetry_options: TelemetryOptions) -> Result<Self, anyhow::Error> {
236
+ let telemetry = telemetry_init(telemetry_options)?;
237
+ Ok(Self::new_assume_tokio_initialized_telem(telemetry))
238
+ }
239
+
240
+ /// Construct a runtime from an already-initialized telemetry instance, assuming a tokio runtime
241
+ /// is already active and this call exists in its context. See [Self::new] for more.
242
+ ///
243
+ /// # Panics
244
+ /// If there is no currently active Tokio runtime
245
+ pub fn new_assume_tokio_initialized_telem(telemetry: TelemetryInstance) -> Self {
246
+ let runtime_handle = tokio::runtime::Handle::current();
247
+ set_trace_subscriber_for_current_thread(telemetry.trace_subscriber());
248
+ Self {
249
+ telemetry,
250
+ runtime: None,
251
+ runtime_handle,
252
+ }
253
+ }
254
+
255
+ /// Get a handle to the tokio runtime used by this Core runtime.
256
+ pub fn tokio_handle(&self) -> tokio::runtime::Handle {
257
+ self.runtime_handle.clone()
258
+ }
259
+
260
+ /// Returns the metric meter used for recording metrics, if they were enabled.
261
+ pub fn metric_meter(&self) -> Option<&opentelemetry::metrics::Meter> {
262
+ self.telemetry.get_metric_meter()
263
+ }
264
+
265
+ /// Return the trace subscriber associated with the telemetry options/instance. Can be used
266
+ /// to manually set the default for a thread or globally using the `tracing` crate, or with
267
+ /// [set_trace_subscriber_for_current_thread]
268
+ pub fn trace_subscriber(&self) -> Arc<dyn tracing::Subscriber + Send + Sync> {
269
+ self.telemetry.trace_subscriber()
270
+ }
271
+
272
+ /// Return a reference to the owned [TelemetryInstance]
273
+ pub fn telemetry(&self) -> &TelemetryInstance {
274
+ &self.telemetry
275
+ }
276
+ }
277
+
278
+ impl Drop for CoreRuntime {
279
+ fn drop(&mut self) {
280
+ remove_trace_subscriber_for_current_thread();
281
+ }
282
+ }
@@ -2,70 +2,214 @@
2
2
  //! to replay canned histories. It should be used by Lang SDKs to provide replay capabilities to
3
3
  //! users during testing.
4
4
 
5
- use crate::worker::client::{mocks::mock_manual_workflow_client, WorkerClient};
6
- use futures::FutureExt;
5
+ use crate::{
6
+ worker::client::{mocks::mock_manual_workflow_client, WorkerClient},
7
+ Worker,
8
+ };
9
+ use futures::{FutureExt, Stream, StreamExt};
10
+ use once_cell::sync::OnceCell;
11
+ use parking_lot::Mutex;
7
12
  use std::{
8
- sync::{
9
- atomic::{AtomicBool, Ordering},
10
- Arc,
11
- },
12
- time::Duration,
13
+ collections::HashMap,
14
+ pin::Pin,
15
+ sync::Arc,
16
+ task::{Context, Poll},
13
17
  };
14
- use temporal_sdk_core_protos::temporal::api::{
15
- common::v1::WorkflowExecution,
16
- history::v1::History,
17
- workflowservice::v1::{
18
- RespondWorkflowTaskCompletedResponse, RespondWorkflowTaskFailedResponse,
18
+ use temporal_sdk_core_protos::{
19
+ coresdk::workflow_activation::remove_from_cache::EvictionReason,
20
+ temporal::api::{
21
+ common::v1::WorkflowExecution,
22
+ history::v1::History,
23
+ workflowservice::v1::{
24
+ RespondWorkflowTaskCompletedResponse, RespondWorkflowTaskFailedResponse,
25
+ },
19
26
  },
20
27
  };
21
28
  pub use temporal_sdk_core_protos::{
22
29
  default_wes_attribs, HistoryInfo, TestHistoryBuilder, DEFAULT_WORKFLOW_TYPE,
23
30
  };
31
+ use tokio::sync::{mpsc, mpsc::UnboundedSender, Mutex as TokioMutex};
32
+ use tokio_stream::wrappers::UnboundedReceiverStream;
33
+ use tokio_util::sync::CancellationToken;
34
+
35
+ /// A history which will be used during replay verification. Since histories do not include the
36
+ /// workflow id, it must be manually attached.
37
+ #[derive(Debug, Clone, derive_more::Constructor)]
38
+ pub struct HistoryForReplay {
39
+ hist: History,
40
+ workflow_id: String,
41
+ }
42
+
43
+ /// Allows lang to feed histories into the replayer one at a time. Simply drop the feeder to signal
44
+ /// to the worker that you're done and it should initiate shutdown.
45
+ pub struct HistoryFeeder {
46
+ tx: mpsc::Sender<HistoryForReplay>,
47
+ }
48
+ /// The stream half of a [HistoryFeeder]
49
+ pub struct HistoryFeederStream {
50
+ rcvr: mpsc::Receiver<HistoryForReplay>,
51
+ }
52
+
53
+ impl HistoryFeeder {
54
+ /// Make a new history feeder, which will store at most `buffer_size` histories before `feed`
55
+ /// blocks.
56
+ ///
57
+ /// Returns a feeder which will be used to feed in histories, and a stream you can pass to
58
+ /// one of the replay worker init functions.
59
+ pub fn new(buffer_size: usize) -> (Self, HistoryFeederStream) {
60
+ let (tx, rcvr) = mpsc::channel(buffer_size);
61
+ (Self { tx }, HistoryFeederStream { rcvr })
62
+ }
63
+ /// Feed a new history into the replayer, blocking if there is not room to accept another
64
+ /// history.
65
+ pub async fn feed(&self, history: HistoryForReplay) -> anyhow::Result<()> {
66
+ self.tx.send(history).await?;
67
+ Ok(())
68
+ }
69
+ }
70
+
71
+ impl Stream for HistoryFeederStream {
72
+ type Item = HistoryForReplay;
73
+
74
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
75
+ self.rcvr.poll_recv(cx)
76
+ }
77
+ }
24
78
 
25
- /// Create a mock client which can be used by a replay worker to serve up canned history.
26
- /// It will return the entire history in one workflow task, after that it will return default
27
- /// responses (with a 10s wait). If a workflow task failure is sent to the mock, it will send
28
- /// the complete response again.
29
- pub(crate) fn mock_client_from_history(
30
- history: &History,
31
- task_queue: impl Into<String>,
32
- ) -> impl WorkerClient {
79
+ /// Create a mock client which can be used by a replay worker to serve up canned histories. It will
80
+ /// return the entire history in one workflow task. If a workflow task failure is sent to the mock,
81
+ /// it will send the complete response again.
82
+ ///
83
+ /// Once it runs out of histories to return, it will serve up default responses after a 10s delay
84
+ pub(crate) fn mock_client_from_histories(historator: Historator) -> impl WorkerClient {
33
85
  let mut mg = mock_manual_workflow_client();
34
86
 
35
- let hist_info = HistoryInfo::new_from_history(history, None).unwrap();
36
- let wf = WorkflowExecution {
37
- workflow_id: "fake_wf_id".to_string(),
38
- run_id: hist_info.orig_run_id().to_string(),
39
- };
87
+ let hist_allow_tx = historator.replay_done_tx.clone();
88
+ let historator = Arc::new(TokioMutex::new(historator));
40
89
 
41
- let did_send = Arc::new(AtomicBool::new(false));
42
- let did_send_clone = did_send.clone();
43
- let tq = task_queue.into();
44
90
  mg.expect_poll_workflow_task().returning(move |_, _| {
45
- let hist_info = hist_info.clone();
46
- let wf = wf.clone();
47
- let did_send_clone = did_send_clone.clone();
48
- let tq = tq.clone();
91
+ let historator = historator.clone();
49
92
  async move {
50
- if !did_send_clone.swap(true, Ordering::AcqRel) {
51
- let mut resp = hist_info.as_poll_wft_response(tq);
52
- resp.workflow_execution = Some(wf.clone());
93
+ let mut hlock = historator.lock().await;
94
+ // Always wait for permission before dispatching the next task
95
+ let _ = hlock.allow_stream.next().await;
96
+
97
+ if let Some(history) = hlock.next().await {
98
+ let hist_info = HistoryInfo::new_from_history(&history.hist, None).unwrap();
99
+ let mut resp = hist_info.as_poll_wft_response();
100
+ resp.workflow_execution = Some(WorkflowExecution {
101
+ workflow_id: history.workflow_id,
102
+ run_id: hist_info.orig_run_id().to_string(),
103
+ });
53
104
  Ok(resp)
54
105
  } else {
55
- tokio::time::sleep(Duration::from_secs(10)).await;
106
+ if let Some(wc) = hlock.worker_closer.get() {
107
+ wc.cancel();
108
+ }
56
109
  Ok(Default::default())
57
110
  }
58
111
  }
59
112
  .boxed()
60
113
  });
61
114
 
62
- mg.expect_complete_workflow_task()
63
- .returning(|_| async move { Ok(RespondWorkflowTaskCompletedResponse::default()) }.boxed());
115
+ mg.expect_complete_workflow_task().returning(move |_| {
116
+ async move { Ok(RespondWorkflowTaskCompletedResponse::default()) }.boxed()
117
+ });
64
118
  mg.expect_fail_workflow_task().returning(move |_, _, _| {
65
- // We'll need to re-send the history if WFT fails
66
- did_send.store(false, Ordering::Release);
67
- async move { Ok(RespondWorkflowTaskFailedResponse {}) }.boxed()
119
+ hist_allow_tx.send("Failed".to_string()).unwrap();
120
+ async move { Ok(RespondWorkflowTaskFailedResponse::default()) }.boxed()
68
121
  });
69
122
 
70
123
  mg
71
124
  }
125
+
126
+ pub(crate) struct Historator {
127
+ iter: Pin<Box<dyn Stream<Item = HistoryForReplay> + Send>>,
128
+ allow_stream: UnboundedReceiverStream<String>,
129
+ worker_closer: Arc<OnceCell<CancellationToken>>,
130
+ dat: Arc<Mutex<HistoratorDat>>,
131
+ replay_done_tx: UnboundedSender<String>,
132
+ }
133
+ impl Historator {
134
+ pub(crate) fn new(histories: impl Stream<Item = HistoryForReplay> + Send + 'static) -> Self {
135
+ let dat = Arc::new(Mutex::new(HistoratorDat::default()));
136
+ let (replay_done_tx, replay_done_rx) = mpsc::unbounded_channel();
137
+ // Need to allow the first history item
138
+ replay_done_tx.send("fake".to_string()).unwrap();
139
+ Self {
140
+ iter: Box::pin(histories.fuse()),
141
+ allow_stream: UnboundedReceiverStream::new(replay_done_rx),
142
+ worker_closer: Arc::new(OnceCell::new()),
143
+ dat,
144
+ replay_done_tx,
145
+ }
146
+ }
147
+
148
+ /// Returns a callback that can be used as the post-activation hook for a worker to indicate
149
+ /// we're ready to replay the next history, or whatever else.
150
+ pub(crate) fn get_post_activate_hook(&self) -> impl Fn(&Worker, &str, usize) + Send + Sync {
151
+ let dat = self.dat.clone();
152
+ let done_tx = self.replay_done_tx.clone();
153
+ move |worker, activated_run_id, last_processed_event| {
154
+ // We can't hold the lock while evaluating the hook, or we'd deadlock.
155
+ let last_event_in_hist = dat
156
+ .lock()
157
+ .run_id_to_last_event_num
158
+ .get(activated_run_id)
159
+ .cloned();
160
+ if let Some(le) = last_event_in_hist {
161
+ if last_processed_event >= le {
162
+ worker.request_wf_eviction(
163
+ activated_run_id,
164
+ "Always evict workflows after replay",
165
+ EvictionReason::LangRequested,
166
+ );
167
+ done_tx.send(activated_run_id.to_string()).unwrap();
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ pub(crate) fn get_shutdown_setter(&self) -> impl FnOnce(CancellationToken) + 'static {
174
+ let wc = self.worker_closer.clone();
175
+ move |ct| {
176
+ wc.set(ct).expect("Shutdown token must only be set once");
177
+ }
178
+ }
179
+ }
180
+
181
+ impl Stream for Historator {
182
+ type Item = HistoryForReplay;
183
+
184
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
185
+ match self.iter.poll_next_unpin(cx) {
186
+ Poll::Ready(Some(history)) => {
187
+ let run_id = history
188
+ .hist
189
+ .extract_run_id_from_start()
190
+ .expect(
191
+ "Histories provided for replay must contain run ids in their workflow \
192
+ execution started events",
193
+ )
194
+ .to_string();
195
+ let last_event = history.hist.last_event_id();
196
+ self.dat
197
+ .lock()
198
+ .run_id_to_last_event_num
199
+ .insert(run_id, last_event as usize);
200
+ Poll::Ready(Some(history))
201
+ }
202
+ Poll::Ready(None) => {
203
+ self.dat.lock().all_dispatched = true;
204
+ Poll::Ready(None)
205
+ }
206
+ o => o,
207
+ }
208
+ }
209
+ }
210
+
211
+ #[derive(Default)]
212
+ struct HistoratorDat {
213
+ run_id_to_last_event_num: HashMap<String, usize>,
214
+ all_dispatched: bool,
215
+ }
@@ -0,0 +1,190 @@
1
+ use parking_lot::Mutex;
2
+ use ringbuf::{Consumer, HeapRb, Producer};
3
+ use std::{collections::HashMap, sync::Arc, time::SystemTime};
4
+ use temporal_sdk_core_api::telemetry::CoreLog;
5
+ use tracing_subscriber::Layer;
6
+
7
+ const RB_SIZE: usize = 2048;
8
+
9
+ pub(super) type CoreLogsOut = Consumer<CoreLog, Arc<HeapRb<CoreLog>>>;
10
+
11
+ pub(super) struct CoreLogExportLayer {
12
+ logs_in: Mutex<Producer<CoreLog, Arc<HeapRb<CoreLog>>>>,
13
+ }
14
+
15
+ #[derive(Debug)]
16
+ struct CoreLogFieldStorage(HashMap<String, serde_json::Value>);
17
+
18
+ impl CoreLogExportLayer {
19
+ pub(super) fn new() -> (Self, CoreLogsOut) {
20
+ let (lin, lout) = HeapRb::new(RB_SIZE).split();
21
+ (
22
+ Self {
23
+ logs_in: Mutex::new(lin),
24
+ },
25
+ lout,
26
+ )
27
+ }
28
+ }
29
+
30
+ impl<S> Layer<S> for CoreLogExportLayer
31
+ where
32
+ S: tracing::Subscriber,
33
+ S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
34
+ {
35
+ fn on_new_span(
36
+ &self,
37
+ attrs: &tracing::span::Attributes<'_>,
38
+ id: &tracing::span::Id,
39
+ ctx: tracing_subscriber::layer::Context<'_, S>,
40
+ ) {
41
+ let span = ctx.span(id).unwrap();
42
+ let mut fields = HashMap::new();
43
+ let mut visitor = JsonVisitor(&mut fields);
44
+ attrs.record(&mut visitor);
45
+ let storage = CoreLogFieldStorage(fields);
46
+ let mut extensions = span.extensions_mut();
47
+ extensions.insert(storage);
48
+ }
49
+
50
+ fn on_record(
51
+ &self,
52
+ id: &tracing::span::Id,
53
+ values: &tracing::span::Record<'_>,
54
+ ctx: tracing_subscriber::layer::Context<'_, S>,
55
+ ) {
56
+ let span = ctx.span(id).unwrap();
57
+
58
+ let mut extensions_mut = span.extensions_mut();
59
+ let custom_field_storage: &mut CoreLogFieldStorage =
60
+ extensions_mut.get_mut::<CoreLogFieldStorage>().unwrap();
61
+ let json_data = &mut custom_field_storage.0;
62
+
63
+ let mut visitor = JsonVisitor(json_data);
64
+ values.record(&mut visitor);
65
+ }
66
+
67
+ fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
68
+ let mut fields = HashMap::new();
69
+ let mut visitor = JsonVisitor(&mut fields);
70
+ event.record(&mut visitor);
71
+
72
+ let mut spans = vec![];
73
+ if let Some(scope) = ctx.event_scope(event) {
74
+ for span in scope.from_root() {
75
+ let extensions = span.extensions();
76
+ let storage = extensions.get::<CoreLogFieldStorage>().unwrap();
77
+ let field_data = &storage.0;
78
+ for (k, v) in field_data {
79
+ fields.insert(k.to_string(), v.clone());
80
+ }
81
+ spans.push(span.name().to_string());
82
+ }
83
+ }
84
+
85
+ // "message" is the magic default field keyname for the string passed to the event
86
+ let message = fields.remove("message").unwrap_or_default();
87
+ let log = CoreLog {
88
+ target: event.metadata().target().to_string(),
89
+ // This weird as_str dance prevents adding extra quotes
90
+ message: message.as_str().unwrap_or_default().to_string(),
91
+ timestamp: SystemTime::now(),
92
+ level: *event.metadata().level(),
93
+ fields,
94
+ span_contexts: spans,
95
+ };
96
+ let _ = self.logs_in.lock().push(log);
97
+ }
98
+ }
99
+
100
+ struct JsonVisitor<'a>(&'a mut HashMap<String, serde_json::Value>);
101
+
102
+ impl<'a> tracing::field::Visit for JsonVisitor<'a> {
103
+ fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
104
+ self.0
105
+ .insert(field.name().to_string(), serde_json::json!(value));
106
+ }
107
+
108
+ fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
109
+ self.0
110
+ .insert(field.name().to_string(), serde_json::json!(value));
111
+ }
112
+
113
+ fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
114
+ self.0
115
+ .insert(field.name().to_string(), serde_json::json!(value));
116
+ }
117
+
118
+ fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
119
+ self.0
120
+ .insert(field.name().to_string(), serde_json::json!(value));
121
+ }
122
+
123
+ fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
124
+ self.0
125
+ .insert(field.name().to_string(), serde_json::json!(value));
126
+ }
127
+
128
+ fn record_error(
129
+ &mut self,
130
+ field: &tracing::field::Field,
131
+ value: &(dyn std::error::Error + 'static),
132
+ ) {
133
+ self.0.insert(
134
+ field.name().to_string(),
135
+ serde_json::json!(value.to_string()),
136
+ );
137
+ }
138
+
139
+ fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
140
+ self.0.insert(
141
+ field.name().to_string(),
142
+ serde_json::json!(format!("{:?}", value)),
143
+ );
144
+ }
145
+ }
146
+
147
+ #[cfg(test)]
148
+ mod tests {
149
+ use crate::{telemetry::construct_filter_string, telemetry_init};
150
+ use temporal_sdk_core_api::telemetry::{CoreTelemetry, Logger, TelemetryOptionsBuilder};
151
+ use tracing::Level;
152
+
153
+ #[instrument(fields(bros = "brohemian"))]
154
+ fn instrumented(thing: &str) {
155
+ warn!("warn");
156
+ info!(foo = "bar", "info");
157
+ debug!("debug");
158
+ }
159
+
160
+ #[tokio::test]
161
+ async fn test_forwarding_output() {
162
+ let opts = TelemetryOptionsBuilder::default()
163
+ .logging(Logger::Forward {
164
+ filter: construct_filter_string(Level::INFO, Level::WARN),
165
+ })
166
+ .build()
167
+ .unwrap();
168
+ let instance = telemetry_init(opts).unwrap();
169
+ let _g = tracing::subscriber::set_default(instance.trace_subscriber.clone());
170
+
171
+ let top_span = span!(Level::INFO, "yayspan", huh = "wat");
172
+ let _guard = top_span.enter();
173
+ info!("Whata?");
174
+ instrumented("hi");
175
+ info!("Donezo");
176
+
177
+ let logs = instance.fetch_buffered_logs();
178
+ // Verify debug log was not forwarded
179
+ assert!(!logs.iter().any(|l| l.message == "debug"));
180
+ assert_eq!(logs.len(), 4);
181
+ // Ensure fields are attached to events properly
182
+ let info_msg = &logs[2];
183
+ assert_eq!(info_msg.message, "info");
184
+ assert_eq!(info_msg.fields.len(), 4);
185
+ assert_eq!(info_msg.fields.get("huh"), Some(&"wat".into()));
186
+ assert_eq!(info_msg.fields.get("foo"), Some(&"bar".into()));
187
+ assert_eq!(info_msg.fields.get("bros"), Some(&"brohemian".into()));
188
+ assert_eq!(info_msg.fields.get("thing"), Some(&"hi".into()));
189
+ }
190
+ }