dope_core/backend/uring/driver/
mod.rs1pub(crate) mod files;
2
3
4use crate::backend::uring::sqe;
5
6use std::io::{self, Error, ErrorKind};
7use std::os::fd::AsRawFd;
8use std::process::abort;
9
10use io_uring::IoUring;
11
12use self::files::FileTable;
13use crate::backend::uring::provided::ring::Ring;
14use crate::driver::token::{
15 KIND_SHIFT, KeyTag, ROUTE_FRAMEWORK, SLOT_MASK, Token, TokenSlab, kind,
16};
17use crate::driver::route::Routes;
18use crate::driver::{Config, PushError};
19use crate::io::{BUFFER, BUFFER_SHIFT};
20use crate::io::fd::FdSlot;
21use io_uring::squeue::Entry;
22use io_uring::types::CancelBuilder;
23use sqe::Sqe;
24
25const SETSOCKOPT_CAP: usize = 4096;
26const _: () = assert!(SETSOCKOPT_CAP <= SLOT_MASK as usize + 1);
27type SetsockoptTag = KeyTag<ROUTE_FRAMEWORK, { kind::SETSOCKOPT }>;
28pub(crate) enum Disposition {
29 Drop,
30 DropBuffer(u16),
31 Internal,
32 Public(u64),
33}
34
35pub struct Uring {
36 pub(crate) uring: IoUring,
37 pub(crate) setsockopt: TokenSlab<libc::c_int, SetsockoptTag>,
38 pub(crate) files: FileTable,
39 pub(crate) provided: Ring,
40 next_slot: u32,
41 accept_slots: u32,
42 pub(crate) routes: Routes,
43}
44
45struct RingSetup {
46 cq_entries: u32,
47 ring_entries: u32,
48 defer_taskrun: bool,
49}
50
51impl RingSetup {
52 fn new(cq_entries: u32, defer_taskrun: bool, ring_entries: u32) -> Self {
53 Self {
54 cq_entries,
55 ring_entries,
56 defer_taskrun,
57 }
58 }
59
60 fn attempt(
61 &self,
62 no_sqarray: bool,
63 single_issuer: bool,
64 defer_taskrun: bool,
65 ) -> io::Result<IoUring> {
66 let mut builder = IoUring::builder();
67 builder.setup_submit_all();
68 if single_issuer {
69 builder.setup_single_issuer();
70 }
71 if no_sqarray {
72 builder.setup_no_sqarray();
73 }
74 builder.setup_cqsize(self.cq_entries);
75 if defer_taskrun {
76 builder.setup_defer_taskrun();
77 }
78 builder.build(self.ring_entries)
79 }
80
81 fn build(&self) -> io::Result<IoUring> {
82 let may_fallback = |error: &io::Error| error.raw_os_error() == Some(libc::EINVAL);
83 let mut built = self.attempt(true, true, self.defer_taskrun);
84 if built.as_ref().err().is_some_and(may_fallback) {
85 built = self.attempt(false, true, self.defer_taskrun);
86 }
87 if built.as_ref().err().is_some_and(may_fallback) {
88 built = self.attempt(false, false, false);
89 }
90 let uring = built?;
91
92 if !uring.params().is_feature_ext_arg() {
93 return Err(Error::new(ErrorKind::Unsupported, "IORING_FEAT_EXT_ARG"));
94 }
95 if !uring.params().is_feature_nodrop() {
96 return Err(Error::new(ErrorKind::Unsupported, "IORING_FEAT_NODROP"));
97 }
98 Ok(uring)
99 }
100
101 fn register_alloc_range(ring: &IoUring, len: u32) -> io::Result<()> {
102 #[repr(C)]
103 struct Range {
104 off: u32,
105 len: u32,
106 resv: u64,
107 }
108 const FILE_ALLOC_RANGE: libc::c_long = 25;
109 let range = Range {
110 off: 0,
111 len,
112 resv: 0,
113 };
114 let rc = unsafe {
115 libc::syscall(
116 libc::SYS_io_uring_register,
117 AsRawFd::as_raw_fd(ring) as libc::c_long,
118 FILE_ALLOC_RANGE,
119 &raw const range as usize as libc::c_long,
120 0 as libc::c_long,
121 )
122 };
123 if rc < 0 {
124 return Err(Error::last_os_error());
125 }
126 Ok(())
127 }
128}
129
130impl Uring {
131 pub(crate) fn new(cfg: &Config) -> io::Result<(Self, usize)> {
132 let uring = RingSetup::new(cfg.cq_entries, cfg.defer_taskrun, cfg.ring_entries).build()?;
133 match uring
134 .submitter()
135 .register_sync_cancel(None, CancelBuilder::user_data(u64::MAX).all())
136 {
137 Ok(()) => {}
138 Err(error) if error.kind() == ErrorKind::NotFound => {}
139 Err(error) => return Err(error),
140 }
141 uring
142 .submitter()
143 .register_files_sparse(cfg.fixed_file_slots)?;
144 RingSetup::register_alloc_range(&uring, cfg.accept_slots)?;
145 let provided = Ring::new(&uring.submitter(), cfg.provided.entries, cfg.provided.len)?;
146
147 let slots = cfg.fixed_file_slots as usize;
148 Ok((
149 Uring {
150 setsockopt: TokenSlab::with_capacity(SETSOCKOPT_CAP),
151 files: FileTable::new(slots),
152 uring,
153 provided,
154 next_slot: cfg.fixed_file_slots,
155 accept_slots: cfg.accept_slots,
156 routes: Routes::new(),
157 },
158 slots,
159 ))
160 }
161}
162
163impl Drop for Uring {
164 fn drop(&mut self) {
165 self.shutdown();
166 }
167}
168
169impl Uring {
170 pub(crate) fn shutdown(&mut self) {
171 let _ = self.uring.submit();
172 match self
173 .uring
174 .submitter()
175 .register_sync_cancel(None, CancelBuilder::any())
176 {
177 Ok(()) => {}
178 Err(error) if error.kind() == ErrorKind::NotFound => {}
179 Err(_) => abort(),
180 }
181 loop {
182 let mut drained = false;
183 {
184 let Self {
185 uring,
186 setsockopt,
187 files,
188 provided,
189 routes,
190 ..
191 } = self;
192 let mut cq = uring.completion();
193 for item in cq.by_ref() {
194 drained = true;
195 if let Disposition::DropBuffer(bid) = Self::complete_cqe(
196 setsockopt,
197 files,
198 routes,
199 item.user_data(),
200 item.result(),
201 item.flags(),
202 ) {
203 provided.defer(bid);
204 }
205 }
206 cq.sync();
207 }
208 if !drained {
209 break;
210 }
211 let _ = self.uring.submit();
212 }
213 }
214
215 pub(crate) fn entry_push(uring: &mut IoUring, entry: &Entry) -> Result<(), PushError> {
216 if unsafe { uring.submission().push(entry) }.is_ok() {
217 return Ok(());
218 }
219 uring.submit().map_err(|_| PushError)?;
220 unsafe { uring.submission().push(entry) }.map_err(|_| PushError)
221 }
222
223 pub(crate) fn await_one(&mut self) -> io::Result<i32> {
224 loop {
225 self.uring.submitter().submit_and_wait(1)?;
226 let mut found = None;
227 {
228 let Self {
229 uring,
230 setsockopt,
231 files,
232 provided,
233 routes,
234 ..
235 } = self;
236 let mut cq = uring.completion();
237 for item in cq.by_ref() {
238 let result = item.result();
239 match Self::complete_cqe(
240 setsockopt,
241 files,
242 routes,
243 item.user_data(),
244 result,
245 item.flags(),
246 ) {
247 Disposition::Drop => {}
248 Disposition::DropBuffer(bid) => provided.defer(bid),
249 Disposition::Internal | Disposition::Public(_) => {
250 found = Some(result);
251 }
252 }
253 }
254 cq.sync();
255 }
256 self.flush_deferred_close();
257 self.flush_ready_create();
258 if let Some(result) = found {
259 return Ok(result);
260 }
261 }
262 }
263
264 pub(crate) fn alloc_fixed_range(&mut self, len: u32) -> io::Result<u32> {
265 let base = self
266 .next_slot
267 .checked_sub(len)
268 .filter(|&b| b >= self.accept_slots)
269 .ok_or_else(|| {
270 Error::new(ErrorKind::OutOfMemory, "dope: fixed-file slots exhausted")
271 })?;
272 self.next_slot = base;
273 Ok(base)
274 }
275
276 fn release_setsockopt(
277 setsockopt: &mut TokenSlab<libc::c_int, SetsockoptTag>,
278 token: Token,
279 ) -> bool {
280 let Some(parts) = token.parts::<SetsockoptTag>() else {
281 return false;
282 };
283 setsockopt.remove_parts(parts.slab());
284 true
285 }
286
287 pub(crate) fn complete_cqe(
288 setsockopt: &mut TokenSlab<libc::c_int, SetsockoptTag>,
289 files: &mut FileTable,
290 routes: &Routes,
291 user_data: u64,
292 result: i32,
293 flags: u32,
294 ) -> Disposition {
295 let Some(token) = Token::try_from_raw(user_data) else {
296 return Disposition::Drop;
297 };
298 if Self::release_setsockopt(setsockopt, token) {
299 return Disposition::Internal;
300 }
301 let op_kind = (user_data >> KIND_SHIFT) as u8;
302 if token.route() == ROUTE_FRAMEWORK {
303 return match op_kind {
304 kind::CLOSE_PREP => Disposition::Drop,
305 kind::CLOSE => {
306 files.complete_close(token.slot());
307 Disposition::Drop
308 }
309 kind::CREATE => files
310 .complete_create(token.slot(), result)
311 .map_or(Disposition::Drop, Disposition::Public),
312 _ => Disposition::Public(user_data),
313 };
314 }
315 if op_kind == kind::ACCEPT {
316 let poisoned = routes.is_poisoned(token.route());
317 files.mark_accepted(result, poisoned);
318 if poisoned {
319 return Disposition::Drop;
320 }
321 } else if op_kind == kind::OPEN && result >= 0 && routes.is_poisoned(token.route()) {
322 unsafe {
323 libc::close(result);
324 }
325 return Disposition::Drop;
326 } else if op_kind == kind::RECV
327 && flags & BUFFER != 0
328 && routes.is_poisoned(token.route())
329 {
330 return Disposition::DropBuffer((flags >> BUFFER_SHIFT) as u16);
331 }
332 Disposition::Public(user_data)
333 }
334
335 fn push_close_at(uring: &mut IoUring, slot: FdSlot) -> bool {
336 let shut = Sqe::shutdown_linked_at(slot, libc::SHUT_RDWR);
337 let close = Sqe::close_at(slot);
338 let entries = [shut.entry().clone(), close.entry().clone()];
339 unsafe { uring.submission().push_multiple(&entries) }.is_ok()
340 }
341
342 pub(crate) fn flush_deferred_close(&mut self) {
343 let Self { uring, files, .. } = self;
344 files.flush_deferred_close(|slot| Self::push_close_at(uring, slot));
345 }
346
347 pub(crate) fn flush_ready_create(&mut self) {
348 let Self { uring, files, .. } = self;
349 files.flush_ready(|sqe| Self::entry_push(uring, sqe.entry()).is_ok());
350 }
351
352 pub(crate) fn close_fd(&mut self, slot: FdSlot) {
353 let Self { uring, files, .. } = self;
354 files.release(slot, |slot| {
355 Self::push_close_at(uring, slot)
356 || (uring.submit().is_ok() && Self::push_close_at(uring, slot))
357 });
358 }
359}