#![allow(unsafe_code)]
use std::collections::VecDeque;
use io_uring::types::{CancelBuilder, Napi, NapiTracking};
use io_uring::{IoUring, opcode, squeue, types};
use super::Conns;
use crate::backend::{Backend, Config};
use crate::endpoint::sealed::Io;
use crate::endpoint::{DriverStats, Endpoint, Handler, Receive};
use crate::error::{Error, Result};
use crate::sys::{self, MsgSlot};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Spin,
Napi,
Sqpoll,
}
const RECV: u64 = 0;
const SEND: u64 = 1;
const CANCEL: u64 = 2;
const MAX_CQES: usize = 64;
struct Slot {
msg: Box<MsgSlot>,
send_buf: Vec<u8>,
send_off: usize,
recv_armed: bool,
send_active: bool,
generation: u32,
napi_known: bool,
napi_id: u32,
}
pub(super) struct Uring {
ring: IoUring,
mode: Mode,
slots: Vec<Slot>,
pending: usize,
wait: types::Timespec,
napi_refs: Vec<(u32, usize)>,
napi_static: bool,
backlog: VecDeque<squeue::Entry>,
}
impl Uring {
pub(super) fn new(cfg: &Config) -> Result<Self> {
let mode = match cfg.backend {
Backend::UringNapi => Mode::Napi,
Backend::UringSqpoll => Mode::Sqpoll,
_ => Mode::Spin,
};
let mut b = IoUring::builder();
if mode == Mode::Sqpoll {
b.setup_sqpoll(cfg.sqpoll_idle_ms);
if let Some(cpu) = cfg.sqpoll_cpu {
b.setup_sqpoll_cpu(cpu);
}
b.setup_single_issuer();
} else {
b.setup_single_issuer()
.setup_defer_taskrun()
.setup_taskrun_flag();
}
let ring = b.build(cfg.ring_entries).map_err(|e| {
if e.raw_os_error() == Some(libc::ENOSYS) || e.raw_os_error() == Some(libc::EPERM) {
Error::Unsupported("io_uring is disabled or unavailable on this kernel")
} else {
Error::Io(e)
}
})?;
let mut napi_static = false;
if mode != Mode::Spin {
let budget = if mode == Mode::Napi && cfg.wait_us == 0 {
0
} else {
cfg.busy_poll_us.max(1)
};
let register = |tracking| {
let mut napi = Napi::new()
.set_busy_poll_timeout(budget)
.set_prefer_busy_poll(cfg.prefer_busy_poll)
.set_tracking(tracking);
ring.submitter().register_napi(&mut napi)
};
let napi_error = |e: std::io::Error| match e.raw_os_error() {
Some(libc::EINVAL) => {
Error::Unsupported("IORING_REGISTER_NAPI needs Linux 6.9 or newer")
}
Some(libc::EOPNOTSUPP) => Error::Unsupported(
"the kernel was built without busy polling (CONFIG_NET_RX_BUSY_POLL)",
),
_ => Error::Io(e),
};
match register(NapiTracking::Static) {
Ok(()) => napi_static = true,
Err(e) if e.raw_os_error() == Some(libc::EINVAL) => {
register(NapiTracking::Dynamic).map_err(napi_error)?;
}
Err(e) => return Err(napi_error(e)),
}
}
Ok(Self {
ring,
mode,
slots: Vec::new(),
pending: 0,
wait: types::Timespec::new()
.sec(u64::from(cfg.wait_us / 1_000_000))
.nsec((cfg.wait_us % 1_000_000) * 1000),
napi_refs: Vec::new(),
napi_static,
backlog: VecDeque::with_capacity(cfg.ring_entries as usize),
})
}
pub(super) fn add<E: Endpoint>(&mut self, ep: &mut E, i: usize) -> Result<()> {
while self.slots.len() <= i {
self.slots.push(Slot {
msg: MsgSlot::new(),
send_buf: Vec::with_capacity(1 << 16),
send_off: 0,
recv_armed: false,
send_active: false,
generation: 0,
napi_known: true,
napi_id: 0,
});
}
self.release_napi(i);
let s = &mut self.slots[i];
s.generation = s.generation.wrapping_add(1) & GEN_MASK;
s.napi_known = !self.napi_static;
if self.napi_static {
let known = self.register_napi_of(ep.fd(), i)?;
self.slots[i].napi_known = known;
}
if !ep.has_buffered_input() {
self.arm_recv(ep, i);
}
Ok(())
}
fn register_napi_of(&mut self, fd: std::os::fd::RawFd, i: usize) -> Result<bool> {
let id = sys::incoming_napi_id(fd).unwrap_or(0);
if id == 0 {
return Ok(false);
}
if let Some(r) = self.napi_refs.iter_mut().find(|r| r.0 == id) {
r.1 += 1;
} else {
self.ring.submitter().register_napi_add_id(id)?;
self.napi_refs.push((id, 1));
}
self.slots[i].napi_id = id;
Ok(true)
}
fn release_napi(&mut self, i: usize) {
let id = std::mem::take(&mut self.slots[i].napi_id);
if id == 0 {
return;
}
if let Some(k) = self.napi_refs.iter().position(|r| r.0 == id) {
self.napi_refs[k].1 -= 1;
if self.napi_refs[k].1 == 0 {
self.napi_refs.swap_remove(k);
let _ = self.ring.submitter().register_napi_del_id(id);
}
}
}
pub(super) fn arm_initial<E: Io>(&mut self, ep: &mut E, i: usize) {
if self.slots.get(i).is_some_and(|s| !s.recv_armed) {
self.arm_recv(ep, i);
}
}
pub(super) fn close<E: Endpoint>(&mut self, conns: &mut Conns<E>, i: usize) {
if conns.at(i).is_none() {
return;
}
conns.begin_close(i);
self.purge_backlog(i);
if let Some(s) = self.slots.get(i) {
let g = s.generation;
for (armed, op) in [(s.recv_armed, RECV), (s.send_active, SEND)] {
if armed {
let e = opcode::AsyncCancel2::new(CancelBuilder::user_data(tag(i, g, op)))
.build()
.user_data(tag(i, g, CANCEL));
self.push(e);
}
}
}
self.maybe_finish(conns, i);
}
fn purge_backlog(&mut self, slot: usize) {
if self.backlog.is_empty() {
return;
}
let Some(state) = self.slots.get_mut(slot) else {
return;
};
let generation = state.generation;
self.backlog.retain(|entry| {
let (other, other_generation, op) = untag(entry.get_user_data());
if other != slot || other_generation != generation {
return true;
}
match op {
RECV => state.recv_armed = false,
SEND => state.send_active = false,
_ => {}
}
false
});
}
fn maybe_finish<E>(&mut self, conns: &mut Conns<E>, i: usize) {
if conns.is_closing(i)
&& self
.slots
.get(i)
.is_none_or(|s| !s.recv_armed && !s.send_active)
{
self.purge_backlog(i);
if let Some(s) = self.slots.get_mut(i) {
s.send_buf.clear();
s.send_off = 0;
}
if i < self.slots.len() {
self.release_napi(i);
}
conns.finish_close(i);
}
}
fn fail<E: Endpoint, H: Handler>(
&mut self,
conns: &mut Conns<E>,
i: usize,
err: Error,
handler: &mut H,
stats: &mut DriverStats,
) {
let id = conns.id(i);
if !conns.is_open(i) {
return;
}
let err = match conns.at(i) {
Some(c) => c.close_reason(err),
None => err,
};
self.close(conns, i);
stats.closed += 1;
handler.on_closed(id, err);
}
#[inline]
pub(super) fn poll<E, H>(
&mut self,
conns: &mut Conns<E>,
handler: &mut H,
stats: &mut DriverStats,
read_seq: &mut u64,
) -> Result<()>
where
E: Receive<H>,
H: Handler,
{
if !self.backlog.is_empty() {
self.drain_backlog();
}
if self.ring.completion().is_empty() {
self.push_sends(conns, stats);
return self.enter(true, stats);
}
let mut cqes = [(0u64, 0i32); MAX_CQES];
let mut n = 0;
for cqe in self.ring.completion() {
cqes[n] = (cqe.user_data(), cqe.result());
n += 1;
if n == MAX_CQES {
break;
}
}
for &(ud, res) in &cqes[..n] {
let (i, g, op) = untag(ud);
if self.slots.get(i).is_none_or(|s| s.generation != g) {
continue;
}
match op {
RECV => self.on_recv(conns, i, res, handler, stats, read_seq),
SEND => self.on_send(conns, i, res, handler, stats),
_ => {}
}
}
self.push_sends(conns, stats);
self.enter(n == 0, stats)
}
#[inline]
fn on_recv<E, H>(
&mut self,
conns: &mut Conns<E>,
i: usize,
res: i32,
handler: &mut H,
stats: &mut DriverStats,
read_seq: &mut u64,
) where
E: Receive<H>,
H: Handler,
{
let Some(slot) = self.slots.get_mut(i) else {
return;
};
slot.recv_armed = false;
if !conns.is_open(i) {
self.maybe_finish(conns, i);
return;
}
let id = conns.id(i);
let Some(conn) = conns.at(i) else {
return;
};
let napi_known = slot.napi_known;
let outcome = match usize::try_from(res) {
Ok(0) => Err(Error::Closed),
Ok(len) => {
*read_seq += 1;
stats.completions += 1;
let meta = slot.msg.meta(*read_seq);
conn.on_received(id, len, &meta, handler)
.map(|m| stats.messages += m as u64)
}
Err(_) if res == -libc::EAGAIN || res == -libc::EINTR => Ok(()),
Err(_) => Err(Error::Io(std::io::Error::from_raw_os_error(-res))),
};
let outcome = outcome.and_then(|()| {
if !napi_known && res > 0 {
let known = self.register_napi_of(conn.fd(), i)?;
self.slots[i].napi_known = known;
}
Ok(())
});
let outcome = outcome.map(|()| self.arm_recv(conn, i));
if let Err(e) = outcome {
self.fail(conns, i, e, handler, stats);
}
}
fn on_send<E: Endpoint, H: Handler>(
&mut self,
conns: &mut Conns<E>,
i: usize,
res: i32,
handler: &mut H,
stats: &mut DriverStats,
) {
let Some(slot) = self.slots.get_mut(i) else {
return;
};
slot.send_active = false;
if !conns.is_open(i) {
self.maybe_finish(conns, i);
return;
}
let Ok(sent) = usize::try_from(res) else {
let e = Error::Io(std::io::Error::from_raw_os_error(-res));
self.fail(conns, i, e, handler, stats);
return;
};
slot.send_off += sent;
if slot.send_off < slot.send_buf.len() {
self.push_send(conns, i, stats);
} else {
slot.send_buf.clear();
slot.send_off = 0;
}
}
#[inline]
fn push_sends<E: Endpoint>(&mut self, conns: &mut Conns<E>, stats: &mut DriverStats) {
for i in 0..conns.len_slots().min(self.slots.len()) {
if self.slots[i].send_active || !conns.is_open(i) {
continue;
}
let Some(c) = conns.at(i) else {
continue;
};
let q = c.tx_queue();
if q.is_empty() {
continue;
}
let n = q.len();
let s = &mut self.slots[i];
std::mem::swap(&mut s.send_buf, q);
s.send_off = 0;
c.on_handed(n);
self.push_send(conns, i, stats);
}
}
pub(super) fn flush<E: Endpoint>(
&mut self,
conns: &mut Conns<E>,
stats: &mut DriverStats,
) -> Result<()> {
if !self.backlog.is_empty() {
self.drain_backlog();
}
self.push_sends(conns, stats);
self.enter(false, stats)
}
#[inline]
fn enter(&mut self, idle: bool, stats: &mut DriverStats) -> Result<()> {
match self.mode {
Mode::Sqpoll => {
let overflow = self.ring.submission().cq_overflow();
if self.pending > 0 || overflow {
if overflow || self.ring.submission().need_wakeup() {
stats.enter_calls += 1;
}
self.ring.submit()?;
self.pending = 0;
}
}
Mode::Napi if idle => {
stats.enter_calls += 1;
let args = types::SubmitArgs::new().timespec(&self.wait);
match self.ring.submitter().submit_with_args(1, &args) {
Ok(_) => {}
Err(e)
if matches!(
e.raw_os_error(),
Some(libc::ETIME | libc::EINTR | libc::EBUSY)
) => {}
Err(e) => return Err(e.into()),
}
self.pending = 0;
}
_ => {
let work = {
let sq = self.ring.submission();
sq.taskrun() || sq.cq_overflow()
};
if self.pending > 0 || work {
stats.enter_calls += 1;
self.ring.submitter().submit_and_wait(0)?;
self.pending = 0;
}
}
}
Ok(())
}
fn arm_recv<E: Endpoint>(&mut self, conn: &mut E, i: usize) {
let s = &mut self.slots[i];
s.msg.arm(conn.rx_space());
let e = opcode::RecvMsg::new(types::Fd(conn.fd()), &raw mut s.msg.hdr)
.build()
.user_data(tag(i, s.generation, RECV));
s.recv_armed = true;
self.push(e);
}
fn push_send<E: Endpoint>(&mut self, conns: &mut Conns<E>, i: usize, stats: &mut DriverStats) {
let Some(fd) = conns.at(i).map(|c| c.fd()) else {
return;
};
let s = &mut self.slots[i];
let rest = &s.send_buf[s.send_off..];
let len = u32::try_from(rest.len()).unwrap_or(u32::MAX);
let e = opcode::Send::new(types::Fd(fd), rest.as_ptr(), len)
.flags(libc::MSG_NOSIGNAL)
.build()
.user_data(tag(i, s.generation, SEND));
s.send_active = true;
stats.sends += 1;
self.push(e);
}
fn push(&mut self, e: squeue::Entry) {
if self.backlog.is_empty() {
if unsafe { self.ring.submission().push(&e) }.is_ok() {
self.pending += 1;
return;
}
}
self.backlog.push_back(e);
}
#[cold]
fn drain_backlog(&mut self) {
if self.mode != Mode::Sqpoll
&& self.ring.submission().is_full()
&& self.ring.submit().is_ok()
{
self.pending = 0;
}
while let Some(e) = self.backlog.front() {
if unsafe { self.ring.submission().push(e) }.is_err() {
break;
}
self.backlog.pop_front();
self.pending += 1;
}
}
pub(super) fn shutdown(&mut self) -> bool {
let inflight = |s: &[Slot]| s.iter().any(|s| s.recv_armed || s.send_active);
if !inflight(&self.slots) {
return true;
}
for e in std::mem::take(&mut self.backlog) {
let (i, g, op) = untag(e.get_user_data());
if let Some(s) = self.slots.get_mut(i).filter(|s| s.generation == g) {
match op {
RECV => s.recv_armed = false,
SEND => s.send_active = false,
_ => {}
}
}
}
if !inflight(&self.slots) {
return true;
}
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(1);
while !self.ring.submission().is_empty() && std::time::Instant::now() < deadline {
let _ = self.ring.submit();
std::hint::spin_loop();
}
while std::time::Instant::now() < deadline {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
let _ = self
.ring
.submitter()
.register_sync_cancel(Some(remaining.into()), CancelBuilder::any());
let _ = self.ring.submitter().submit_and_wait(0);
for cqe in self.ring.completion() {
let (i, g, op) = untag(cqe.user_data());
if let Some(s) = self.slots.get_mut(i).filter(|s| s.generation == g) {
match op {
RECV => s.recv_armed = false,
SEND => s.send_active = false,
_ => {}
}
}
}
if !inflight(&self.slots) {
return true;
}
std::hint::spin_loop();
}
std::mem::forget(std::mem::take(&mut self.slots));
false
}
}
const GEN_MASK: u32 = 0x00FF_FFFF;
const fn tag(i: usize, generation: u32, op: u64) -> u64 {
((generation & GEN_MASK) as u64) << 40 | ((i as u64) & 0xFFFF_FFFF) << 8 | op
}
const fn untag(ud: u64) -> (usize, u32, u64) {
(
((ud >> 8) & 0xFFFF_FFFF) as usize,
(ud >> 40) as u32,
ud & 0xFF,
)
}