Skip to main content

temporalio_sdk/
runtime.rs

1//! Runtime configuration and low-level Core worker building blocks.
2//!
3//! These types are grouped here to keep Core-specific configuration separate from the SDK's
4//! primary workflow and activity APIs. Create a [`crate::Runtime`] before connecting a client,
5//! then pass it to [`crate::Worker::new`].
6
7use std::time::Duration;
8
9use temporalio_common::telemetry::TelemetryOptions;
10use temporalio_sdk_core::{
11    CoreRuntime, PollerBehavior as CorePollerBehavior, RuntimeOptions as CoreRuntimeOptions,
12    TokioRuntimeBuilder as CoreTokioRuntimeBuilder, WorkflowErrorType as CoreWorkflowErrorType,
13};
14
15use crate::error::RuntimeError;
16
17/// Worker concurrency tuning.
18pub mod worker_tuner;
19
20// Keep these public only with the raw-worker APIs while they are migrated separately.
21// Worker::new_from_core, Worker::new_from_core_options, Worker::with_new_core_worker
22#[cfg(feature = "experimental")]
23pub use temporalio_sdk_core::{Worker as CoreWorker, WorkerConfig};
24
25/// Wraps a Tokio runtime builder so the SDK can install its per-thread telemetry state.
26#[derive(bon::Builder)]
27#[builder(state_mod(vis = "pub"))]
28#[non_exhaustive]
29pub struct TokioRuntimeBuilder {
30    /// The Tokio runtime builder used to create the runtime.
31    pub inner: tokio::runtime::Builder,
32}
33
34impl Default for TokioRuntimeBuilder {
35    fn default() -> Self {
36        Self {
37            inner: tokio::runtime::Builder::new_multi_thread(),
38        }
39    }
40}
41
42impl TokioRuntimeBuilder {
43    fn into_core(self) -> CoreTokioRuntimeBuilder<Box<dyn Fn() + Send + Sync>> {
44        CoreTokioRuntimeBuilder {
45            inner: self.inner,
46            lang_on_thread_start: None,
47        }
48    }
49}
50
51/// Options for automatically scaling the number of concurrent task polls.
52#[derive(bon::Builder, Clone, Copy, Debug, PartialEq)]
53#[builder(state_mod(vis = "pub"))]
54#[non_exhaustive]
55pub struct AutoscalingOptions {
56    /// Minimum number of concurrent polls. Cannot be zero.
57    pub minimum: usize,
58    /// Maximum number of concurrent polls. Must be at least `minimum`.
59    pub maximum: usize,
60    /// Initial number of concurrent polls. Must be between `minimum` and `maximum`.
61    pub initial: usize,
62}
63
64/// Controls how many concurrent task polls a worker issues.
65#[derive(Clone, Copy, Debug, PartialEq)]
66#[non_exhaustive]
67pub enum PollerBehavior {
68    /// Poll whenever a slot is available, up to the supplied maximum.
69    SimpleMaximum(usize),
70    /// Adjust concurrent polls using feedback from the server.
71    Autoscaling(AutoscalingOptions),
72}
73
74impl PollerBehavior {
75    pub(crate) fn into_core(self) -> CorePollerBehavior {
76        match self {
77            PollerBehavior::SimpleMaximum(maximum) => CorePollerBehavior::SimpleMaximum(maximum),
78            PollerBehavior::Autoscaling(AutoscalingOptions {
79                minimum,
80                maximum,
81                initial,
82            }) => CorePollerBehavior::Autoscaling {
83                minimum,
84                maximum,
85                initial,
86            },
87        }
88    }
89}
90
91/// Workflow-processing errors that may be configured to fail the workflow execution.
92#[derive(Clone, Debug, Eq, PartialEq, Hash)]
93#[non_exhaustive]
94pub enum WorkflowErrorType {
95    /// A workflow produced commands that do not match its recorded history.
96    Nondeterminism,
97}
98
99impl WorkflowErrorType {
100    pub(crate) fn into_core(self) -> CoreWorkflowErrorType {
101        match self {
102            WorkflowErrorType::Nondeterminism => CoreWorkflowErrorType::Nondeterminism,
103        }
104    }
105}
106
107/// Configuration for the Rust SDK runtime. Construct with [`RuntimeOptions::builder`].
108#[derive(bon::Builder)]
109#[builder(finish_fn(vis = "", name = build_internal))]
110#[non_exhaustive]
111pub struct RuntimeOptions {
112    /// Telemetry configuration options.
113    #[builder(default)]
114    telemetry_options: TelemetryOptions,
115    /// Optional worker heartbeat interval for all workers created with this runtime.
116    ///
117    /// The interval must be between 1 and 60 seconds, inclusive.
118    #[builder(required, default = Some(Duration::from_secs(60)))]
119    heartbeat_interval: Option<Duration>,
120    /// Disable including runtime, hosting, and platform information in worker heartbeats.
121    #[builder(default)]
122    disable_environment_info: bool,
123}
124
125impl Default for RuntimeOptions {
126    fn default() -> Self {
127        Self::builder().build().expect("builder defaults are valid")
128    }
129}
130
131impl<S: runtime_options_builder::State> RuntimeOptionsBuilder<S> {
132    /// Builds the runtime options.
133    ///
134    /// # Errors
135    /// Returns an error if `heartbeat_interval` is set but is not between 1 and 60 seconds,
136    /// inclusive.
137    pub fn build(self) -> Result<RuntimeOptions, String> {
138        let options = self.build_internal();
139        if let Some(interval) = options.heartbeat_interval
140            && (interval < Duration::from_secs(1) || interval > Duration::from_secs(60))
141        {
142            return Err(format!(
143                "heartbeat_interval ({interval:?}) must be between 1s and 60s",
144            ));
145        }
146        Ok(options)
147    }
148}
149
150impl RuntimeOptions {
151    fn into_core(self) -> CoreRuntimeOptions {
152        CoreRuntimeOptions::builder()
153            .telemetry_options(self.telemetry_options)
154            .heartbeat_interval(self.heartbeat_interval)
155            .disable_environment_info(self.disable_environment_info)
156            .build()
157            .expect("SDK runtime options have already been validated")
158    }
159}
160
161/// Holds shared state and components used by Rust SDK workers.
162pub struct Runtime(CoreRuntime);
163
164impl Runtime {
165    /// Creates a runtime with a newly constructed Tokio runtime.
166    ///
167    /// # Errors
168    /// Returns an error if telemetry or the Tokio runtime cannot be initialized.
169    pub fn new(
170        options: RuntimeOptions,
171        tokio_builder: TokioRuntimeBuilder,
172    ) -> Result<Self, RuntimeError> {
173        CoreRuntime::new(options.into_core(), tokio_builder.into_core())
174            .map(Self)
175            .map_err(RuntimeError::from_core)
176    }
177
178    /// Creates a runtime using the currently active Tokio runtime.
179    ///
180    /// # Errors
181    /// Returns [`RuntimeError::NoCurrentTokioRuntime`] if there is no currently active Tokio
182    /// runtime, or [`RuntimeError::Initialization`] if telemetry cannot be initialized.
183    pub fn from_current_tokio(options: RuntimeOptions) -> Result<Self, RuntimeError> {
184        tokio::runtime::Handle::try_current().map_err(|_| RuntimeError::NoCurrentTokioRuntime)?;
185        CoreRuntime::new_assume_tokio(options.into_core())
186            .map(Self)
187            .map_err(RuntimeError::from_core)
188    }
189
190    /// Creates a runtime using the currently active Tokio runtime.
191    ///
192    /// # Errors
193    /// Returns [`RuntimeError::NoCurrentTokioRuntime`] if there is no currently active Tokio
194    /// runtime, or [`RuntimeError::Initialization`] if telemetry cannot be initialized.
195    #[deprecated(note = "use `Runtime::from_current_tokio` instead")]
196    pub fn new_assume_tokio(options: RuntimeOptions) -> Result<Self, RuntimeError> {
197        Self::from_current_tokio(options)
198    }
199
200    pub(crate) fn core(&self) -> &CoreRuntime {
201        &self.0
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::{Runtime, TokioRuntimeBuilder};
208    use crate::error::RuntimeError;
209
210    #[test]
211    fn from_current_tokio_without_runtime_returns_error() {
212        assert!(matches!(
213            Runtime::from_current_tokio(Default::default()),
214            Err(RuntimeError::NoCurrentTokioRuntime)
215        ));
216    }
217
218    #[test]
219    fn tokio_runtime_builder_constructs_with_an_inner_builder() {
220        let _builder = TokioRuntimeBuilder::builder()
221            .inner(tokio::runtime::Builder::new_current_thread())
222            .build();
223    }
224}