Skip to main content

ntex_net/uring/
reactor.rs

1use std::cell::{Cell, UnsafeCell};
2use std::os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd};
3use std::{cmp, collections::VecDeque, fmt, io, mem, net, ptr, rc::Rc, sync::Arc};
4
5#[cfg(unix)]
6use std::os::unix::net::UnixStream as OsUnixStream;
7
8use ntex_io::Io;
9use ntex_io_uring::cqueue::{self, Entry as CEntry, more};
10use ntex_io_uring::opcode::{AsyncCancel, PollAdd};
11use ntex_io_uring::squeue::{Entry as SEntry, SubmissionQueue};
12use ntex_io_uring::{IoUring, Probe, Submitter, types::CancelBuilder, types::Fd};
13use ntex_rt::{DriverType, Notify, PollResult, Runtime, syscall};
14use ntex_service::cfg::SharedCfg;
15use socket2::{Protocol, SockAddr, Socket, Type};
16
17use super::{TcpStream, UnixStream, stream::StreamOps};
18use crate::channel::Receiver;
19
20pub trait Handler {
21    /// Operation is completed.
22    fn completed(&mut self, id: usize, flags: u32, result: io::Result<usize>);
23
24    /// Operation is canceled.
25    fn canceled(&mut self, id: usize);
26
27    /// The driver's turn has completed.
28    fn tick(&mut self);
29
30    /// Clean up the handle before dropping the driver.
31    fn cleanup(&mut self);
32}
33
34/// Api for io-uring reactor.
35pub struct ReactorApi {
36    batch: u64,
37    inner: Rc<ReactorInner>,
38}
39
40impl ReactorApi {
41    #[inline]
42    /// Check if kernel ver 6.1 or greater
43    pub fn is_new(&self) -> bool {
44        self.inner.flags.get().contains(Flags::NEW)
45    }
46
47    fn submit_inner<F>(&self, f: F)
48    where
49        F: FnOnce(&mut SEntry),
50    {
51        unsafe {
52            let changes = &mut *self.inner.changes.get();
53            // the run loop syncs the queue before submitting
54            let f = if changes.is_empty() {
55                match self.inner.ring.submission_unsynced().try_push_inline(f) {
56                    Ok(()) => return,
57                    Err(f) => f,
58                }
59            } else {
60                f
61            };
62            changes.push_back(mem::MaybeUninit::uninit());
63            let entry = changes.back_mut().unwrap();
64            ptr::write_bytes(entry.as_mut_ptr(), 0, 1);
65            f(entry.assume_init_mut());
66        }
67    }
68
69    #[inline]
70    /// Submit request to the driver.
71    pub fn submit(&self, id: u32, entry: SEntry) {
72        self.submit_inner(|en| {
73            *en = entry;
74            en.set_user_data(u64::from(id) | self.batch);
75        });
76    }
77
78    #[inline]
79    /// Submit request to the driver.
80    pub fn submit_inline<F>(&self, id: u32, f: F)
81    where
82        F: FnOnce(&mut SEntry),
83    {
84        self.submit_inner(|en| {
85            f(en);
86            en.set_user_data(u64::from(id) | self.batch);
87        });
88    }
89
90    #[inline]
91    /// Attempt to cancel an already issued request.
92    pub fn cancel(&self, id: u32) {
93        self.submit_inner(|en| {
94            *en = AsyncCancel::new(u64::from(id) | self.batch)
95                .build()
96                .user_data(Reactor::CANCEL);
97        });
98    }
99
100    #[inline]
101    /// Attempt to sync cancel all requests.
102    pub fn cancel_all_sync(&self, fd: Fd) -> io::Result<()> {
103        self.inner
104            .ring
105            .submitter()
106            .register_sync_cancel(None, CancelBuilder::fd(fd).all())
107    }
108
109    /// Get whether a specific io-uring opcode is supported.
110    pub fn is_supported(&self, opcode: u8) -> bool {
111        self.inner.probe.is_supported(opcode)
112    }
113}
114
115/// io-uring reactor.
116pub struct Reactor {
117    fd: RawFd,
118    hid: Cell<u64>,
119    notifier: Notifier,
120    #[allow(clippy::box_collection)]
121    handlers: Cell<Option<Box<Vec<HandlerItem>>>>,
122    inner: Rc<ReactorInner>,
123}
124
125struct HandlerItem {
126    hnd: Box<dyn Handler>,
127    modified: bool,
128}
129
130impl HandlerItem {
131    fn tick(&mut self) {
132        if self.modified {
133            self.modified = false;
134            self.hnd.tick();
135        }
136    }
137}
138
139bitflags::bitflags! {
140    #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
141    struct Flags: u8 {
142        const NEW      = 0b0000_0001;
143        const NOTIFIER = 0b0000_0010;
144    }
145}
146
147struct ReactorInner {
148    probe: Probe,
149    flags: Cell<Flags>,
150    ring: IoUring<SEntry, CEntry>,
151    changes: UnsafeCell<VecDeque<mem::MaybeUninit<SEntry>>>,
152}
153
154impl Reactor {
155    const NOTIFY: u64 = u64::MAX;
156    const CANCEL: u64 = u64::MAX - 1;
157    const BATCH: u64 = 48;
158    const BATCH_MASK: u64 = 0xFFFF_0000_0000_0000;
159    const DATA_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
160
161    /// Create io-uring driver
162    pub fn new(capacity: u32) -> io::Result<Self> {
163        // Create ring
164        let (new, ring) = if let Ok(ring) = IoUring::builder()
165            .setup_coop_taskrun()
166            .setup_taskrun_flag()
167            .setup_single_issuer()
168            .setup_defer_taskrun()
169            .build(capacity)
170        {
171            log::info!(
172                "New io-uring driver with single-issuer, coop-taskrun, taskrun-flag, defer-taskrun"
173            );
174            (true, ring)
175        } else if let Ok(ring) = IoUring::builder().setup_single_issuer().build(capacity) {
176            log::info!("New io-uring driver with single-issuer");
177            (true, ring)
178        } else {
179            let ring = IoUring::builder().build(capacity)?;
180            log::info!("New io-uring driver");
181            (false, ring)
182        };
183
184        let mut probe = Probe::new();
185        ring.submitter().register_probe(&mut probe)?;
186
187        // Remote notifier
188        let notifier = Notifier::new()?;
189        unsafe {
190            let sq = ring.submission();
191            sq.push(
192                &PollAdd::new(Fd(notifier.as_raw_fd()), libc::POLLIN as _)
193                    .multi(true)
194                    .build()
195                    .user_data(Self::NOTIFY),
196            )
197            .expect("the squeue sould not be full");
198            sq.sync();
199        }
200
201        let fd = ring.as_raw_fd();
202        let inner = Rc::new(ReactorInner {
203            ring,
204            probe,
205            flags: Cell::new(if new { Flags::NEW } else { Flags::empty() }),
206            changes: UnsafeCell::new(VecDeque::with_capacity(32)),
207        });
208
209        Ok(Self {
210            fd,
211            inner,
212            notifier,
213            hid: Cell::new(0),
214            handlers: Cell::new(Some(Box::new(Vec::new()))),
215        })
216    }
217
218    /// Driver type
219    pub const fn tp(&self) -> DriverType {
220        DriverType::IoUring
221    }
222
223    /// Register updates handler
224    pub fn register<F>(&self, f: F)
225    where
226        F: FnOnce(ReactorApi) -> Box<dyn Handler>,
227    {
228        let id = self.hid.get();
229        let mut handlers = self.handlers.take().unwrap_or_default();
230        handlers.push(HandlerItem {
231            hnd: f(ReactorApi {
232                batch: id << Self::BATCH,
233                inner: self.inner.clone(),
234            }),
235            modified: false,
236        });
237        self.handlers.set(Some(handlers));
238        self.hid.set(id + 1);
239    }
240
241    fn apply_changes(&self, sq: SubmissionQueue<'_, SEntry>) -> bool {
242        unsafe {
243            let changes = &mut *self.inner.changes.get();
244            if changes.is_empty() {
245                false
246            } else {
247                let num = cmp::min(changes.len(), sq.capacity() - sq.len());
248                let (s1, s2) = changes.as_slices();
249                let s1_num = cmp::min(s1.len(), num);
250                let s2_num = cmp::min(s2.len(), num - s1_num);
251                // safety: "changes" contains only initialized entries
252                if s1_num > 0 {
253                    sq.push_multiple(
254                        ((&raw const s1[0..s1_num]) as *const [SEntry])
255                            .as_ref()
256                            .unwrap(),
257                    )
258                    .unwrap();
259                }
260                // the deque wraps around, the rest of the entries is in `s2`
261                if s2_num > 0 {
262                    sq.push_multiple(
263                        ((&raw const s2[0..s2_num]) as *const [SEntry])
264                            .as_ref()
265                            .unwrap(),
266                    )
267                    .unwrap();
268                }
269                changes.drain(0..num);
270
271                !changes.is_empty()
272            }
273        }
274    }
275
276    /// Submit all pending changes to the kernel
277    fn flush(&self, sq: SubmissionQueue<'_, SEntry>, submitter: &Submitter<'_>) {
278        let mut retries = 0;
279        loop {
280            let more_changes = self.apply_changes(sq);
281            sq.sync();
282
283            if let Err(e) = submitter.submit() {
284                match e.raw_os_error() {
285                    Some(libc::ETIME | libc::EBUSY | libc::EAGAIN | libc::EINTR)
286                        if retries < 16 =>
287                    {
288                        retries += 1;
289                        continue;
290                    }
291                    _ => {
292                        log::error!("Cannot submit pending operations: {e:?}");
293                        break;
294                    }
295                }
296            }
297            if !more_changes {
298                break;
299            }
300        }
301    }
302
303    /// Handle ring completions, forward changes to specific handler
304    fn poll_completions(
305        &self,
306        cq: &mut cqueue::CompletionQueue<'_, CEntry>,
307        sq: SubmissionQueue<'_, SEntry>,
308    ) {
309        cq.sync();
310
311        if !cqueue::CompletionQueue::<'_, _>::is_empty(cq) {
312            let mut handlers = self.handlers.take().unwrap();
313            for entry in &mut *cq {
314                let user_data = entry.user_data();
315                match user_data {
316                    Self::CANCEL => {}
317                    Self::NOTIFY => {
318                        let flags = entry.flags();
319                        self.notifier.clear().expect("cannot clear notifier");
320
321                        // re-submit notifier fd
322                        if !more(flags) {
323                            unsafe {
324                                sq.push(
325                                    &PollAdd::new(Fd(self.notifier.as_raw_fd()), libc::POLLIN as _)
326                                        .multi(true)
327                                        .build()
328                                        .user_data(Self::NOTIFY),
329                                )
330                            }
331                            .expect("the squeue sould not be full");
332                        }
333                    }
334                    _ => {
335                        let batch = ((user_data & Self::BATCH_MASK) >> Self::BATCH) as usize;
336                        let user_data = (user_data & Self::DATA_MASK) as usize;
337
338                        let result = entry.result();
339                        // A canceled zero-copy send still posts a notification,
340                        // the operation must stay alive until it arrives.
341                        if result == -libc::ECANCELED && !more(entry.flags()) {
342                            handlers[batch].modified = true;
343                            handlers[batch].hnd.canceled(user_data);
344                        } else {
345                            // zero-copy notification result is a set of flags
346                            let result = if result < 0 && !cqueue::notif(entry.flags()) {
347                                Err(io::Error::from_raw_os_error(-result))
348                            } else {
349                                #[allow(clippy::cast_sign_loss)]
350                                Ok(result as u32 as _)
351                            };
352                            handlers[batch].modified = true;
353                            handlers[batch]
354                                .hnd
355                                .completed(user_data, entry.flags(), result);
356                        }
357                    }
358                }
359            }
360            // publish the consumed entries, the kernel would count them as
361            // pending completions and return from the next wait immediately
362            cq.sync();
363
364            for h in handlers.iter_mut() {
365                h.tick();
366            }
367            self.handlers.set(Some(handlers));
368        }
369    }
370}
371
372impl AsRawFd for Reactor {
373    fn as_raw_fd(&self) -> RawFd {
374        self.fd
375    }
376}
377
378impl crate::Reactor for Reactor {
379    fn tcp_connect(&self, addr: net::SocketAddr, cfg: SharedCfg) -> Receiver<Io> {
380        let addr = SockAddr::from(addr);
381        let result = Socket::new(addr.domain(), Type::STREAM, Some(Protocol::TCP))
382            .and_then(crate::helpers::prep_tcp_socket)
383            .and_then(crate::helpers::prep_socket)
384            .map(move |sock| (addr, sock));
385
386        match result {
387            Err(err) => Receiver::new(Err(err)),
388            Ok((addr, sock)) => super::connect::ConnectOps::get(self).connect(sock, addr, cfg),
389        }
390    }
391
392    fn unix_connect(&self, addr: std::path::PathBuf, cfg: SharedCfg) -> Receiver<Io> {
393        let result = SockAddr::unix(addr).and_then(|addr| {
394            Socket::new(addr.domain(), Type::STREAM, None)
395                .and_then(crate::helpers::prep_socket)
396                .map(move |sock| (addr, sock))
397        });
398
399        match result {
400            Err(err) => Receiver::new(Err(err)),
401            Ok((addr, sock)) => super::connect::ConnectOps::get(self).connect(sock, addr, cfg),
402        }
403    }
404
405    fn from_tcp_stream(&self, stream: net::TcpStream, cfg: SharedCfg) -> io::Result<Io> {
406        stream.set_nodelay(true)?;
407
408        Ok(Io::new(
409            TcpStream(
410                crate::helpers::prep_socket(Socket::from(stream))?,
411                StreamOps::get(self),
412            ),
413            cfg,
414        ))
415    }
416
417    #[cfg(unix)]
418    fn from_unix_stream(&self, stream: OsUnixStream, cfg: SharedCfg) -> io::Result<Io> {
419        Ok(Io::new(
420            UnixStream(
421                crate::helpers::prep_socket(Socket::from(stream))?,
422                StreamOps::get(self),
423            ),
424            cfg,
425        ))
426    }
427}
428
429impl ntex_rt::Driver for Reactor {
430    /// Poll the driver and handle completed operations.
431    fn run(&self, rt: &Runtime) -> io::Result<()> {
432        let ring = &self.inner.ring;
433        let sq = ring.submission();
434        let mut cq = unsafe { ring.completion_shared() };
435        let submitter = ring.submitter();
436        let result = loop {
437            // tasks woken until the runtime is polled do not need to notify
438            rt.awake();
439            self.poll_completions(&mut cq, sq);
440
441            let more_tasks = match rt.poll() {
442                PollResult::Pending => false,
443                PollResult::PollAgain => true,
444                PollResult::Ready => break Ok(()),
445            };
446            let more_changes = self.apply_changes(sq);
447
448            // squeue has to sync after we apply all changes
449            // otherwise ring won't see any change in submit call
450            sq.sync();
451
452            let result = if more_changes || more_tasks {
453                // skip the syscall if there is nothing to submit and
454                // no deferred completions to post
455                if more_changes || !sq.is_empty() || sq.taskrun() {
456                    submitter.submit()
457                } else {
458                    Ok(0)
459                }
460            } else {
461                submitter.submit_and_wait(1)
462            };
463
464            if let Err(e) = result {
465                match e.raw_os_error() {
466                    Some(libc::ETIME | libc::EBUSY | libc::EAGAIN | libc::EINTR) => {
467                        log::info!("Ring submit interrupted, {e:?}");
468                    }
469                    _ => break Err(e),
470                }
471            }
472        };
473
474        // cleanup handlers
475        if result.is_ok() {
476            // Operations queued during the last turn (`Close` of dropped
477            // streams in particular) have not reached the kernel yet.
478            self.flush(sq, &submitter);
479
480            // Handlers release buffers and sockets referenced by in-flight
481            // operations, the kernel must be done with all of them first.
482            if let Err(e) = submitter.register_sync_cancel(None, CancelBuilder::any()) {
483                log::error!("Cannot cancel in-flight operations: {e:?}");
484            }
485
486            for mut h in self.handlers.take().unwrap().into_iter() {
487                h.hnd.cleanup();
488            }
489        }
490
491        result
492    }
493
494    /// Get notification handle
495    fn handle(&self) -> Box<dyn Notify> {
496        Box::new(self.notifier.handle())
497    }
498}
499
500#[derive(Debug)]
501pub(crate) struct Notifier {
502    fd: Arc<OwnedFd>,
503}
504
505impl Notifier {
506    /// Create a new notifier.
507    pub(crate) fn new() -> io::Result<Self> {
508        let fd = syscall!(libc::eventfd(0, libc::EFD_CLOEXEC | libc::EFD_NONBLOCK))?;
509        let fd = unsafe { OwnedFd::from_raw_fd(fd) };
510        Ok(Self { fd: Arc::new(fd) })
511    }
512
513    pub(crate) fn clear(&self) -> io::Result<()> {
514        loop {
515            let mut buffer = [0u64];
516            let res = syscall!(libc::read(
517                self.fd.as_raw_fd(),
518                buffer.as_mut_ptr().cast(),
519                mem::size_of::<u64>()
520            ));
521            #[allow(clippy::cast_possible_wrap)]
522            match res {
523                Ok(len) => {
524                    debug_assert_eq!(len, mem::size_of::<u64>() as isize);
525                    break Ok(());
526                }
527                // Clear the next time
528                Err(e) if e.kind() == io::ErrorKind::WouldBlock => break Ok(()),
529                // Just like read_exact
530                Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
531                Err(e) => break Err(e),
532            }
533        }
534    }
535
536    pub(crate) fn handle(&self) -> NotifyHandle {
537        NotifyHandle::new(self.fd.clone())
538    }
539}
540
541impl AsRawFd for Notifier {
542    fn as_raw_fd(&self) -> RawFd {
543        self.fd.as_raw_fd()
544    }
545}
546
547#[derive(Clone, Debug)]
548/// A notify handle to the driver.
549pub(crate) struct NotifyHandle {
550    fd: Arc<OwnedFd>,
551}
552
553impl NotifyHandle {
554    pub(crate) fn new(fd: Arc<OwnedFd>) -> Self {
555        Self { fd }
556    }
557}
558
559impl Notify for NotifyHandle {
560    /// Notify the driver.
561    fn notify(&self) -> io::Result<()> {
562        let data = 1u64;
563        syscall!(libc::write(
564            self.fd.as_raw_fd(),
565            (&raw const data).cast(),
566            std::mem::size_of::<u64>(),
567        ))?;
568        Ok(())
569    }
570}
571
572impl fmt::Debug for Reactor {
573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574        f.debug_struct("Reactor")
575            .field("fd", &self.fd)
576            .field("hid", &self.hid)
577            .field("nodifier", &self.notifier)
578            .finish()
579    }
580}
581
582impl fmt::Debug for ReactorApi {
583    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
584        f.debug_struct("ReactorApi")
585            .field("batch", &self.batch)
586            .finish()
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use ntex_io_uring::opcode::Nop;
593
594    use super::*;
595
596    #[test]
597    fn apply_wrapped_changes() {
598        let Ok(reactor) = Reactor::new(4) else {
599            return;
600        };
601        let ring = &reactor.inner.ring;
602        let sq = ring.submission();
603        // submit the notifier
604        ring.submitter().submit().unwrap();
605        sq.sync();
606        assert_eq!(sq.len(), 0);
607
608        // queued entries wrap around the end of the deque storage
609        let changes = unsafe { &mut *reactor.inner.changes.get() };
610        let cap = changes.capacity();
611        for _ in 0..cap - 2 {
612            changes.push_back(mem::MaybeUninit::new(Nop::new().build()));
613        }
614        for _ in 0..cap - 2 {
615            changes.pop_front();
616        }
617        for i in 0..4 {
618            changes.push_back(mem::MaybeUninit::new(Nop::new().build().user_data(i)));
619        }
620        assert_eq!(changes.as_slices().0.len(), 2);
621
622        assert!(!reactor.apply_changes(sq));
623        sq.sync();
624        assert!(changes.is_empty());
625        assert_eq!(sq.len(), 4);
626
627        ring.submitter().submit_and_wait(4).unwrap();
628        let mut cq = unsafe { ring.completion_shared() };
629        cq.sync();
630        let ids: Vec<_> = cq.map(|e| e.user_data()).collect();
631        assert_eq!(ids, [0, 1, 2, 3]);
632    }
633}