agave_io_uring/ring.rs
1use {
2 crate::slab::FixedSlab,
3 io_uring::{
4 IoUring, cqueue, squeue,
5 types::{SubmitArgs, Timespec},
6 },
7 smallvec::{SmallVec, smallvec},
8 std::{io, os::fd::RawFd, time::Duration},
9};
10
11/// Trait for accessing the context and pushing operations to the [Ring].
12///
13/// Enables generic operations on [Ring] or [Completion].
14pub trait RingAccess {
15 type Context;
16 type Operation;
17
18 /// Returns a reference to the context value stored in a [Ring].
19 fn context(&self) -> &Self::Context;
20
21 /// Returns a mutable reference to the context value stored in a [Ring].
22 fn context_mut(&mut self) -> &mut Self::Context;
23
24 /// Pushes an operation for execution in io_uring.
25 ///
26 /// Once completed, [RingOp::complete] will be called with the result.
27 ///
28 /// Note that the exact moment the operation is submitted to the kernel is implementation
29 /// specific.
30 fn push(&mut self, op: Self::Operation) -> io::Result<()>;
31}
32
33/// An io_uring instance.
34pub struct Ring<T, E: RingOp<T>> {
35 ring: IoUring,
36 entries: FixedSlab<E>,
37 context: T,
38}
39
40impl<T, E: RingOp<T>> Ring<T, E> {
41 /// Creates a new ring with the provided io_uring instance and context.
42 ///
43 /// The context `T` is a user defined value that will be passed to entries `E` once they
44 /// complete. This value can be used to update state or perform additional actions as operations
45 /// complete asynchronously.
46 pub fn new(ring: IoUring, ctx: T) -> Self {
47 Self {
48 entries: FixedSlab::with_capacity(ring.params().cq_entries() as usize),
49 ring,
50 context: ctx,
51 }
52 }
53
54 /// Registers in-memory fixed buffers for I/O with the kernel.
55 ///
56 /// # Safety
57 ///
58 /// Callers must ensure that the iov_base and iov_len values are valid and will be valid until
59 /// buffers are unregistered or the ring destroyed, otherwise undefined behaviour may occur.
60 ///
61 /// See
62 /// [Submitter::register_buffers](https://docs.rs/io-uring/0.6.3/io_uring/struct.Submitter.html#method.register_buffers).
63 pub unsafe fn register_buffers(&self, iovecs: &[libc::iovec]) -> io::Result<()> {
64 unsafe { self.ring.submitter().register_buffers(iovecs) }
65 }
66
67 /// Registers file descriptors as fixed for I/O with the kernel.
68 ///
69 /// Operations may then use `types::Fixed(index)` for index in `fds` to refer to the
70 /// registered file descriptor.
71 ///
72 /// `-1` values can be used as slots for kernel managed fixed file descriptors (created by
73 /// open operation).
74 pub fn register_files(&self, fds: &[RawFd]) -> io::Result<()> {
75 self.ring.submitter().register_files(fds)
76 }
77
78 /// Submits all pending operations to the kernel.
79 ///
80 /// If the ring can't accept any more submissions because the completion
81 /// queue is full, this will process completions and retry until the
82 /// submissions are accepted.
83 ///
84 /// See also [Ring::process_completions].
85 pub fn submit(&mut self) -> io::Result<()> {
86 self.submit_and_wait(0, None).map(|_| ())
87 }
88
89 /// Submits all pending operations to the kernel and waits for completions.
90 ///
91 /// If no `timeout` is passed this will block until `want` completions are available. If a
92 /// timeout is passed, this will block until `want` completions are available or the timeout is
93 /// reached.
94 ///
95 /// Returns the number of completions received.
96 pub fn submit_and_wait(&mut self, want: usize, timeout: Option<Duration>) -> io::Result<usize> {
97 let mut args = SubmitArgs::new();
98 let ts;
99 if let Some(timeout) = timeout {
100 ts = Timespec::from(timeout);
101 args = args.timespec(&ts);
102 }
103
104 loop {
105 match self.ring.submitter().submit_with_args(want, &args) {
106 Ok(n) => return Ok(n),
107 Err(e) if e.raw_os_error() == Some(libc::ETIME) => return Ok(0),
108 Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {
109 // the completion queue is full, process completions and retry
110 self.process_completions()?;
111 continue;
112 }
113 Err(e) if e.raw_os_error() == Some(libc::EINTR) => return Ok(0),
114 Err(e) => return Err(e),
115 }
116 }
117 }
118
119 /// Processes completions from the kernel.
120 ///
121 /// This will process all completions currently available in the completion
122 /// queue and invoke [RingOp::complete] for each completed operation.
123 pub fn process_completions(&mut self) -> io::Result<()> {
124 let mut completion = self.ring.completion();
125 let mut new_entries = smallvec![];
126 while let Some(cqe) = completion.next() {
127 let completed_key = cqe.user_data() as usize;
128 let entry = self.entries.get_mut(completed_key).unwrap();
129 let result = entry.result(cqe.result());
130 let mut comp_ctx = Completion {
131 context: &mut self.context,
132 new_entries,
133 };
134 let res = entry.complete(&mut comp_ctx, result);
135 if !cqueue::more(cqe.flags()) {
136 self.entries.remove(completed_key);
137 }
138 res?;
139 new_entries = std::mem::take(&mut comp_ctx.new_entries);
140 if !new_entries.is_empty() {
141 completion.sync();
142 drop(completion);
143 for new_entry in new_entries.drain(..) {
144 self.push(new_entry)?;
145 }
146 completion = self.ring.completion();
147 }
148 }
149
150 Ok(())
151 }
152
153 /// Drains the ring.
154 ///
155 /// This will submit all pending operations to the kernel and process all
156 /// completions until the ring is empty.
157 pub fn drain(&mut self) -> io::Result<()> {
158 loop {
159 self.process_completions()?;
160
161 if self.entries.is_empty() {
162 break;
163 }
164
165 match self.ring.submitter().submit_with_args(
166 1,
167 &SubmitArgs::new().timespec(&Timespec::from(Duration::from_millis(10))),
168 ) {
169 Ok(_) => {}
170 Err(e) if e.raw_os_error() == Some(libc::ETIME) => {}
171 Err(e) => return Err(e),
172 }
173 }
174
175 Ok(())
176 }
177}
178
179/// Trait for operations that can be submitted to a [Ring].
180pub trait RingOp<T> {
181 fn entry(&mut self) -> squeue::Entry;
182 fn complete(&mut self, ctx: &mut Completion<T, Self>, res: io::Result<i32>) -> io::Result<()>
183 where
184 Self: Sized;
185 fn result(&self, res: i32) -> io::Result<i32> {
186 if res < 0 {
187 Err(io::Error::from_raw_os_error(res.wrapping_neg()))
188 } else {
189 Ok(res)
190 }
191 }
192}
193
194/// Context object passed to [RingOp::complete].
195pub struct Completion<'a, T, E: RingOp<T>> {
196 // Give new_entries a stack size of 2 to avoid heap allocations in the common case where only
197 // one or two ops are queued from a completion handler.
198 //
199 // It's common to want to queue some extra work after a completion, for instance if you've
200 // completed a read and want to close the file descriptor, or if you're doing chained operations
201 // and want to push the next one. It's less common to want to queue many operations.
202 new_entries: SmallVec<[E; 2]>,
203 context: &'a mut T,
204}
205
206impl<T, E: RingOp<T>> RingAccess for Ring<T, E> {
207 type Context = T;
208 type Operation = E;
209
210 fn context(&self) -> &T {
211 &self.context
212 }
213
214 fn context_mut(&mut self) -> &mut T {
215 &mut self.context
216 }
217
218 /// Pushes an operation to the submission queue.
219 ///
220 /// Note that the operation is not submitted to the kernel until [Ring::submit] is called. If
221 /// the submission queue is full, submit will be called internally to make room for the new
222 /// operation.
223 ///
224 /// See also [Ring::submit].
225 fn push(&mut self, op: E) -> io::Result<()> {
226 loop {
227 self.process_completions()?;
228
229 if !self.entries.is_full() {
230 break;
231 }
232 // if the entries slab is full, we need to submit and poll
233 // completions to make room
234 self.submit_and_wait(1, None)?;
235 }
236 let key = self.entries.insert(op);
237 let entry = self.entries.get_mut(key).unwrap().entry();
238 let entry = entry.user_data(key as u64);
239 // Safety: the entry is stored in self.entries and guaranteed to be valid for the lifetime
240 // of the operation. E implementations must still ensure that the entry
241 // remains valid until the last E::complete call.
242 while unsafe { self.ring.submission().push(&entry) }.is_err() {
243 self.submit()?;
244 self.process_completions()?;
245 }
246
247 Ok(())
248 }
249}
250
251impl<T, E: RingOp<T>> RingAccess for Completion<'_, T, E> {
252 type Context = T;
253 type Operation = E;
254
255 fn context(&self) -> &T {
256 self.context
257 }
258
259 fn context_mut(&mut self) -> &mut T {
260 self.context
261 }
262
263 /// This can be used to push new operations from within [RingOp::complete].
264 ///
265 /// Note that the operations are buffered until completion is finished and then pushed
266 /// to the parent [Ring].
267 fn push(&mut self, op: E) -> io::Result<()> {
268 self.new_entries.push(op);
269 Ok(())
270 }
271}