use std::collections::VecDeque;
use std::io;
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
use std::sync::Arc;
use std::time::Duration;
use super::Notifier;
use crate::syscore::linux::iouring::ops::NOT_AN_OP;
use crate::syscore::linux::iouring::ring::sys::IORING_SETUP_SINGLE_ISSUER;
use crate::syscore::linux::iouring::ring::{IoUringCqe, Ring, RingConfig, SqFull, Sqe};
pub(crate) struct RingPoller {
ring: Ring,
wake: Arc<OwnedFd>,
wake_buf: Box<u64>,
wake_armed: bool,
waiting: VecDeque<Sqe>,
enabled: bool,
overflowed: bool,
overflows: u64,
}
unsafe impl Send for RingPoller {}
impl RingPoller {
pub(crate) fn new(cfg: &RingConfig, files: u32) -> io::Result<Self> {
let mut cfg = *cfg;
cfg.start_disabled = true;
let ring = Ring::new(&cfg)?;
if files > 0 {
ring.register_files_sparse(files)?;
}
let wake = unsafe {
let fd = libc::eventfd(0, libc::EFD_CLOEXEC);
if fd < 0 {
return Err(io::Error::last_os_error());
}
OwnedFd::from_raw_fd(fd)
};
Ok(Self {
ring,
wake: Arc::new(wake),
wake_buf: Box::new(0),
wake_armed: false,
waiting: VecDeque::new(),
enabled: false,
overflowed: false,
overflows: 0,
})
}
pub(crate) fn enable(&mut self) -> io::Result<()> {
if self.enabled {
return Ok(());
}
self.ring.enable()?;
self.enabled = true;
if self.ring.flags() & IORING_SETUP_SINGLE_ISSUER != 0 {
let _ = self.ring.register_ring_fd();
}
Ok(())
}
pub(crate) fn notifier(&self) -> Notifier {
Notifier::of(Arc::clone(&self.wake))
}
pub(crate) fn push(&mut self, sqe: Sqe) {
if !self.waiting.is_empty() || self.ring.push(&sqe).is_err() {
self.waiting.push_back(sqe);
}
}
fn flush_waiting(&mut self) {
while let Some(sqe) = self.waiting.front() {
if let Err(SqFull) = self.ring.push(sqe) {
return;
}
self.waiting.pop_front();
}
}
fn arm_wake(&mut self) {
if self.wake_armed {
return;
}
let sqe = Sqe::read(
self.wake.as_raw_fd(),
core::ptr::from_mut(&mut *self.wake_buf).cast(),
8,
0,
)
.user_data(NOT_AN_OP);
if self.ring.push(&sqe).is_err() {
self.waiting.push_front(sqe);
}
self.wake_armed = true;
}
pub(crate) fn wait(
&mut self,
timeout: Option<Duration>,
out: &mut Vec<IoUringCqe>,
) -> io::Result<bool> {
self.enable()?;
self.arm_wake();
self.flush_waiting();
self.look_for_overflow();
let min = u32::from(timeout != Some(Duration::ZERO));
match self.ring.submit_and_wait(min, timeout) {
Ok(_) => {}
Err(err)
if matches!(
err.raw_os_error(),
Some(libc::EINTR) | Some(libc::EBUSY) | Some(libc::EAGAIN)
) => {}
Err(err) => return Err(err),
}
let mut woken = false;
for cqe in self.ring.completions() {
if cqe.user_data == NOT_AN_OP {
woken = true;
self.wake_armed = false;
} else {
out.push(cqe);
}
}
self.look_for_overflow();
self.flush_waiting();
Ok(woken)
}
fn look_for_overflow(&mut self) {
let overflowed = self.ring.cq_overflow_pending();
self.overflows += u64::from(overflowed && !self.overflowed);
self.overflowed = overflowed;
}
pub(crate) fn update_file(&self, slot: u32, fd: std::os::fd::RawFd) -> io::Result<()> {
self.ring.update_file(slot, fd)
}
pub(crate) fn overflows(&self) -> u64 {
self.overflows
}
pub(crate) fn cq_entries(&self) -> u32 {
self.ring.cq_entries()
}
pub(crate) fn ring(&self) -> &Ring {
&self.ring
}
}
#[cfg(test)]
mod tests {
use std::time::{Duration, Instant};
use super::RingPoller;
use crate::syscore::linux::iouring::ring::{RingConfig, Sqe};
fn poller() -> Option<RingPoller> {
let mut p = match RingPoller::new(&RingConfig::new(8), 16) {
Ok(p) => p,
Err(err) => {
eprintln!("skipping: io_uring unavailable: {err}");
return None;
}
};
p.enable().unwrap();
Some(p)
}
#[test]
fn a_wake_from_another_thread_ends_a_wait() {
let Some(mut p) = poller() else { return };
let n = p.notifier();
let waker = std::thread::spawn(move || n.notify());
let mut out = Vec::new();
let start = Instant::now();
loop {
if p.wait(Some(Duration::from_secs(10)), &mut out).unwrap() {
break;
}
assert!(start.elapsed() < Duration::from_secs(10), "no wake came");
}
waker.join().unwrap();
assert!(out.is_empty());
p.notifier().notify();
assert!(p.wait(Some(Duration::from_secs(10)), &mut out).unwrap());
}
#[test]
fn a_timed_wait_ends_at_its_timeout() {
let Some(mut p) = poller() else { return };
let mut out = Vec::new();
let start = Instant::now();
assert!(!p.wait(Some(Duration::from_millis(20)), &mut out).unwrap());
assert!(start.elapsed() >= Duration::from_millis(20));
}
fn flood(p: &mut RingPoller, first: u64, n: u64) {
let mut next = first;
while next < first + n {
while next < first + n && p.ring.push(&Sqe::nop().user_data(next)).is_ok() {
next += 1;
}
match p.ring.submit() {
Ok(_) => {}
Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {}
Err(e) => panic!("submit failed: {e}"),
}
}
assert!(
p.ring.cq_overflow_pending(),
"the flood overflowed the queue"
);
}
#[test]
fn an_overflow_is_counted_once_and_loses_nothing() {
let Some(mut p) = poller() else { return };
const N: u64 = 64;
let mut out = Vec::new();
for round in 0..2 {
flood(&mut p, round * N, N);
assert_eq!(p.overflows(), round);
out.clear();
let deadline = Instant::now() + Duration::from_secs(10);
while out.len() < N as usize {
p.wait(Some(Duration::from_millis(100)), &mut out).unwrap();
assert!(Instant::now() < deadline, "{} of {N} completed", out.len());
}
let mut got: Vec<u64> = out.iter().map(|c| c.user_data).collect();
got.sort_unstable();
assert_eq!(got, (round * N..round * N + N).collect::<Vec<_>>());
assert_eq!(p.overflows(), round + 1);
}
}
#[test]
fn entries_past_a_full_queue_wait_and_complete_in_order() {
let Some(mut p) = poller() else { return };
for i in 0..40u64 {
p.push(Sqe::nop().user_data(i));
}
let mut out = Vec::new();
let deadline = Instant::now() + Duration::from_secs(10);
while out.len() < 40 {
p.wait(Some(Duration::from_millis(100)), &mut out).unwrap();
assert!(Instant::now() < deadline, "{} of 40 completed", out.len());
}
let got: Vec<u64> = out.iter().map(|c| c.user_data).collect();
assert_eq!(got, (0..40).collect::<Vec<_>>());
}
}