Skip to main content

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}