pal_async/io_uring.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! io-uring submission trait.
5
6// UNSAFETY: The `IoUringSubmit` trait has an unsafe method for submitting SQEs.
7#![expect(unsafe_code)]
8
9pub use squeue::Entry;
10
11use io_uring::squeue;
12use std::future::Future;
13use std::io;
14
15/// Component trait for drivers that optionally support io-uring submission.
16///
17/// All types that participate in the `Driver` blanket impl on Linux must
18/// implement this trait. There is no default—implementors must explicitly
19/// return `None` if they do not support io-uring, so that wrapper types
20/// do not silently drop the capability.
21#[cfg(target_os = "linux")]
22pub trait IoUringDriver {
23 /// The type used to submit io-uring operations.
24 ///
25 /// Use [`NoIoUring`] if the driver does not support io-uring.
26 type Submitter: IoUringSubmit;
27
28 /// Returns an io-uring submitter.
29 fn io_uring_submitter(&self) -> Option<&Self::Submitter>;
30}
31
32/// A type that represents the absence of io-uring support.
33pub enum NoIoUring {}
34
35impl IoUringSubmit for NoIoUring {
36 fn probe(&self, _opcode: u8) -> bool {
37 match *self {}
38 }
39
40 unsafe fn submit(&self, _sqe: Entry) -> impl Future<Output = io::Result<i32>> + Send + '_ {
41 (match *self {}) as std::future::Pending<_>
42 }
43}
44
45/// Trait for submitting io-uring operations.
46pub trait IoUringSubmit: Send + Sync {
47 /// Returns whether the given opcode is supported by the ring.
48 fn probe(&self, opcode: u8) -> bool;
49
50 /// Submits an io-uring SQE for asynchronous execution.
51 ///
52 /// Returns a future that completes with the IO result. The future **aborts
53 /// the process** if dropped while the IO is in flight, since there is no
54 /// way to synchronously cancel an in-flight io-uring operation.
55 ///
56 /// # Safety
57 ///
58 /// All memory referenced by the SQE must remain valid for the lifetime of
59 /// the returned future.
60 ///
61 /// This can be hard to do safely; in particular, if this future can be
62 /// leaked (via [`std::mem::forget`] or otherwise) then the caller must
63 /// ensure that any referenced memory also leaks. The easiest way to do that
64 /// is to ensure that the future is `await`ed in an async function or block
65 /// that owns the underlying memory. So, this is safe:
66 ///
67 /// ```rust,ignore
68 /// async fn write(uring: &impl IoUringSubmit, file: &File, buf: Vec<u8>) -> io::Result<usize> {
69 /// let sqe = opcode::Write::new(
70 /// types::Fd(file.as_raw_fd()), buf.as_ptr(), buf.len() as u32,
71 /// ).build();
72 /// // SAFETY: `buf` is owned by this async function's state machine.
73 /// // If the outer future is leaked, `buf` leaks with it, so the
74 /// // memory remains valid for the io-uring operation.
75 /// unsafe { uring.submit(sqe).await? };
76 /// Ok(buf.len())
77 /// }
78 /// ```
79 ///
80 /// But this is not:
81 ///
82 /// ```rust,ignore
83 /// async fn write(uring: &impl IoUringSubmit, file: &File, buf: &[u8]) -> io::Result<usize> {
84 /// let sqe = opcode::Write::new(
85 /// types::Fd(file.as_raw_fd()), buf.as_ptr(), buf.len() as u32,
86 /// ).build();
87 /// // NOT SAFE: `buf` is a borrow. If the outer future is leaked,
88 /// // the referent can be freed while the io-uring operation is
89 /// // still in flight.
90 /// unsafe { uring.submit(sqe).await? };
91 /// Ok(buf.len())
92 /// }
93 /// ```
94 unsafe fn submit(&self, sqe: Entry) -> impl Future<Output = io::Result<i32>> + Send + '_;
95}