Skip to main content

wireshift_core/
config.rs

1//! Ring configuration types.
2
3use std::time::Duration;
4
5use crate::error::{Error, Result};
6
7/// Controls backend selection.
8#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
9#[non_exhaustive]
10pub enum BackendPreference {
11    /// Prefer `io_uring`, falling back automatically if unavailable.
12    #[default]
13    Auto,
14    /// Require Linux `io_uring`.
15    IoUring,
16    /// Force the fallback backend.
17    Fallback,
18}
19
20/// Configuration for pre-registered kernel buffer pools.
21///
22/// When enabled, the ring allocates `count` buffers of `size` bytes and
23/// registers them with the kernel via `IORING_REGISTER_BUFFERS`. Subsequent
24/// read and write operations using these buffers bypass per-I/O page pinning,
25/// yielding 2-3× throughput on high-IOPS workloads.
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub struct RegisteredBufferConfig {
28    /// Number of buffers to pre-allocate and register.
29    pub count: u32,
30    /// Size of each buffer in bytes.
31    pub size: u32,
32}
33
34/// Configuration for a [`crate::Ring`].
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct RingConfig {
37    /// Maximum in-flight operations.
38    pub queue_depth: u32,
39    /// Worker threads for the fallback backend.
40    pub fallback_threads: usize,
41    /// Completion batch size for drain strategies.
42    pub completion_batch: usize,
43    /// Backend selection policy.
44    pub backend: BackendPreference,
45    /// Optional default timeout for blocking waits.
46    pub default_timeout: Option<Duration>,
47    /// Enable `IORING_SETUP_SQPOLL`  -  the kernel polls the submission queue
48    /// from a dedicated kernel thread, eliminating the submit syscall entirely.
49    ///
50    /// The value is the idle timeout in milliseconds: the kernel thread spins
51    /// for this long after the last submission before sleeping. A typical value
52    /// is `2000` (2 seconds).
53    ///
54    /// Requires Linux 5.11+ (unprivileged SQPOLL was added in 5.19).
55    pub sq_poll_idle_ms: Option<u32>,
56    /// Pre-register a pool of kernel-pinned buffers for zero-copy I/O.
57    ///
58    /// When set, the native Linux `io_uring` backend uses this count and size
59    /// with [`IORING_REGISTER_BUFFERS`](https://man7.org/linux/man-pages/man2/io_uring_register.2.html).
60    /// When unset, that backend still registers a default pool (32 buffers of
61    /// 4096 bytes each) so fixed-buffer machinery is always available unless
62    /// registration fails during setup.
63    pub registered_buffers: Option<RegisteredBufferConfig>,
64}
65
66impl Default for RingConfig {
67    fn default() -> Self {
68        Self {
69            queue_depth: 256,
70            fallback_threads: 4,
71            completion_batch: 64,
72            backend: BackendPreference::Auto,
73            default_timeout: Some(Duration::from_secs(30)),
74            sq_poll_idle_ms: None,
75            registered_buffers: None,
76        }
77    }
78}
79
80impl RingConfig {
81    /// Returns a copy with the provided queue depth.
82    #[must_use]
83    pub fn with_queue_depth(mut self, queue_depth: u32) -> Self {
84        self.queue_depth = queue_depth;
85        self
86    }
87
88    /// Returns a copy with the provided backend policy.
89    #[must_use]
90    pub fn with_backend(mut self, backend: BackendPreference) -> Self {
91        self.backend = backend;
92        self
93    }
94
95    /// Returns a copy with the provided fallback thread count.
96    #[must_use]
97    pub fn with_fallback_threads(mut self, fallback_threads: usize) -> Self {
98        self.fallback_threads = fallback_threads;
99        self
100    }
101
102    /// Returns a copy with the provided completion batch size.
103    #[must_use]
104    pub fn with_completion_batch(mut self, completion_batch: usize) -> Self {
105        self.completion_batch = completion_batch;
106        self
107    }
108
109    /// Returns a copy with the provided default timeout.
110    #[must_use]
111    pub fn with_default_timeout(mut self, default_timeout: Option<Duration>) -> Self {
112        self.default_timeout = default_timeout;
113        self
114    }
115
116    /// Enables `IORING_SETUP_SQPOLL` with the given idle timeout in milliseconds.
117    ///
118    /// When active, the kernel polls the submission queue from a dedicated
119    /// thread, eliminating the `io_uring_enter` syscall for submissions.
120    /// The kernel thread sleeps after `idle_ms` milliseconds of inactivity.
121    #[must_use]
122    pub fn with_sq_poll(mut self, idle_ms: u32) -> Self {
123        self.sq_poll_idle_ms = Some(idle_ms);
124        self
125    }
126
127    /// Enables pre-registered kernel-pinned buffers.
128    ///
129    /// The ring allocates `count` buffers of `size` bytes and registers them
130    /// with the kernel at creation time. I/O operations using these buffers
131    /// bypass per-operation page pinning overhead.
132    #[must_use]
133    pub fn with_registered_buffers(mut self, count: u32, size: u32) -> Self {
134        self.registered_buffers = Some(RegisteredBufferConfig { count, size });
135        self
136    }
137
138    /// Validates the configuration.
139    pub fn validate(&self) -> Result<()> {
140        if self.queue_depth == 0 {
141            return Err(Error::invalid_config(
142                "queue_depth must be greater than zero",
143            ));
144        }
145        if self.queue_depth > 4096 {
146            return Err(Error::invalid_config(
147                "queue_depth above 4096 is rejected to bound kernel and fallback memory use",
148            ));
149        }
150        if self.fallback_threads == 0 {
151            return Err(Error::invalid_config(
152                "fallback_threads must be greater than zero",
153            ));
154        }
155        if self.completion_batch == 0 {
156            return Err(Error::invalid_config(
157                "completion_batch must be greater than zero",
158            ));
159        }
160        if let Some(ref rb) = self.registered_buffers {
161            if rb.count == 0 {
162                return Err(Error::invalid_config(
163                    "registered buffer count must be greater than zero",
164                ));
165            }
166            if rb.size == 0 {
167                return Err(Error::invalid_config(
168                    "registered buffer size must be greater than zero",
169                ));
170            }
171        }
172        Ok(())
173    }
174}