use crate::service::dns::BlockingDnsResolver;
use crate::service::endpoint::{Context, Endpoint, EndpointWithContext};
use crate::service::node::IONode;
use crate::service::select::{Selectable, Selector, SelectorToken};
use crate::service::time::SystemTimeClockSource;
use crate::service::{IOService, IntoIOService, IntoIOServiceWithContext};
use io_uring::{IoUring, cqueue, opcode, squeue, types};
use std::collections::HashMap;
use std::io;
use std::marker::PhantomData;
use std::os::fd::{AsRawFd, RawFd};
use std::time::Duration;
#[derive(Debug, Clone, Copy)]
pub struct IoUringConfig {
pub entries: u32,
pub wait_timeout: Option<Duration>,
pub napi_busy_poll_timeout: Option<u32>,
pub prefer_busy_poll: bool,
}
impl Default for IoUringConfig {
fn default() -> Self {
Self {
entries: 64,
wait_timeout: None,
napi_busy_poll_timeout: None,
prefer_busy_poll: false,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
enum Operation {
Connect = 1,
Read = 2,
Cancel = 3,
}
impl Operation {
fn from_user_data(user_data: u64) -> Option<Self> {
match (user_data >> 32) as u8 {
1 => Some(Self::Connect),
2 => Some(Self::Read),
3 => Some(Self::Cancel),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Registration {
fd: RawFd,
operation: Operation,
}
fn user_data(token: SelectorToken, operation: Operation) -> u64 {
((operation as u64) << 32) | u64::from(token)
}
fn token_from_user_data(user_data: u64) -> SelectorToken {
user_data as SelectorToken
}
pub struct IoUringSelector<S> {
ring: IoUring,
config: IoUringConfig,
registrations: HashMap<SelectorToken, Registration>,
next_token: SelectorToken,
phantom: PhantomData<S>,
}
impl<S> IoUringSelector<S> {
pub fn new() -> io::Result<Self> {
Self::new_with_config(IoUringConfig::default())
}
pub fn new_with_config(config: IoUringConfig) -> io::Result<Self> {
if config.entries == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "io_uring entries must be non-zero"));
}
let ring = IoUring::builder().setup_single_issuer().build(config.entries)?;
if let Some(timeout) = config.napi_busy_poll_timeout {
let mut napi = types::Napi::new()
.set_busy_poll_timeout(timeout)
.set_prefer_busy_poll(config.prefer_busy_poll);
ring.submitter().register_napi(&mut napi)?;
}
Ok(Self {
ring,
config,
registrations: HashMap::new(),
next_token: 0,
phantom: PhantomData,
})
}
fn push(&mut self, entry: squeue::Entry) -> io::Result<()> {
let pushed = {
let mut submission = self.ring.submission();
unsafe { submission.push(&entry).is_ok() }
};
if pushed {
return Ok(());
}
self.ring.submit()?;
let mut submission = self.ring.submission();
unsafe { submission.push(&entry) }.map_err(|_| io::Error::other("io_uring submission queue is full"))
}
fn arm(&mut self, token: SelectorToken, fd: RawFd, operation: Operation) -> io::Result<()> {
let flags = match operation {
Operation::Connect => libc::POLLOUT | libc::POLLERR | libc::POLLHUP,
Operation::Read => libc::POLLIN | libc::POLLERR | libc::POLLHUP,
Operation::Cancel => unreachable!(),
} as u32;
let entry = opcode::PollAdd::new(types::Fd(fd), flags)
.multi(operation == Operation::Read)
.build()
.user_data(user_data(token, operation));
self.push(entry)
}
fn wait(&self) -> io::Result<usize> {
if self.registrations.is_empty() {
return self.ring.submit();
}
let Some(timeout) = self.config.wait_timeout else {
return self.ring.submit();
};
if timeout.is_zero() {
return self.ring.submit();
}
let timespec = types::Timespec::from(timeout);
let args = types::SubmitArgs::new().timespec(×pec);
match self.ring.submitter().submit_with_args(1, &args) {
Err(error) if error.raw_os_error() == Some(libc::ETIME) => Ok(0),
result => result,
}
}
fn completions(&mut self) -> Vec<(u64, i32, u32)> {
let mut completions = Vec::new();
let mut queue = self.ring.completion();
for completion in &mut queue {
completions.push((completion.user_data(), completion.result(), completion.flags()));
}
completions
}
}
impl<S: AsRawFd + Selectable> Selector for IoUringSelector<S> {
type Target = S;
fn register<E>(&mut self, token: SelectorToken, io_node: &mut IONode<Self::Target, E>) -> io::Result<()> {
let fd = io_node.as_stream().as_raw_fd();
self.arm(token, fd, Operation::Connect)?;
self.ring.submit()?;
self.registrations.insert(
token,
Registration {
fd,
operation: Operation::Connect,
},
);
Ok(())
}
fn unregister<E>(&mut self, io_node: &mut IONode<Self::Target, E>) -> io::Result<()> {
let fd = io_node.as_stream().as_raw_fd();
let Some(token) = self
.registrations
.iter()
.find_map(|(token, registration)| (registration.fd == fd).then_some(*token))
else {
return Ok(());
};
let registration = self.registrations.remove(&token).unwrap();
let entry = opcode::PollRemove::new(user_data(token, registration.operation))
.build()
.user_data(user_data(token, Operation::Cancel));
self.push(entry)?;
self.ring.submit()?;
Ok(())
}
fn poll<E>(&mut self, io_nodes: &mut HashMap<SelectorToken, IONode<Self::Target, E>>) -> io::Result<()> {
self.wait()?;
let completions = self.completions();
let mut rearms = Vec::new();
for (data, result, flags) in completions {
let Some(operation) = Operation::from_user_data(data) else {
continue;
};
if operation == Operation::Cancel {
continue;
}
let token = token_from_user_data(data);
let Some(registration) = self.registrations.get(&token).copied() else {
continue;
};
if registration.operation != operation {
continue;
}
if result < 0 {
let errno = -result;
if matches!(errno, libc::ECANCELED | libc::ENOENT) {
continue;
}
return Err(io::Error::from_raw_os_error(errno));
}
let Some(io_node) = io_nodes.get_mut(&token) else {
continue;
};
match operation {
Operation::Connect => {
if io_node.as_stream_mut().connected()? {
io_node.as_stream_mut().make_writable()?;
self.registrations.get_mut(&token).unwrap().operation = Operation::Read;
rearms.push((token, registration.fd, Operation::Read));
} else {
rearms.push((token, registration.fd, Operation::Connect));
}
}
Operation::Read => {
io_node.as_stream_mut().make_readable()?;
if !cqueue::more(flags) {
rearms.push((token, registration.fd, Operation::Read));
}
}
Operation::Cancel => unreachable!(),
}
}
for (token, fd, operation) in rearms {
if self.registrations.contains_key(&token) {
self.arm(token, fd, operation)?;
}
}
if !self.registrations.is_empty() {
self.ring.submit()?;
}
Ok(())
}
fn next_token(&mut self) -> SelectorToken {
let token = self.next_token;
self.next_token = self.next_token.wrapping_add(1);
token
}
}
impl<E: Endpoint> IntoIOService<E> for IoUringSelector<E::Target>
where
E::Target: AsRawFd + Selectable,
{
fn into_io_service(self) -> IOService<Self, E, (), SystemTimeClockSource, BlockingDnsResolver> {
IOService::new(self, SystemTimeClockSource, BlockingDnsResolver)
}
}
impl<C: Context, E: EndpointWithContext<C>> IntoIOServiceWithContext<E, C> for IoUringSelector<E::Target>
where
E::Target: AsRawFd + Selectable,
{
fn into_io_service_with_context(self) -> IOService<Self, E, C, SystemTimeClockSource, BlockingDnsResolver> {
IOService::new(self, SystemTimeClockSource, BlockingDnsResolver)
}
}