laminar_core/io_uring/
config.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum RingMode {
6 #[default]
8 Standard,
9 SqPoll,
11 IoPoll,
14 SqPollIoPoll,
16}
17
18impl RingMode {
19 #[must_use]
21 pub const fn uses_sqpoll(&self) -> bool {
22 matches!(self, Self::SqPoll | Self::SqPollIoPoll)
23 }
24
25 #[must_use]
27 pub const fn uses_iopoll(&self) -> bool {
28 matches!(self, Self::IoPoll | Self::SqPollIoPoll)
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct IoUringConfig {
35 pub ring_entries: u32,
37 pub mode: RingMode,
39 pub sqpoll_idle_ms: u32,
42 pub sqpoll_cpu: Option<u32>,
45 pub buffer_size: usize,
47 pub buffer_count: usize,
49 pub coop_taskrun: bool,
51 pub single_issuer: bool,
53 pub direct_table: bool,
55 pub direct_table_size: u32,
57}
58
59impl Default for IoUringConfig {
60 fn default() -> Self {
61 Self {
62 ring_entries: 256,
63 mode: RingMode::Standard,
64 sqpoll_idle_ms: 1000,
65 sqpoll_cpu: None,
66 buffer_size: 64 * 1024, buffer_count: 256, coop_taskrun: true,
69 single_issuer: true,
70 direct_table: false,
71 direct_table_size: 256,
72 }
73 }
74}
75
76impl IoUringConfig {
77 #[must_use]
79 pub fn builder() -> IoUringConfigBuilder {
80 IoUringConfigBuilder::default()
81 }
82
83 #[must_use]
99 pub fn auto() -> Self {
100 let caps = crate::detect::SystemCapabilities::detect();
101
102 let mode = if caps.io_uring.iopoll_supported && caps.storage.device_type.supports_iopoll() {
103 if caps.io_uring.sqpoll_supported {
104 RingMode::SqPollIoPoll
105 } else {
106 RingMode::IoPoll
107 }
108 } else if caps.io_uring.sqpoll_supported {
109 RingMode::SqPoll
110 } else {
111 RingMode::Standard
112 };
113
114 Self {
115 ring_entries: 256,
116 mode,
117 sqpoll_idle_ms: 1000,
118 sqpoll_cpu: None,
119 buffer_size: 64 * 1024,
120 buffer_count: 256,
121 coop_taskrun: caps.io_uring.coop_taskrun,
122 single_issuer: caps.io_uring.single_issuer,
123 direct_table: false,
124 direct_table_size: 256,
125 }
126 }
127
128 #[must_use]
130 pub const fn total_buffer_size(&self) -> usize {
131 self.buffer_size * self.buffer_count
132 }
133
134 pub fn validate(&self) -> Result<(), super::IoUringError> {
140 if !self.ring_entries.is_power_of_two() {
142 return Err(super::IoUringError::InvalidConfig(format!(
143 "ring_entries must be power of 2, got {}",
144 self.ring_entries
145 )));
146 }
147
148 if self.ring_entries < 2 {
150 return Err(super::IoUringError::InvalidConfig(
151 "ring_entries must be at least 2".to_string(),
152 ));
153 }
154
155 if self.buffer_size == 0 {
157 return Err(super::IoUringError::InvalidConfig(
158 "buffer_size must be positive".to_string(),
159 ));
160 }
161
162 if self.buffer_size > 16 * 1024 * 1024 {
163 return Err(super::IoUringError::InvalidConfig(
164 "buffer_size cannot exceed 16MB".to_string(),
165 ));
166 }
167
168 if self.buffer_count == 0 {
170 return Err(super::IoUringError::InvalidConfig(
171 "buffer_count must be positive".to_string(),
172 ));
173 }
174
175 if self.buffer_count > 65536 {
176 return Err(super::IoUringError::InvalidConfig(
177 "buffer_count cannot exceed 65536".to_string(),
178 ));
179 }
180
181 Ok(())
182 }
183}
184
185#[derive(Debug, Default)]
187pub struct IoUringConfigBuilder {
188 config: IoUringConfig,
189}
190
191impl IoUringConfigBuilder {
192 #[must_use]
194 pub const fn ring_entries(mut self, entries: u32) -> Self {
195 self.config.ring_entries = entries;
196 self
197 }
198
199 #[must_use]
201 pub const fn mode(mut self, mode: RingMode) -> Self {
202 self.config.mode = mode;
203 self
204 }
205
206 #[must_use]
208 pub const fn enable_sqpoll(mut self, idle_ms: u32) -> Self {
209 self.config.mode = RingMode::SqPoll;
210 self.config.sqpoll_idle_ms = idle_ms;
211 self
212 }
213
214 #[must_use]
216 pub const fn sqpoll_cpu(mut self, cpu: u32) -> Self {
217 self.config.sqpoll_cpu = Some(cpu);
218 self
219 }
220
221 #[must_use]
223 pub const fn enable_iopoll(mut self) -> Self {
224 self.config.mode = match self.config.mode {
225 RingMode::SqPoll | RingMode::SqPollIoPoll => RingMode::SqPollIoPoll,
226 _ => RingMode::IoPoll,
227 };
228 self
229 }
230
231 #[must_use]
233 pub const fn buffer_size(mut self, size: usize) -> Self {
234 self.config.buffer_size = size;
235 self
236 }
237
238 #[must_use]
240 pub const fn buffer_count(mut self, count: usize) -> Self {
241 self.config.buffer_count = count;
242 self
243 }
244
245 #[must_use]
247 pub const fn coop_taskrun(mut self, enable: bool) -> Self {
248 self.config.coop_taskrun = enable;
249 self
250 }
251
252 #[must_use]
254 pub const fn single_issuer(mut self, enable: bool) -> Self {
255 self.config.single_issuer = enable;
256 self
257 }
258
259 #[must_use]
261 pub const fn direct_table(mut self, size: u32) -> Self {
262 self.config.direct_table = true;
263 self.config.direct_table_size = size;
264 self
265 }
266
267 pub fn build(self) -> Result<IoUringConfig, super::IoUringError> {
273 self.config.validate()?;
274 Ok(self.config)
275 }
276
277 #[must_use]
279 pub fn build_unchecked(self) -> IoUringConfig {
280 self.config
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_default_config() {
290 let config = IoUringConfig::default();
291 assert_eq!(config.ring_entries, 256);
292 assert_eq!(config.mode, RingMode::Standard);
293 assert_eq!(config.buffer_size, 64 * 1024);
294 assert_eq!(config.buffer_count, 256);
295 assert!(config.coop_taskrun);
296 assert!(config.single_issuer);
297 }
298
299 #[test]
300 fn test_builder() {
301 let config = IoUringConfig::builder()
302 .ring_entries(512)
303 .enable_sqpoll(2000)
304 .sqpoll_cpu(4)
305 .buffer_size(128 * 1024)
306 .buffer_count(512)
307 .build()
308 .unwrap();
309
310 assert_eq!(config.ring_entries, 512);
311 assert!(config.mode.uses_sqpoll());
312 assert_eq!(config.sqpoll_idle_ms, 2000);
313 assert_eq!(config.sqpoll_cpu, Some(4));
314 assert_eq!(config.buffer_size, 128 * 1024);
315 assert_eq!(config.buffer_count, 512);
316 }
317
318 #[test]
319 fn test_ring_mode() {
320 assert!(!RingMode::Standard.uses_sqpoll());
321 assert!(!RingMode::Standard.uses_iopoll());
322
323 assert!(RingMode::SqPoll.uses_sqpoll());
324 assert!(!RingMode::SqPoll.uses_iopoll());
325
326 assert!(!RingMode::IoPoll.uses_sqpoll());
327 assert!(RingMode::IoPoll.uses_iopoll());
328
329 assert!(RingMode::SqPollIoPoll.uses_sqpoll());
330 assert!(RingMode::SqPollIoPoll.uses_iopoll());
331 }
332
333 #[test]
334 fn test_total_buffer_size() {
335 let config = IoUringConfig {
336 buffer_size: 64 * 1024,
337 buffer_count: 256,
338 ..Default::default()
339 };
340 assert_eq!(config.total_buffer_size(), 16 * 1024 * 1024); }
342
343 #[test]
344 fn test_validation_ring_entries_power_of_two() {
345 let config = IoUringConfig {
346 ring_entries: 100, ..Default::default()
348 };
349 assert!(config.validate().is_err());
350 }
351
352 #[test]
353 fn test_validation_buffer_size_zero() {
354 let config = IoUringConfig {
355 buffer_size: 0,
356 ..Default::default()
357 };
358 assert!(config.validate().is_err());
359 }
360
361 #[test]
362 fn test_validation_buffer_count_zero() {
363 let config = IoUringConfig {
364 buffer_count: 0,
365 ..Default::default()
366 };
367 assert!(config.validate().is_err());
368 }
369
370 #[test]
371 fn test_enable_iopoll_combines_with_sqpoll() {
372 let config = IoUringConfig::builder()
373 .enable_sqpoll(1000)
374 .enable_iopoll()
375 .build_unchecked();
376
377 assert_eq!(config.mode, RingMode::SqPollIoPoll);
378 }
379
380 #[test]
381 fn test_io_uring_config_auto() {
382 let config = IoUringConfig::auto();
383
384 assert_eq!(config.ring_entries, 256);
386 assert_eq!(config.buffer_size, 64 * 1024);
387 assert_eq!(config.buffer_count, 256);
388
389 assert!(config.validate().is_ok());
391
392 #[cfg(not(all(target_os = "linux", feature = "io-uring")))]
395 {
396 assert_eq!(config.mode, RingMode::Standard);
397 }
398 }
399}