@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.
- package/Cargo.lock +327 -419
- package/Cargo.toml +1 -1
- package/index.js +25 -2
- package/lib/errors.d.ts +22 -0
- package/lib/errors.js +65 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +440 -0
- package/lib/index.js +8 -0
- package/lib/index.js.map +1 -0
- package/package.json +11 -5
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.buildkite/docker/Dockerfile +1 -1
- package/sdk-core/.buildkite/docker/docker-compose.yaml +2 -2
- package/sdk-core/bridge-ffi/Cargo.toml +1 -1
- package/sdk-core/bridge-ffi/include/sdk-core-bridge.h +0 -25
- package/sdk-core/bridge-ffi/src/lib.rs +29 -108
- package/sdk-core/bridge-ffi/src/wrappers.rs +35 -25
- package/sdk-core/client/Cargo.toml +1 -1
- package/sdk-core/client/src/lib.rs +12 -20
- package/sdk-core/client/src/raw.rs +9 -8
- package/sdk-core/client/src/retry.rs +100 -23
- package/sdk-core/core/Cargo.toml +5 -5
- package/sdk-core/core/benches/workflow_replay.rs +13 -10
- package/sdk-core/core/src/abstractions.rs +22 -22
- package/sdk-core/core/src/core_tests/activity_tasks.rs +1 -1
- package/sdk-core/core/src/core_tests/local_activities.rs +228 -6
- package/sdk-core/core/src/core_tests/queries.rs +247 -89
- package/sdk-core/core/src/core_tests/workers.rs +2 -2
- package/sdk-core/core/src/core_tests/workflow_cancels.rs +1 -1
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +46 -27
- package/sdk-core/core/src/lib.rs +139 -32
- package/sdk-core/core/src/replay/mod.rs +185 -41
- package/sdk-core/core/src/telemetry/log_export.rs +190 -0
- package/sdk-core/core/src/telemetry/metrics.rs +184 -139
- package/sdk-core/core/src/telemetry/mod.rs +296 -318
- package/sdk-core/core/src/telemetry/prometheus_server.rs +4 -3
- package/sdk-core/core/src/test_help/mod.rs +9 -7
- package/sdk-core/core/src/worker/activities/local_activities.rs +2 -1
- package/sdk-core/core/src/worker/activities.rs +40 -23
- package/sdk-core/core/src/worker/client/mocks.rs +1 -1
- package/sdk-core/core/src/worker/client.rs +30 -4
- package/sdk-core/core/src/worker/mod.rs +22 -18
- package/sdk-core/core/src/worker/workflow/driven_workflow.rs +10 -19
- package/sdk-core/core/src/worker/workflow/history_update.rs +99 -25
- package/sdk-core/core/src/worker/workflow/machines/activity_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/cancel_external_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/cancel_workflow_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/complete_workflow_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/continue_as_new_workflow_state_machine.rs +2 -6
- package/sdk-core/core/src/worker/workflow/machines/fail_workflow_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/local_activity_state_machine.rs +18 -21
- package/sdk-core/core/src/worker/workflow/machines/mod.rs +12 -38
- package/sdk-core/core/src/worker/workflow/machines/modify_workflow_properties_state_machine.rs +178 -0
- package/sdk-core/core/src/worker/workflow/machines/patch_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/signal_external_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/timer_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +8 -2
- package/sdk-core/core/src/worker/workflow/machines/upsert_search_attributes_state_machine.rs +1 -5
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +232 -216
- package/sdk-core/core/src/worker/workflow/machines/workflow_task_state_machine.rs +1 -6
- package/sdk-core/core/src/worker/workflow/managed_run/managed_wf_test.rs +4 -4
- package/sdk-core/core/src/worker/workflow/managed_run.rs +13 -5
- package/sdk-core/core/src/worker/workflow/mod.rs +61 -9
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +56 -11
- package/sdk-core/core-api/Cargo.toml +4 -3
- package/sdk-core/core-api/src/lib.rs +1 -43
- package/sdk-core/core-api/src/telemetry.rs +147 -0
- package/sdk-core/core-api/src/worker.rs +13 -0
- package/sdk-core/fsm/rustfsm_procmacro/tests/trybuild/no_handle_conversions_require_into_fail.stderr +1 -1
- package/sdk-core/histories/evict_while_la_running_no_interference-23_history.bin +0 -0
- package/sdk-core/histories/evict_while_la_running_no_interference-85_history.bin +0 -0
- package/sdk-core/protos/api_upstream/.github/CODEOWNERS +1 -1
- package/sdk-core/protos/api_upstream/buf.yaml +0 -3
- package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +3 -7
- package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +8 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +1 -2
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +2 -0
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +3 -0
- package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +13 -0
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +19 -59
- package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -19
- package/sdk-core/protos/api_upstream/temporal/api/schedule/v1/message.proto +108 -29
- package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +2 -2
- package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +1 -0
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +47 -8
- package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +15 -1
- package/sdk-core/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +2 -0
- package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +8 -1
- package/sdk-core/sdk/src/interceptors.rs +36 -3
- package/sdk-core/sdk/src/lib.rs +7 -4
- package/sdk-core/sdk/src/workflow_context.rs +13 -2
- package/sdk-core/sdk-core-protos/src/history_builder.rs +47 -1
- package/sdk-core/sdk-core-protos/src/history_info.rs +22 -22
- package/sdk-core/sdk-core-protos/src/lib.rs +49 -27
- package/sdk-core/test-utils/Cargo.toml +1 -0
- package/sdk-core/test-utils/src/lib.rs +81 -29
- package/sdk-core/tests/integ_tests/metrics_tests.rs +37 -0
- package/sdk-core/tests/integ_tests/polling_tests.rs +0 -13
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +145 -4
- package/sdk-core/tests/integ_tests/workflow_tests/modify_wf_properties.rs +53 -0
- package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +106 -20
- package/sdk-core/tests/integ_tests/workflow_tests.rs +18 -8
- package/sdk-core/tests/main.rs +6 -4
- package/src/conversions.rs +52 -47
- package/src/errors.rs +28 -86
- package/src/helpers.rs +3 -4
- package/src/lib.rs +2 -2
- package/src/runtime.rs +132 -61
- package/src/testing.rs +7 -4
- package/src/worker.rs +67 -50
- package/ts/errors.ts +55 -0
- package/{index.d.ts → ts/index.ts} +121 -15
- package/sdk-core/core/src/log_export.rs +0 -62
- package/sdk-core/core/src/worker/workflow/machines/mutable_side_effect_state_machine.rs +0 -127
- package/sdk-core/core/src/worker/workflow/machines/side_effect_state_machine.rs +0 -71
- package/sdk-core/protos/api_upstream/temporal/api/cluster/v1/message.proto +0 -83
- package/sdk-core/protos/api_upstream/temporal/api/enums/v1/cluster.proto +0 -40
package/sdk-core/core/src/lib.rs
CHANGED
|
@@ -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
|
|
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::
|
|
48
|
-
telemetry::
|
|
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
|
-
|
|
54
|
+
telemetry::{CoreTelemetry, TelemetryOptions},
|
|
55
|
+
Worker as WorkerTrait,
|
|
56
56
|
};
|
|
57
|
-
use temporal_sdk_core_protos::
|
|
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>(
|
|
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.
|
|
106
|
-
///
|
|
107
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
let
|
|
120
|
-
let
|
|
121
|
-
let
|
|
122
|
-
let mut worker = Worker::new(config, None, Arc::new(client), MetricsContext::
|
|
123
|
-
worker.
|
|
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,
|
|
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::
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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::
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
26
|
-
///
|
|
27
|
-
///
|
|
28
|
-
///
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
36
|
-
let
|
|
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
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
+
}
|