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