1use super::Result;
17use derive_builder::Builder;
18use figment::{
19 providers::{Env, Format, Serialized, Toml},
20 Figment,
21};
22use serde::{Deserialize, Serialize};
23use validator::Validate;
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct WorkerConfig {
27 pub graceful_shutdown_timeout: u64,
29}
30
31impl WorkerConfig {
32 pub fn from_settings() -> Self {
35 Figment::new()
37 .merge(Serialized::defaults(Self::default()))
38 .merge(Env::prefixed("DYN_WORKER_"))
39 .extract()
40 .unwrap() }
42}
43
44impl Default for WorkerConfig {
45 fn default() -> Self {
46 WorkerConfig {
47 graceful_shutdown_timeout: if cfg!(debug_assertions) {
48 1 } else {
50 30 },
52 }
53 }
54}
55
56#[derive(Serialize, Deserialize, Validate, Debug, Builder, Clone)]
59#[builder(build_fn(private, name = "build_internal"), derive(Debug, Serialize))]
60pub struct RuntimeConfig {
61 #[validate(range(min = 1))]
64 #[builder(default = "16")]
65 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
66 pub num_worker_threads: usize,
67
68 #[validate(range(min = 1))]
71 #[builder(default = "512")]
72 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
73 pub max_blocking_threads: usize,
74}
75
76impl RuntimeConfig {
77 pub fn builder() -> RuntimeConfigBuilder {
78 RuntimeConfigBuilder::default()
79 }
80
81 pub(crate) fn figment() -> Figment {
82 Figment::new()
83 .merge(Serialized::defaults(RuntimeConfig::default()))
84 .merge(Toml::file("/opt/dynamo/defaults/runtime.toml"))
85 .merge(Toml::file("/opt/dynamo/etc/runtime.toml"))
86 .merge(Env::prefixed("DYN_RUNTIME_").filter_map(|k| {
87 let full_key = format!("DYN_RUNTIME_{}", k.as_str());
88 match std::env::var(&full_key) {
90 Ok(v) if !v.is_empty() => Some(k.into()),
91 _ => None,
92 }
93 }))
94 }
95
96 pub fn from_settings() -> Result<RuntimeConfig> {
105 let config: RuntimeConfig = Self::figment().extract()?;
106 config.validate()?;
107 Ok(config)
108 }
109
110 pub fn single_threaded() -> Self {
111 RuntimeConfig {
112 num_worker_threads: 1,
113 max_blocking_threads: 1,
114 }
115 }
116
117 pub(crate) fn create_runtime(&self) -> Result<tokio::runtime::Runtime> {
119 Ok(tokio::runtime::Builder::new_multi_thread()
120 .worker_threads(self.num_worker_threads)
121 .max_blocking_threads(self.max_blocking_threads)
122 .enable_all()
123 .build()?)
124 }
125}
126
127impl Default for RuntimeConfig {
128 fn default() -> Self {
129 Self {
130 num_worker_threads: 16,
131 max_blocking_threads: 16,
132 }
133 }
134}
135
136impl RuntimeConfigBuilder {
137 pub fn build(&self) -> Result<RuntimeConfig> {
139 let config = self.build_internal()?;
140 config.validate()?;
141 Ok(config)
142 }
143}
144
145pub fn env_is_truthy(env: &str) -> bool {
147 match std::env::var(env) {
148 Ok(val) => is_truthy(val.as_str()),
149 Err(_) => false,
150 }
151}
152
153pub fn is_truthy(val: &str) -> bool {
158 matches!(val.to_lowercase().as_str(), "1" | "true" | "on" | "yes")
159}
160
161pub fn jsonl_logging_enabled() -> bool {
164 env_is_truthy("DYN_LOGGING_JSONL")
165}
166
167pub fn disable_ansi_logging() -> bool {
170 env_is_truthy("DYN_SDK_DISABLE_ANSI_LOGGING")
171}
172
173pub fn use_local_timezone() -> bool {
176 env_is_truthy("DYN_LOG_USE_LOCAL_TZ")
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_runtime_config_with_env_vars() -> Result<()> {
185 temp_env::with_vars(
186 vec![
187 ("DYN_RUNTIME_NUM_WORKER_THREADS", Some("24")),
188 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("32")),
189 ],
190 || {
191 let config = RuntimeConfig::from_settings()?;
192 assert_eq!(config.num_worker_threads, 24);
193 assert_eq!(config.max_blocking_threads, 32);
194 Ok(())
195 },
196 )
197 }
198
199 #[test]
200 fn test_runtime_config_defaults() -> Result<()> {
201 temp_env::with_vars(
202 vec![
203 ("DYN_RUNTIME_NUM_WORKER_THREADS", None::<&str>),
204 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("")),
205 ],
206 || {
207 let config = RuntimeConfig::from_settings()?;
208
209 let default_config = RuntimeConfig::default();
210 assert_eq!(config.num_worker_threads, default_config.num_worker_threads);
211 assert_eq!(
212 config.max_blocking_threads,
213 default_config.max_blocking_threads
214 );
215 Ok(())
216 },
217 )
218 }
219
220 #[test]
221 fn test_runtime_config_rejects_invalid_thread_count() -> Result<()> {
222 temp_env::with_vars(
223 vec![
224 ("DYN_RUNTIME_NUM_WORKER_THREADS", Some("0")),
225 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("0")),
226 ],
227 || {
228 let result = RuntimeConfig::from_settings();
229 assert!(result.is_err());
230 if let Err(e) = result {
231 assert!(e
232 .to_string()
233 .contains("num_worker_threads: Validation error"));
234 assert!(e
235 .to_string()
236 .contains("max_blocking_threads: Validation error"));
237 }
238 Ok(())
239 },
240 )
241 }
242}