use std::ffi::OsStr;
use std::fmt;
use std::num::NonZeroUsize;
use serde::Deserialize;
const WORKER_THREADS_ENV: &str = "TOKIO_WORKER_THREADS";
const MAX_BLOCKING_THREADS: usize = 512;
#[derive(Debug, Default, Deserialize)]
pub struct FileConfig {
#[serde(default)]
pub runtime: RuntimeConfig,
}
#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RuntimeConfig {
#[serde(default, deserialize_with = "present_count")]
pub worker_threads: Option<NonZeroUsize>,
}
fn present_count<'de, D: serde::Deserializer<'de>>(
de: D,
) -> Result<Option<NonZeroUsize>, D::Error> {
NonZeroUsize::deserialize(de).map(Some)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WorkerSource {
Config,
Environment,
AvailableParallelism,
}
impl fmt::Display for WorkerSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Config => "runtime.worker_threads",
Self::Environment => WORKER_THREADS_ENV,
Self::AvailableParallelism => "available parallelism",
})
}
}
impl FileConfig {
pub fn from_yaml_str(yaml: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(yaml)
}
}
impl RuntimeConfig {
pub fn build(&self) -> std::io::Result<(tokio::runtime::Runtime, WorkerSource)> {
self.build_with(std::env::var_os(WORKER_THREADS_ENV).as_deref())
}
fn build_with(
&self,
env: Option<&OsStr>,
) -> std::io::Result<(tokio::runtime::Runtime, WorkerSource)> {
let (workers, source) = match (self.worker_threads, env) {
(Some(n), _) => (n.get(), WorkerSource::Config),
(None, Some(raw)) => {
let n = raw
.to_str()
.and_then(|s| s.trim().parse::<NonZeroUsize>().ok())
.ok_or_else(|| {
std::io::Error::other(format!(
"{WORKER_THREADS_ENV} must be a positive integer, got {raw:?}"
))
})?;
(n.get(), WorkerSource::Environment)
}
(None, None) => (
std::thread::available_parallelism().map_or(1, NonZeroUsize::get),
WorkerSource::AvailableParallelism,
),
};
if workers.checked_add(MAX_BLOCKING_THREADS).is_none() {
return Err(std::io::Error::other(format!(
"{source} is too large: {workers} worker threads"
)));
}
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(workers)
.max_blocking_threads(MAX_BLOCKING_THREADS)
.enable_all()
.build()?;
Ok((runtime, source))
}
}
#[cfg(test)]
mod tests;