pub(crate) mod buf_ring;
pub(crate) mod queue;
pub(crate) mod sqe;
pub(crate) mod sys;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_net;
#[cfg(test)]
mod tests_setup;
#[cfg(test)]
mod tests_wake;
use std::{
ffi::c_void,
fmt, io,
marker::PhantomData,
mem::size_of,
os::fd::{AsRawFd, OwnedFd, RawFd},
ptr,
time::Duration,
};
pub(crate) use queue::{Completions, SqFull};
pub(crate) use sqe::Sqe;
pub(crate) use sys::*;
use crate::config::IoUringConfiguration;
use queue::{Cq, Sq};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RingConfig {
pub entries: u32,
pub cq_entries: Option<u32>,
pub sqpoll_idle_ms: Option<u32>,
pub iopoll: bool,
pub start_disabled: bool,
pub multi_issuer: bool,
pub iowq_max_workers: Option<[u32; 2]>,
pub recv_buffers: u16,
pub recv_buffer_size: u32,
}
impl RingConfig {
pub(crate) fn new(entries: u32) -> Self {
Self {
entries,
cq_entries: None,
sqpoll_idle_ms: None,
iopoll: false,
start_disabled: false,
multi_issuer: false,
iowq_max_workers: None,
recv_buffers: 0,
recv_buffer_size: 0,
}
}
pub(crate) fn handed_over(self) -> Self {
Self {
multi_issuer: true,
..self
}
}
}
impl From<&IoUringConfiguration> for RingConfig {
fn from(c: &IoUringConfiguration) -> Self {
Self {
entries: c.queue_len,
cq_entries: None,
sqpoll_idle_ms: c.sqpoll_wake_interval,
iopoll: c.iopoll_enabled,
start_disabled: false,
multi_issuer: false,
iowq_max_workers: (c.per_numa_bounded_worker_count.is_some()
|| c.per_numa_unbounded_worker_count.is_some())
.then(|| {
[
c.per_numa_bounded_worker_count.unwrap_or(0),
c.per_numa_unbounded_worker_count.unwrap_or(0),
]
}),
recv_buffers: c.recv_buffers,
recv_buffer_size: c.recv_buffer_size,
}
}
}
pub(crate) fn setup_flags(cfg: &RingConfig, no_sqarray: bool) -> u32 {
let mut flags = IORING_SETUP_SUBMIT_ALL | IORING_SETUP_CLAMP | IORING_SETUP_CQSIZE;
flags |= if cfg.sqpoll_idle_ms.is_some() {
IORING_SETUP_SQPOLL
} else if cfg.multi_issuer {
0
} else {
IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN
};
if cfg.iopoll {
flags |= IORING_SETUP_IOPOLL;
}
if cfg.start_disabled {
flags |= IORING_SETUP_R_DISABLED;
}
if no_sqarray {
flags |= IORING_SETUP_NO_SQARRAY;
}
flags
}
pub(crate) fn setup_params(cfg: &RingConfig, no_sqarray: bool) -> IoUringParams {
IoUringParams {
flags: setup_flags(cfg, no_sqarray),
cq_entries: cfg
.cq_entries
.unwrap_or(0)
.max(cfg.entries.saturating_mul(2)),
sq_thread_idle: cfg.sqpoll_idle_ms.unwrap_or(0),
..IoUringParams::default()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Refusal {
NotInKernel,
FloorFlags,
Feature(&'static str),
Registration(&'static str),
}
impl fmt::Display for Refusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotInKernel => f.write_str("this kernel has no io_uring support"),
Self::FloorFlags => {
f.write_str("the kernel lacks the io_uring features this runtime needs")
}
Self::Feature(name) => write!(f, "the kernel's io_uring lacks {name}"),
Self::Registration(name) => write!(f, "the kernel's io_uring refuses {name}"),
}
}
}
impl std::error::Error for Refusal {}
impl From<Refusal> for io::Error {
fn from(r: Refusal) -> Self {
io::Error::new(io::ErrorKind::Unsupported, r)
}
}
pub(crate) fn refused(name: &'static str) -> impl FnOnce(io::Error) -> io::Error {
move |e| match e.raw_os_error() {
Some(libc::EINVAL | libc::EOPNOTSUPP) => Refusal::Registration(name).into(),
_ => e,
}
}
fn negotiate<T>(
cfg: &RingConfig,
mut setup: impl FnMut(u32, &mut IoUringParams) -> io::Result<T>,
) -> io::Result<(T, IoUringParams)> {
for no_sqarray in [true, false] {
let mut params = setup_params(cfg, no_sqarray);
match setup(cfg.entries, &mut params) {
Ok(v) => return Ok((v, params)),
Err(e) if e.raw_os_error() == Some(libc::EINVAL) => {}
Err(e) if e.raw_os_error() == Some(libc::ENOSYS) => {
return Err(Refusal::NotInKernel.into());
}
Err(e) => return Err(e),
}
}
Err(Refusal::FloorFlags.into())
}
const REQUIRED_FEATURES: [(u32, &str); 4] = [
(IORING_FEAT_SINGLE_MMAP, "a single mapping for both queues"),
(IORING_FEAT_NODROP, "lossless completion overflow"),
(IORING_FEAT_EXT_ARG, "timed waits"),
(IORING_FEAT_FAST_POLL, "fast poll"),
];
fn enter_outcome(r: io::Result<u32>) -> io::Result<u32> {
match r {
Err(e) if e.raw_os_error() == Some(libc::ETIME) => Ok(0),
Err(e) if e.raw_os_error() == Some(libc::EBADR) => Err(io::Error::other(
"completions were lost because the completion queue overflowed",
)),
r => r,
}
}
#[derive(Debug)]
pub(crate) struct Ring {
fd: OwnedFd,
sq: Sq,
cq: Cq,
flags: u32,
features: u32,
sq_entries: u32,
cq_entries: u32,
reg_index: Option<u32>,
deferred_iowq: Option<[u32; 2]>,
_ring_map: sys::Mapping,
_sqes_map: sys::Mapping,
_not_send: PhantomData<*const ()>,
}
impl Ring {
pub(crate) fn new(cfg: &RingConfig) -> io::Result<Ring> {
Self::with_setup(cfg, io_uring_setup)
}
fn with_setup(
cfg: &RingConfig,
setup: impl FnMut(u32, &mut IoUringParams) -> io::Result<OwnedFd>,
) -> io::Result<Ring> {
if cfg.entries == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"the submission queue needs at least one entry",
));
}
let (fd, params) = negotiate(cfg, setup)?;
for (bit, name) in REQUIRED_FEATURES {
if params.features & bit == 0 {
return Err(Refusal::Feature(name).into());
}
}
if !params.sq_entries.is_power_of_two() || !params.cq_entries.is_power_of_two() {
return Err(io::Error::other(
"the kernel reported an unusable queue size",
));
}
let has_array = params.flags & IORING_SETUP_NO_SQARRAY == 0;
let sq_len = if has_array {
params.sq_off.array as usize + params.sq_entries as usize * size_of::<u32>()
} else {
0
};
let cq_len =
params.cq_off.cqes as usize + params.cq_entries as usize * size_of::<IoUringCqe>();
let sqes_len = params.sq_entries as usize * size_of::<IoUringSqe>();
let ring_map = sys::Mapping::ring(fd.as_raw_fd(), sq_len.max(cq_len), IORING_OFF_SQ_RING)?;
let sqes_map = sys::Mapping::ring(fd.as_raw_fd(), sqes_len, IORING_OFF_SQES)?;
let (sq, cq) = unsafe {
(
Sq::new(
ring_map.as_ptr(),
¶ms.sq_off,
sqes_map.as_ptr().cast(),
params.sq_entries,
has_array,
),
Cq::new(ring_map.as_ptr(), ¶ms.cq_off, params.cq_entries),
)
};
let mut ring = Ring {
fd,
sq,
cq,
flags: params.flags,
features: params.features,
sq_entries: params.sq_entries,
cq_entries: params.cq_entries,
reg_index: None,
deferred_iowq: cfg.iowq_max_workers,
_ring_map: ring_map,
_sqes_map: sqes_map,
_not_send: PhantomData,
};
if !cfg.start_disabled {
ring.apply_deferred_iowq()?;
}
Ok(ring)
}
pub(crate) fn as_raw_fd(&self) -> RawFd {
self.fd.as_raw_fd()
}
pub(crate) fn flags(&self) -> u32 {
self.flags
}
pub(crate) fn features(&self) -> u32 {
self.features
}
pub(crate) fn sq_entries(&self) -> u32 {
self.sq_entries
}
pub(crate) fn cq_entries(&self) -> u32 {
self.cq_entries
}
pub(crate) fn sq_space(&self) -> u32 {
self.sq.space()
}
pub(crate) fn push(&mut self, sqe: &Sqe) -> Result<(), SqFull> {
self.sq.push(sqe)
}
pub(crate) fn completions(&mut self) -> Completions<'_> {
self.cq.completions()
}
pub(crate) fn cq_overflow_pending(&self) -> bool {
self.sq.cq_overflow_pending()
}
pub(crate) fn sq_thread_asleep(&self) -> bool {
self.sq.sqpoll_asleep()
}
pub(crate) fn submit(&mut self) -> io::Result<u32> {
let getevents = self.flags & (IORING_SETUP_DEFER_TASKRUN | IORING_SETUP_IOPOLL) != 0
|| self.cq_overflow_pending();
self.enter(0, None, getevents)
}
pub(crate) fn submit_and_wait(
&mut self,
min_complete: u32,
timeout: Option<Duration>,
) -> io::Result<u32> {
self.enter(min_complete, timeout, true)
}
fn enter(
&mut self,
min_complete: u32,
timeout: Option<Duration>,
getevents: bool,
) -> io::Result<u32> {
self.sq.publish();
let queued = self.sq.ready();
let mut flags = if getevents { IORING_ENTER_GETEVENTS } else { 0 };
if self.flags & IORING_SETUP_SQPOLL != 0 {
if self.sq.sqpoll_needs_wakeup() {
flags |= IORING_ENTER_SQ_WAKEUP;
}
if flags == 0 {
return Ok(queued);
}
} else if flags == 0 && queued == 0 {
return Ok(0);
}
let (fd, reg_flag) = match self.reg_index {
Some(i) => (i as RawFd, IORING_ENTER_REGISTERED_RING),
None => (self.fd.as_raw_fd(), 0),
};
let ts = timeout
.filter(|_| min_complete > 0)
.map(KernelTimespec::from_duration);
let arg = ts.as_ref().map(|ts| IoUringGeteventsArg {
ts: ptr::from_ref(ts).expose_provenance() as u64,
..IoUringGeteventsArg::default()
});
let (argp, argsz) = match &arg {
Some(a) => {
flags |= IORING_ENTER_EXT_ARG;
(
ptr::from_ref(a).cast::<c_void>(),
size_of::<IoUringGeteventsArg>(),
)
}
None => (ptr::null(), 0),
};
let r = unsafe { io_uring_enter(fd, queued, min_complete, flags | reg_flag, argp, argsz) };
enter_outcome(r)
}
unsafe fn register(&self, opcode: u32, arg: *const c_void, nr_args: u32) -> io::Result<u32> {
unsafe { io_uring_register(self.fd.as_raw_fd(), opcode, arg, nr_args) }
}
pub(crate) fn register_probe(&self) -> io::Result<OpcodeSet> {
let mut probe = ProbeBuf::default();
unsafe {
self.register(
IORING_REGISTER_PROBE,
ptr::from_mut(&mut probe).cast(),
PROBE_OPS as u32,
)
}?;
let mut set = OpcodeSet::default();
for op in probe.ops.iter().take(usize::from(probe.head.ops_len)) {
if op.flags & IO_URING_OP_SUPPORTED != 0 {
set.insert(op.op);
}
}
Ok(set)
}
pub(crate) fn register_files_sparse(&self, slots: u32) -> io::Result<()> {
let table = IoUringRsrcRegister {
nr: slots,
flags: IORING_RSRC_REGISTER_SPARSE,
..IoUringRsrcRegister::default()
};
unsafe {
self.register(
IORING_REGISTER_FILES2,
ptr::from_ref(&table).cast(),
size_of::<IoUringRsrcRegister>() as u32,
)
}
.map_err(refused("sparse registered file tables"))?;
Ok(())
}
pub(crate) fn update_file(&self, slot: u32, fd: RawFd) -> io::Result<()> {
let fds = [fd];
let update = IoUringRsrcUpdate2 {
offset: slot,
data: fds.as_ptr().expose_provenance() as u64,
nr: 1,
..IoUringRsrcUpdate2::default()
};
let n = unsafe {
self.register(
IORING_REGISTER_FILES_UPDATE2,
ptr::from_ref(&update).cast(),
size_of::<IoUringRsrcUpdate2>() as u32,
)
}?;
if n != 1 {
return Err(io::Error::other("the kernel did not update the fixed file"));
}
Ok(())
}
pub(crate) fn register_iowq_max_workers(&self, limits: [u32; 2]) -> io::Result<[u32; 2]> {
let mut limits = limits;
unsafe {
self.register(
IORING_REGISTER_IOWQ_MAX_WORKERS,
limits.as_mut_ptr().cast(),
2,
)
}?;
Ok(limits)
}
fn apply_deferred_iowq(&mut self) -> io::Result<()> {
if let Some(limits) = self.deferred_iowq.take() {
self.register_iowq_max_workers(limits)?;
}
Ok(())
}
pub(crate) fn enable(&mut self) -> io::Result<()> {
unsafe { self.register(IORING_REGISTER_ENABLE_RINGS, ptr::null(), 0) }?;
self.apply_deferred_iowq()
}
pub(crate) fn register_ring_fd(&mut self) -> io::Result<()> {
if self.reg_index.is_some() {
return Ok(());
}
let mut update = IoUringRsrcUpdate {
offset: u32::MAX,
resv: 0,
data: self.fd.as_raw_fd() as u64,
};
let n = unsafe {
self.register(
IORING_REGISTER_RING_FDS,
ptr::from_mut(&mut update).cast(),
1,
)
}?;
if n != 1 {
return Err(io::Error::other("the kernel did not register the ring fd"));
}
self.reg_index = Some(update.offset);
Ok(())
}
}
impl Drop for Ring {
fn drop(&mut self) {
if let Some(index) = self.reg_index.take() {
let update = IoUringRsrcUpdate {
offset: index,
..IoUringRsrcUpdate::default()
};
let _ = unsafe {
self.register(IORING_UNREGISTER_RING_FDS, ptr::from_ref(&update).cast(), 1)
};
}
}
}