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}