use std::{io, num::NonZeroU32, time::Duration};
use crate::runtime::local::Runtime;
#[derive(Debug, Clone)]
pub struct RuntimeConfig {
pub(crate) blocking_threads: usize,
pub(crate) blocking_keep_alive: Duration,
pub(crate) driver_capacity: usize,
pub(crate) event_interval: NonZeroU32,
pub(crate) buffer_pool_size: u16,
pub(crate) buffer_pool_buffer_len: usize,
pub(crate) multishot_accept_capacity: usize,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
blocking_threads: 256,
blocking_keep_alive: Duration::from_secs(60),
driver_capacity: 1024,
event_interval: NonZeroU32::new(61).expect("default event interval is nonzero"),
buffer_pool_size: 64,
buffer_pool_buffer_len: 8192,
multishot_accept_capacity: 128,
}
}
}
impl RuntimeConfig {
pub(crate) fn validate(&self) -> io::Result<()> {
#[cfg(target_os = "linux")]
{
if self.buffer_pool_size > crate::buf::MAX_BUFFER_RING_ENTRIES {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("buffer pool size cannot exceed {}", crate::buf::MAX_BUFFER_RING_ENTRIES),
));
}
if self.buffer_pool_buffer_len > u32::MAX as usize {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"buffer pool buffer length cannot exceed u32::MAX",
));
}
}
Ok(())
}
}
pub struct RuntimeBuilder {
config: RuntimeConfig,
}
impl Default for RuntimeBuilder {
fn default() -> Self {
Self::new()
}
}
impl RuntimeBuilder {
#[must_use]
pub fn new() -> Self {
Self {
config: RuntimeConfig::default(),
}
}
#[must_use]
pub fn blocking_threads(mut self, threads: usize) -> Self {
self.config.blocking_threads = threads.max(1);
self
}
#[must_use]
pub fn blocking_keep_alive(mut self, keep_alive: Duration) -> Self {
self.config.blocking_keep_alive = keep_alive;
self
}
#[must_use]
pub fn driver_capacity(mut self, capacity: usize) -> Self {
self.config.driver_capacity = capacity.max(1);
self
}
#[must_use]
#[track_caller]
pub fn event_interval(mut self, interval: u32) -> Self {
self.config.event_interval = NonZeroU32::new(interval).expect("event interval must be greater than zero");
self
}
#[must_use]
pub fn buffer_pool_size(mut self, size: u16) -> Self {
self.config.buffer_pool_size = size.max(1);
self
}
#[must_use]
pub fn buffer_pool_buffer_len(mut self, len: usize) -> Self {
self.config.buffer_pool_buffer_len = len.max(1);
self
}
#[must_use]
pub fn multishot_accept_capacity(mut self, capacity: usize) -> Self {
self.config.multishot_accept_capacity = capacity.max(1);
self
}
pub fn build(self) -> std::io::Result<Runtime> {
Runtime::from_config(self.config)
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
use crate::runtime::local::Runtime;
#[test]
fn builder_produces_runnable_runtime() {
let mut rt = RuntimeBuilder::new()
.blocking_threads(64)
.blocking_keep_alive(Duration::from_secs(10))
.driver_capacity(2048)
.build()
.expect("runtime should build");
rt.block_on(async { 7usize });
}
#[test]
fn default_builder_matches_runtime_new() {
let mut rt = Runtime::new().expect("runtime should build");
rt.block_on(async { 1usize });
}
#[test]
fn runtime_builder_entrypoint_works() {
let mut rt = Runtime::builder().build().expect("runtime should build");
rt.block_on(async { 2usize });
}
#[test]
fn defaults_are_sane() {
let config = RuntimeConfig::default();
assert_eq!(config.blocking_threads, 256);
assert_eq!(config.blocking_keep_alive, Duration::from_secs(60));
assert_eq!(config.driver_capacity, 1024);
assert_eq!(config.event_interval.get(), 61);
assert_eq!(config.multishot_accept_capacity, 128);
}
#[test]
fn event_interval_is_configurable() {
let builder = RuntimeBuilder::new().event_interval(31);
assert_eq!(builder.config.event_interval.get(), 31);
}
#[test]
fn multishot_accept_capacity_is_configurable() {
let builder = RuntimeBuilder::new().multishot_accept_capacity(32);
assert_eq!(builder.config.multishot_accept_capacity, 32);
}
#[cfg(target_os = "linux")]
#[test]
fn oversized_buffer_pool_is_rejected() {
let mut config = RuntimeConfig::default();
config.buffer_pool_size = crate::buf::MAX_BUFFER_RING_ENTRIES + 1;
let err = config.validate().expect_err("oversized pool must fail");
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
}
#[test]
#[should_panic(expected = "event interval must be greater than zero")]
fn zero_event_interval_is_rejected() {
let _ = RuntimeBuilder::new().event_interval(0);
}
}