use std::num::NonZeroUsize;
use std::time::Duration;
use thiserror::Error;
const DEFAULT_CAPACITY: NonZeroUsize = NonZeroUsize::new(1024).unwrap();
const DEFAULT_SUBSCRIBER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
pub(crate) const MAX_ASYNC_CAPACITY: usize = tokio::sync::Semaphore::MAX_PERMITS;
pub(crate) fn validate_async_capacity(
field: &'static str,
value: NonZeroUsize,
) -> Result<(), ConfigError> {
if value.get() > MAX_ASYNC_CAPACITY {
Err(ConfigError::TooLarge {
field,
value: value.get(),
max: MAX_ASYNC_CAPACITY,
})
} else {
Ok(())
}
}
fn checked_async_capacity(field: &'static str, value: usize) -> Result<NonZeroUsize, ConfigError> {
let value = NonZeroUsize::new(value).ok_or(ConfigError::Zero { field })?;
validate_async_capacity(field, value)?;
Ok(value)
}
const MAX_GRACE: Duration = Duration::from_secs(60 * 60 * 24 * 365 * 30);
const fn normalize_grace(grace: Duration) -> Duration {
if grace.as_secs() > MAX_GRACE.as_secs()
|| (grace.as_secs() == MAX_GRACE.as_secs()
&& grace.subsec_nanos() > MAX_GRACE.subsec_nanos())
{
MAX_GRACE
} else {
grace
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum ConfigError {
#[error("{field} must be greater than zero")]
#[non_exhaustive]
Zero {
field: &'static str,
},
#[error("{field} must not exceed {max}; got {value}")]
#[non_exhaustive]
TooLarge {
field: &'static str,
value: usize,
max: usize,
},
}
#[derive(Clone, Debug)]
#[must_use]
pub struct SupervisorConfig {
grace: Duration,
subscriber_shutdown_timeout: Duration,
max_concurrent: Option<NonZeroUsize>,
max_registered_tasks: Option<NonZeroUsize>,
ownership_capacity: Option<NonZeroUsize>,
bus_capacity: NonZeroUsize,
registry_queue_capacity: NonZeroUsize,
}
impl SupervisorConfig {
pub const fn new() -> Self {
Self {
grace: Duration::from_secs(60),
subscriber_shutdown_timeout: DEFAULT_SUBSCRIBER_SHUTDOWN_TIMEOUT,
max_concurrent: None,
max_registered_tasks: Some(DEFAULT_CAPACITY),
ownership_capacity: Some(DEFAULT_CAPACITY),
bus_capacity: DEFAULT_CAPACITY,
registry_queue_capacity: DEFAULT_CAPACITY,
}
}
#[must_use]
pub const fn grace(&self) -> Duration {
self.grace
}
#[must_use]
pub const fn subscriber_shutdown_timeout(&self) -> Duration {
self.subscriber_shutdown_timeout
}
#[must_use]
pub const fn max_concurrent(&self) -> Option<NonZeroUsize> {
self.max_concurrent
}
#[must_use]
pub const fn max_registered_tasks(&self) -> Option<NonZeroUsize> {
self.max_registered_tasks
}
#[must_use]
pub const fn ownership_capacity(&self) -> Option<NonZeroUsize> {
self.ownership_capacity
}
#[must_use]
pub const fn bus_capacity(&self) -> NonZeroUsize {
self.bus_capacity
}
#[must_use]
pub const fn registry_queue_capacity(&self) -> NonZeroUsize {
self.registry_queue_capacity
}
pub const fn with_grace(mut self, grace: Duration) -> Self {
self.grace = normalize_grace(grace);
self
}
pub const fn with_subscriber_shutdown_timeout(mut self, timeout: Duration) -> Self {
self.subscriber_shutdown_timeout = timeout;
self
}
pub const fn with_max_concurrent(mut self, max_concurrent: Option<NonZeroUsize>) -> Self {
self.max_concurrent = max_concurrent;
self
}
pub fn try_with_max_concurrent(self, max_concurrent: usize) -> Result<Self, ConfigError> {
let value = checked_async_capacity("max_concurrent", max_concurrent)?;
Ok(self.with_max_concurrent(Some(value)))
}
pub const fn with_max_registered_tasks(
mut self,
max_registered_tasks: Option<NonZeroUsize>,
) -> Self {
self.max_registered_tasks = max_registered_tasks;
self
}
pub fn try_with_max_registered_tasks(
self,
max_registered_tasks: usize,
) -> Result<Self, ConfigError> {
let value = NonZeroUsize::new(max_registered_tasks).ok_or(ConfigError::Zero {
field: "max_registered_tasks",
})?;
Ok(self.with_max_registered_tasks(Some(value)))
}
pub const fn with_ownership_capacity(
mut self,
ownership_capacity: Option<NonZeroUsize>,
) -> Self {
self.ownership_capacity = ownership_capacity;
self
}
pub fn try_with_ownership_capacity(
self,
ownership_capacity: usize,
) -> Result<Self, ConfigError> {
let value = NonZeroUsize::new(ownership_capacity).ok_or(ConfigError::Zero {
field: "ownership_capacity",
})?;
Ok(self.with_ownership_capacity(Some(value)))
}
pub const fn with_bus_capacity(mut self, bus_capacity: NonZeroUsize) -> Self {
self.bus_capacity = bus_capacity;
self
}
pub fn try_with_bus_capacity(self, bus_capacity: usize) -> Result<Self, ConfigError> {
let value = checked_async_capacity("bus_capacity", bus_capacity)?;
Ok(self.with_bus_capacity(value))
}
pub const fn with_registry_queue_capacity(
mut self,
registry_queue_capacity: NonZeroUsize,
) -> Self {
self.registry_queue_capacity = registry_queue_capacity;
self
}
pub fn try_with_registry_queue_capacity(
self,
registry_queue_capacity: usize,
) -> Result<Self, ConfigError> {
let value = checked_async_capacity("registry_queue_capacity", registry_queue_capacity)?;
Ok(self.with_registry_queue_capacity(value))
}
pub(crate) fn validate(&self) -> Result<(), ConfigError> {
if let Some(max_concurrent) = self.max_concurrent {
validate_async_capacity("max_concurrent", max_concurrent)?;
}
validate_async_capacity("bus_capacity", self.bus_capacity)?;
validate_async_capacity("registry_queue_capacity", self.registry_queue_capacity)
}
}
impl Default for SupervisorConfig {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_contract_is_explicit() {
const CONFIG: SupervisorConfig = SupervisorConfig::new();
const LIMITED: SupervisorConfig =
SupervisorConfig::new().with_max_concurrent(NonZeroUsize::new(4));
let config = SupervisorConfig::default();
assert_eq!(CONFIG.grace(), config.grace());
assert_eq!(CONFIG.max_concurrent(), config.max_concurrent());
assert_eq!(LIMITED.max_concurrent().map(NonZeroUsize::get), Some(4));
assert_eq!(config.grace(), Duration::from_secs(60));
assert_eq!(config.subscriber_shutdown_timeout(), Duration::from_secs(5));
assert_eq!(config.max_concurrent(), None);
assert_eq!(
config.max_registered_tasks().map(NonZeroUsize::get),
Some(1024)
);
assert_eq!(
config.ownership_capacity().map(NonZeroUsize::get),
Some(1024)
);
assert_eq!(config.bus_capacity().get(), 1024);
assert_eq!(config.registry_queue_capacity().get(), 1024);
}
#[test]
fn typed_builders_preserve_runtime_invariants() {
let config = SupervisorConfig::default()
.with_grace(Duration::ZERO)
.with_subscriber_shutdown_timeout(Duration::from_secs(2))
.with_max_concurrent(NonZeroUsize::new(4))
.with_max_registered_tasks(NonZeroUsize::new(32))
.with_ownership_capacity(NonZeroUsize::new(64))
.with_bus_capacity(NonZeroUsize::new(8).unwrap())
.with_registry_queue_capacity(NonZeroUsize::new(16).unwrap());
assert_eq!(config.grace(), Duration::ZERO);
assert_eq!(config.subscriber_shutdown_timeout(), Duration::from_secs(2));
assert_eq!(config.max_concurrent().map(NonZeroUsize::get), Some(4));
assert_eq!(
config.max_registered_tasks().map(NonZeroUsize::get),
Some(32)
);
assert_eq!(config.ownership_capacity().map(NonZeroUsize::get), Some(64));
assert_eq!(config.bus_capacity().get(), 8);
assert_eq!(config.registry_queue_capacity().get(), 16);
}
#[test]
fn grace_is_normalized_once_and_getter_returns_the_effective_value() {
let maximum = SupervisorConfig::new().with_grace(MAX_GRACE);
let excessive = SupervisorConfig::new().with_grace(Duration::MAX);
let fractional_excess = SupervisorConfig::new().with_grace(Duration::new(
MAX_GRACE.as_secs(),
MAX_GRACE.subsec_nanos() + 1,
));
assert_eq!(maximum.grace(), MAX_GRACE);
assert_eq!(excessive.grace(), MAX_GRACE);
assert_eq!(fractional_excess.grace(), MAX_GRACE);
}
#[test]
fn raw_zero_values_return_clear_errors() {
type RawSetter = fn(SupervisorConfig, usize) -> Result<SupervisorConfig, ConfigError>;
let cases: [(&str, RawSetter); 5] = [
("max_concurrent", SupervisorConfig::try_with_max_concurrent),
(
"max_registered_tasks",
SupervisorConfig::try_with_max_registered_tasks,
),
(
"ownership_capacity",
SupervisorConfig::try_with_ownership_capacity,
),
("bus_capacity", SupervisorConfig::try_with_bus_capacity),
(
"registry_queue_capacity",
SupervisorConfig::try_with_registry_queue_capacity,
),
];
for (field, set) in cases {
assert_eq!(
set(SupervisorConfig::default(), 0).unwrap_err(),
ConfigError::Zero { field }
);
}
}
#[test]
fn ownership_capacity_can_be_disabled_explicitly() {
let config = SupervisorConfig::default().with_ownership_capacity(None);
assert_eq!(config.ownership_capacity(), None);
}
#[test]
fn async_capacities_reject_values_above_tokio_structural_limit() {
type RawSetter = fn(SupervisorConfig, usize) -> Result<SupervisorConfig, ConfigError>;
let cases: [(&str, RawSetter); 3] = [
("max_concurrent", SupervisorConfig::try_with_max_concurrent),
("bus_capacity", SupervisorConfig::try_with_bus_capacity),
(
"registry_queue_capacity",
SupervisorConfig::try_with_registry_queue_capacity,
),
];
let excessive = MAX_ASYNC_CAPACITY + 1;
for (field, set) in cases {
assert_eq!(
set(SupervisorConfig::default(), excessive).unwrap_err(),
ConfigError::TooLarge {
field,
value: excessive,
max: MAX_ASYNC_CAPACITY,
}
);
}
}
}