#[derive(Clone, Debug, Default)]
pub struct NucleiConfig {
pub iouring: IoUringConfiguration,
pub backend: Option<crate::IoBackend>,
}
#[derive(Clone, Debug)]
pub struct IoUringConfiguration {
pub queue_len: u32,
pub sqpoll_wake_interval: Option<u32>,
pub per_numa_bounded_worker_count: Option<u32>,
pub per_numa_unbounded_worker_count: Option<u32>,
pub aggressive_poll: bool,
pub iopoll_enabled: bool,
pub recv_buffers: u16,
pub recv_buffer_size: u32,
}
pub(crate) const MIN_QUEUE_LEN: u32 = 64;
pub(crate) const MIN_RECV_BUFFER_SIZE: u32 = 512;
pub(crate) const MAX_RECV_BUFFER_SIZE: u32 = 1 << 30;
pub(crate) fn refuse(
setting: &str,
value: impl core::fmt::Display,
allowed: impl core::fmt::Display,
) -> std::io::Error {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("{setting} is {value}; it must be {allowed}"),
)
}
const DEFAULT_RECV_BUFFERS: u16 = 256;
fn default_recv_buffers(queue_len: u32) -> u16 {
u16::try_from(
queue_len
.min(u32::from(DEFAULT_RECV_BUFFERS))
.next_power_of_two(),
)
.unwrap_or(DEFAULT_RECV_BUFFERS)
}
const DEFAULT_RECV_BUFFER_SIZE: u32 = 16 * 1024;
impl IoUringConfiguration {
pub(crate) fn validate(&self) -> std::io::Result<()> {
if self.queue_len < MIN_QUEUE_LEN {
return Err(refuse(
"io_uring queue_len",
self.queue_len,
format_args!("at least {MIN_QUEUE_LEN}"),
));
}
if self.recv_buffers == 0 {
return Ok(());
}
let twice = self.queue_len.saturating_mul(2);
if !self.recv_buffers.is_power_of_two() || u32::from(self.recv_buffers) >= twice {
return Err(refuse(
"io_uring recv_buffers",
self.recv_buffers,
format_args!(
"0, or a power of two from 1 to 32768 and below twice queue_len ({twice})"
),
));
}
if !(MIN_RECV_BUFFER_SIZE..=MAX_RECV_BUFFER_SIZE).contains(&self.recv_buffer_size) {
return Err(refuse(
"io_uring recv_buffer_size",
self.recv_buffer_size,
format_args!("from {MIN_RECV_BUFFER_SIZE} to {MAX_RECV_BUFFER_SIZE}"),
));
}
Ok(())
}
pub fn interrupt_driven(queue_len: u32) -> Self {
Self {
queue_len,
sqpoll_wake_interval: None,
per_numa_bounded_worker_count: None,
per_numa_unbounded_worker_count: None,
aggressive_poll: false,
iopoll_enabled: false,
recv_buffers: default_recv_buffers(queue_len),
recv_buffer_size: DEFAULT_RECV_BUFFER_SIZE,
}
}
pub fn low_latency_driven(queue_len: u32) -> Self {
Self {
queue_len,
sqpoll_wake_interval: Some(2),
aggressive_poll: true,
recv_buffers: default_recv_buffers(queue_len),
..Self::default()
}
}
pub fn kernel_poll_only(queue_len: u32) -> Self {
Self {
queue_len,
sqpoll_wake_interval: Some(2),
aggressive_poll: false,
recv_buffers: default_recv_buffers(queue_len),
..Self::default()
}
}
pub fn io_poll(queue_len: u32) -> Self {
Self {
queue_len,
iopoll_enabled: true,
recv_buffers: default_recv_buffers(queue_len),
..Self::default()
}
}
}
impl Default for IoUringConfiguration {
fn default() -> Self {
Self {
per_numa_bounded_worker_count: Some(1 << 8),
per_numa_unbounded_worker_count: Some(1 << 9),
..Self::interrupt_driven(1 << 11)
}
}
}
#[cfg(all(test, ring))]
mod tests;