use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkerPoolConfig {
#[serde(default = "default_min_threads")]
pub min_threads: usize,
#[serde(default)]
pub max_threads: usize,
#[serde(default = "default_grow_below")]
pub grow_below: f64,
#[serde(default = "default_shrink_above")]
pub shrink_above: f64,
#[serde(default = "default_emergency_above")]
pub emergency_above: f64,
#[serde(default = "default_memory_pressure_cap")]
pub memory_pressure_cap: f64,
#[serde(default = "default_scale_interval_secs")]
pub scale_interval_secs: u64,
#[serde(default = "default_async_concurrency")]
pub async_concurrency: usize,
#[serde(default = "default_health_saturation_timeout_secs")]
pub health_saturation_timeout_secs: u64,
}
fn default_min_threads() -> usize {
2
}
fn default_grow_below() -> f64 {
0.60
}
fn default_shrink_above() -> f64 {
0.85
}
fn default_emergency_above() -> f64 {
0.95
}
fn default_memory_pressure_cap() -> f64 {
0.80
}
fn default_scale_interval_secs() -> u64 {
5
}
fn default_async_concurrency() -> usize {
32
}
fn default_health_saturation_timeout_secs() -> u64 {
30
}
impl Default for WorkerPoolConfig {
fn default() -> Self {
Self {
min_threads: default_min_threads(),
max_threads: 0,
grow_below: default_grow_below(),
shrink_above: default_shrink_above(),
emergency_above: default_emergency_above(),
memory_pressure_cap: default_memory_pressure_cap(),
scale_interval_secs: default_scale_interval_secs(),
async_concurrency: default_async_concurrency(),
health_saturation_timeout_secs: default_health_saturation_timeout_secs(),
}
}
}
impl WorkerPoolConfig {
pub fn from_cascade(key: &str) -> Result<Self, crate::config::ConfigError> {
let pool_cfg: Self = if let Some(cfg) = crate::config::try_get() {
cfg.unmarshal_key(key).unwrap_or_default()
} else {
tracing::debug!("Config cascade not initialised, using default WorkerPoolConfig");
Self::default()
};
pool_cfg.validate()?;
Ok(pool_cfg)
}
pub fn validate(&self) -> Result<(), crate::config::ConfigError> {
if self.max_threads != 0 && self.min_threads > self.max_threads {
return Err(crate::config::ConfigError::InvalidValue {
key: "worker_pool.min_threads".into(),
reason: format!(
"min_threads ({}) > max_threads ({})",
self.min_threads, self.max_threads
),
});
}
if self.grow_below >= self.shrink_above {
return Err(crate::config::ConfigError::InvalidValue {
key: "worker_pool.grow_below".into(),
reason: format!(
"grow_below ({}) >= shrink_above ({})",
self.grow_below, self.shrink_above
),
});
}
if self.shrink_above >= self.emergency_above {
return Err(crate::config::ConfigError::InvalidValue {
key: "worker_pool.shrink_above".into(),
reason: format!(
"shrink_above ({}) >= emergency_above ({})",
self.shrink_above, self.emergency_above
),
});
}
Ok(())
}
pub fn resolve_max_threads(&mut self) {
let available = std::thread::available_parallelism().map_or(4, std::num::NonZero::get);
if self.max_threads == 0 {
self.max_threads = available;
} else {
self.max_threads = self.max_threads.min(available);
}
}
}