1#![warn(missing_docs)] #![allow(clippy::upper_case_acronyms)]
3
4#[cfg(test)]
10#[macro_use]
11pub extern crate assert_matches;
12#[macro_use]
13extern crate tracing;
14extern crate core;
15
16mod abstractions;
17#[cfg(feature = "antithesis_assertions")]
18mod antithesis;
19#[cfg(feature = "debug-plugin")]
20pub mod debug_client;
21mod environment;
22#[cfg(feature = "ephemeral-server")]
23pub mod ephemeral_server;
24mod internal_flags;
25mod pollers;
26mod protosext;
27pub mod replay;
28pub(crate) mod retry_logic;
29pub mod telemetry;
30mod worker;
31
32#[cfg(test)]
33mod core_tests;
34#[cfg(any(feature = "test-utilities", test))]
35#[macro_use]
36pub mod test_help;
37
38pub use crate::worker::client::{
39 PollActivityOptions, PollOptions, PollWorkflowOptions, WorkerClient, WorkflowTaskCompletion,
40};
41pub use pollers::{Client, ClientOptions, ClientTlsOptions, RetryOptions, TlsOptions};
42pub use temporalio_common::protos::TaskToken;
43pub use url::Url;
44pub use worker::{
45 ActivitySlotKind, CompleteActivityError, CompleteNexusError, CompleteWfError,
46 FixedSizeSlotSupplier, LocalActivitySlotKind, NamespaceCapabilities, NexusSlotKind, PollError,
47 PollerBehavior, ResourceBasedSlotsOptions, ResourceBasedSlotsOptionsBuilder,
48 ResourceBasedTuner, ResourceBasedTunerConfig, ResourceController, ResourceSlotOptions,
49 SlotInfo, SlotInfoTrait, SlotKind, SlotKindType, SlotMarkUsedContext, SlotReleaseContext,
50 SlotReservationContext, SlotSupplier, SlotSupplierOptions, SlotSupplierPermit, TunerBuilder,
51 TunerHolder, TunerHolderOptions, TunerHolderOptionsBuilder, Worker, WorkerConfig,
52 WorkerConfigBuilder, WorkerTuner, WorkerValidationError, WorkerVersioningStrategy,
53 WorkflowErrorType, WorkflowSlotKind,
54};
55
56use crate::{
57 replay::{HistoryForReplay, ReplayWorkerInput},
58 telemetry::metrics::MetricsContext,
59 worker::client::WorkerClientBag,
60};
61use anyhow::bail;
62use futures_util::Stream;
63use std::{sync::Arc, time::Duration};
64use temporalio_client::{Connection, SharedReplaceableClient};
65use temporalio_common::{
66 protos::{
67 coresdk::ActivityHeartbeat,
68 temporal::api::worker::v1::{EnvironmentInfo, environment_info::Runtime},
69 },
70 telemetry::{
71 TelemetryInstance, TelemetryOptions, remove_trace_subscriber_for_current_thread,
72 set_trace_subscriber_for_current_thread, telemetry_init,
73 },
74};
75
76pub fn init_worker(
86 runtime: &CoreRuntime,
87 worker_config: WorkerConfig,
88 mut connection: Connection,
89) -> Result<Worker, anyhow::Error> {
90 let namespace = worker_config.namespace.clone();
91 if namespace.is_empty() {
92 bail!("Worker namespace cannot be empty");
93 }
94
95 *connection.retry_options_mut() = RetryOptions::default();
96 init_worker_client(
97 &mut connection,
98 worker_config.client_identity_override.clone(),
99 );
100 let client = SharedReplaceableClient::new(connection);
101 let client_ident = client.inner_cow().identity().to_owned();
102 if client_ident.is_empty() {
103 bail!("Client identity cannot be empty. Either lang or user should be setting this value");
104 }
105 let sticky_q = sticky_q_name_for_worker(&client_ident, worker_config.max_cached_workflows);
106
107 let worker_instance_key = uuid::Uuid::new_v4();
108 let client_bag = Arc::new(WorkerClientBag::new(
109 client,
110 namespace.clone(),
111 worker_config.versioning_strategy.clone(),
112 worker_instance_key,
113 ));
114
115 Worker::new(
116 worker_config.clone(),
117 sticky_q,
118 client_bag.clone(),
119 Some(&runtime.telemetry),
120 runtime.heartbeat_interval,
121 runtime.environment_info.clone(),
122 )
123}
124
125pub fn init_replay_worker<I>(rwi: ReplayWorkerInput<I>) -> Result<Worker, anyhow::Error>
132where
133 I: Stream<Item = HistoryForReplay> + Send + 'static,
134{
135 info!(
136 task_queue = rwi.config.task_queue.as_str(),
137 "Registering replay worker"
138 );
139 rwi.into_core_worker()
140}
141
142pub(crate) fn init_worker_client(
143 connection: &mut Connection,
144 client_identity_override: Option<String>,
145) {
146 if let Some(ref id_override) = client_identity_override {
147 connection.identity_mut().clone_from(id_override);
148 }
149}
150
151pub(crate) fn sticky_q_name_for_worker(
154 process_identity: &str,
155 max_cached_workflows: usize,
156) -> Option<String> {
157 if max_cached_workflows > 0 {
158 Some(format!(
159 "{}-{}",
160 &process_identity,
161 uuid::Uuid::new_v4().simple()
162 ))
163 } else {
164 None
165 }
166}
167
168pub struct CoreRuntime {
172 telemetry: TelemetryInstance,
173 runtime: Option<tokio::runtime::Runtime>,
174 runtime_handle: tokio::runtime::Handle,
175 heartbeat_interval: Option<Duration>,
176 environment_info: Option<Arc<EnvironmentInfo>>,
177}
178
179#[derive(bon::Builder)]
181#[builder(finish_fn(vis = "", name = build_internal))]
182#[non_exhaustive]
183pub struct RuntimeOptions {
184 #[builder(default)]
186 telemetry_options: TelemetryOptions,
187 #[builder(required, default = Some(Duration::from_secs(60)))]
192 heartbeat_interval: Option<Duration>,
193 #[builder(default)]
195 disable_environment_info: bool,
196 #[doc(hidden)]
198 #[builder(skip = vec![environment::native_runtime()])]
199 runtimes: Vec<Runtime>,
200}
201
202impl RuntimeOptions {
203 #[doc(hidden)]
205 pub fn with_runtimes(mut self, runtimes: Vec<Runtime>) -> Self {
206 self.runtimes = runtimes;
207 self
208 }
209}
210
211impl Default for RuntimeOptions {
212 fn default() -> Self {
213 Self::builder().build().expect("builder defaults are valid")
214 }
215}
216
217impl<S: runtime_options_builder::State> RuntimeOptionsBuilder<S> {
218 pub fn build(self) -> Result<RuntimeOptions, String> {
223 let options = self.build_internal();
224 {
225 if let Some(interval) = options.heartbeat_interval
226 && (interval < Duration::from_secs(1) || interval > Duration::from_secs(60))
227 {
228 return Err(format!(
229 "heartbeat_interval ({interval:?}) must be between 1s and 60s",
230 ));
231 }
232
233 Ok(options)
234 }
235 }
236}
237
238pub struct TokioRuntimeBuilder<F> {
240 pub inner: tokio::runtime::Builder,
242 pub lang_on_thread_start: Option<F>,
244}
245
246impl Default for TokioRuntimeBuilder<Box<dyn Fn() + Send + Sync>> {
247 fn default() -> Self {
248 TokioRuntimeBuilder {
249 inner: tokio::runtime::Builder::new_multi_thread(),
250 lang_on_thread_start: None,
251 }
252 }
253}
254
255impl CoreRuntime {
256 pub fn new<F>(
270 runtime_options: RuntimeOptions,
271 mut tokio_builder: TokioRuntimeBuilder<F>,
272 ) -> Result<Self, anyhow::Error>
273 where
274 F: Fn() + Send + Sync + 'static,
275 {
276 let RuntimeOptions {
277 telemetry_options,
278 heartbeat_interval,
279 disable_environment_info,
280 runtimes,
281 } = runtime_options;
282 let telemetry = telemetry_init(telemetry_options)?;
283 let subscriber = telemetry.trace_subscriber();
284 let runtime = tokio_builder
285 .inner
286 .enable_all()
287 .on_thread_start(move || {
288 if let Some(sub) = subscriber.as_ref() {
289 set_trace_subscriber_for_current_thread(sub.clone());
290 }
291 if let Some(lang_on_thread_start) = tokio_builder.lang_on_thread_start.as_ref() {
292 lang_on_thread_start();
293 }
294 })
295 .build()?;
296 let _rg = runtime.enter();
297 let mut me = Self::new_assume_tokio_initialized_telem_with_environment(
298 telemetry,
299 heartbeat_interval,
300 disable_environment_info,
301 runtimes,
302 );
303 me.runtime = Some(runtime);
304 Ok(me)
305 }
306
307 pub fn new_assume_tokio(runtime_options: RuntimeOptions) -> Result<Self, anyhow::Error> {
313 let RuntimeOptions {
314 telemetry_options,
315 heartbeat_interval,
316 disable_environment_info,
317 runtimes,
318 } = runtime_options;
319 let telemetry = telemetry_init(telemetry_options)?;
320 Ok(Self::new_assume_tokio_initialized_telem_with_environment(
321 telemetry,
322 heartbeat_interval,
323 disable_environment_info,
324 runtimes,
325 ))
326 }
327
328 pub fn new_assume_tokio_initialized_telem(
334 telemetry: TelemetryInstance,
335 heartbeat_interval: Option<Duration>,
336 ) -> Self {
337 Self::new_assume_tokio_initialized_telem_with_environment(
338 telemetry,
339 heartbeat_interval,
340 false,
341 vec![environment::native_runtime()],
342 )
343 }
344
345 fn new_assume_tokio_initialized_telem_with_environment(
346 telemetry: TelemetryInstance,
347 heartbeat_interval: Option<Duration>,
348 disable_environment_info: bool,
349 runtimes: Vec<Runtime>,
350 ) -> Self {
351 let runtime_handle = tokio::runtime::Handle::current();
352 if let Some(sub) = telemetry.trace_subscriber() {
353 set_trace_subscriber_for_current_thread(sub);
354 }
355 let environment_info = heartbeat_interval
356 .filter(|_| !disable_environment_info)
357 .map(|_| Arc::new(environment::detect(runtimes)));
358 Self {
359 telemetry,
360 runtime: None,
361 runtime_handle,
362 heartbeat_interval,
363 environment_info,
364 }
365 }
366
367 pub fn tokio_handle(&self) -> tokio::runtime::Handle {
369 self.runtime_handle.clone()
370 }
371
372 pub fn telemetry(&self) -> &TelemetryInstance {
374 &self.telemetry
375 }
376
377 pub fn telemetry_mut(&mut self) -> &mut TelemetryInstance {
379 &mut self.telemetry
380 }
381}
382
383impl Drop for CoreRuntime {
384 fn drop(&mut self) {
385 remove_trace_subscriber_for_current_thread();
386 }
387}
388
389#[cfg(test)]
390mod test {
391 use super::*;
392 #[test]
393 fn runtime_options_default_matches_builder_default() {
394 let default = RuntimeOptions::default();
395 let built = RuntimeOptions::builder().build().unwrap();
396 assert_eq!(default.heartbeat_interval, built.heartbeat_interval);
397 }
398}