#![warn(
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]
#![doc = include_str!("../README.md")]
use io_uring::opcode::EpollCtl;
use io_uring::IoUring;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::os::fd::RawFd;
#[derive(Debug)]
pub enum EpollHandlerError {
IoUringCreate(String),
EpollCreate1(String),
NotSupported,
Probing(String),
Duplicate,
Submission(String),
}
impl core::fmt::Display for EpollHandlerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Self::IoUringCreate(s) => write!(f, "IoUring Create: {}", s),
Self::EpollCreate1(s) => write!(f, "epoll_create1(): {}", s),
Self::NotSupported => write!(
f,
"EpollCtl io_uring OpCode is not supported in your Kernel"
),
Self::Probing(s) => write!(
f,
"Error whilst probing EpollCtl support from kernel: {}",
s
),
Self::Duplicate => write!(f, "The filehandle is already maped in. Possible duplicate?"),
Self::Submission(s) => write!(f, "Submission: {}", s),
}
}
}
pub struct EpollHandler<'fd> {
pub(crate) epfd: u32,
pub(crate) io_uring: IoUring<io_uring::squeue::Entry, io_uring::cqueue::Entry>,
pub(crate) in_flight: u32,
pub(crate) fds: HashMap<i32, &'fd HandledFd>,
pub(crate) submit_counter: u64,
}
impl<'fd> EpollHandler<'fd> {
pub fn new(capacity: u32) -> Result<Self, EpollHandlerError> {
let iou: IoUring<io_uring::squeue::Entry, io_uring::cqueue::Entry> = IoUring::builder()
.build(capacity)
.map_err(|e| EpollHandlerError::IoUringCreate(e.to_string()))?;
Self::from_io_uring(iou)
}
pub fn from_io_uring(
iou: IoUring<io_uring::squeue::Entry, io_uring::cqueue::Entry>,
) -> Result<Self, EpollHandlerError> {
let mut epoll_probe = io_uring::Probe::new();
iou.submitter()
.register_probe(&mut epoll_probe)
.map_err(|e| EpollHandlerError::Probing(e.to_string()))?;
#[allow(clippy::bool_comparison)]
if epoll_probe.is_supported(EpollCtl::CODE) == false {
return Err(EpollHandlerError::NotSupported);
}
let epfd = unsafe { libc::epoll_create1(0) };
if epfd == -1 {
let errno = unsafe { libc::__errno_location() };
return Err(EpollHandlerError::EpollCreate1(format!(
"errno: {:?}",
errno
)));
}
Ok(Self {
epfd: epfd as u32,
io_uring: iou,
in_flight: 0,
submit_counter: 0,
fds: HashMap::new(),
})
}
pub fn io_uring(&mut self) -> &mut IoUring<io_uring::squeue::Entry, io_uring::cqueue::Entry> {
&mut self.io_uring
}
pub fn add_fd(&mut self, handled_fd: &'fd HandledFd) -> Result<(), EpollHandlerError> {
self.fds.insert(handled_fd.fd, handled_fd);
Ok(())
}
pub fn submit(&self) -> Result<usize, EpollHandlerError> {
self.io_uring
.submit()
.map_err(|e| EpollHandlerError::Submission(e.to_string()))
}
pub fn submit_and_wait(&self, want: usize) -> Result<usize, EpollHandlerError> {
self.io_uring
.submit_and_wait(want)
.map_err(|e| EpollHandlerError::Submission(e.to_string()))
}
pub fn prepare_submit(&mut self) -> Result<FdCommitResults<'fd>, EpollHandlerError> {
let mut fd_commit_results = FdCommitResults {
new_commits: 0,
change_commits: 0,
no_change: 0,
empty: 0,
errors_on_submit: vec![],
};
let iou = &mut self.io_uring;
let mut s_queue = iou.submission();
let mut updates: VecDeque<HandledFd> = VecDeque::new();
for (_, handled_fd) in self.fds.iter() {
let mut new_fd = (**handled_fd).clone();
let mut commit_new: Option<i32> = None;
let mut epoll_op = EPOLL_CTL_MOD;
if handled_fd.wants.is_none() && handled_fd.committed.is_some() {
epoll_op = EPOLL_CTL_DEL;
commit_new = Some(0);
}
if let Some(fd_wants) = handled_fd.wants {
match handled_fd.committed {
None => {
commit_new = Some(handled_fd.wants.unwrap_or(0));
epoll_op = EPOLL_CTL_ADD;
fd_commit_results.new_commits += 1;
}
Some(committed) => {
if committed != fd_wants {
commit_new = Some(fd_wants);
fd_commit_results.change_commits += 1;
} else {
fd_commit_results.no_change += 1;
}
}
}
if let Some(commit) = commit_new {
self.submit_counter += 1;
let epoll_event = libc::epoll_event {
events: commit as u32,
u64: self.submit_counter,
};
let uring_submission_rec = EpollCtl::new(
io_uring::types::Fixed(self.epfd),
io_uring::types::Fd(handled_fd.fd),
epoll_op,
std::ptr::addr_of!(epoll_event) as *const io_uring::types::epoll_event,
)
.build();
let p_result = unsafe { s_queue.push(&uring_submission_rec) };
match p_result {
Err(e) => {
new_fd.error = Some(e.to_string());
fd_commit_results.errors_on_submit.push(handled_fd);
}
Ok(_) => {
new_fd.current_submission = Some(self.submit_counter);
self.in_flight += 1;
}
}
}
} else {
fd_commit_results.empty += 1;
}
if new_fd != **handled_fd {
updates.push_back(new_fd);
}
}
while let Some(update) = updates.pop_front() {
let fd_get: Option<&&HandledFd> = self.fds.get(&update.fd);
if let Some(unwrapped_fd) = fd_get {
let mut pinned_mut: std::pin::Pin<&mut &HandledFd> = std::pin::pin!(*unwrapped_fd);
let pinned_update: std::pin::Pin<&mut &HandledFd> = std::pin::pin!(&update);
let _st = std::mem::replace(&mut pinned_mut, pinned_update);
}
}
Ok(fd_commit_results)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HandledFd {
pub(crate) fd: RawFd,
pub(crate) wants: Option<i32>,
pub(crate) pending: Option<i32>,
pub(crate) committed: Option<i32>,
pub(crate) error: Option<String>,
pub(crate) current_submission: Option<u64>,
}
const EPOLL_CTL_ADD: i32 = 1;
const EPOLL_CTL_DEL: i32 = 2;
const EPOLL_CTL_MOD: i32 = 3;
impl HandledFd {
pub fn new(fd: RawFd) -> Self {
HandledFd {
fd,
wants: None,
committed: None,
current_submission: None,
error: None,
pending: None,
}
}
pub fn as_raw(&self) -> RawFd {
self.fd
}
fn turn_on_or_off(&mut self, mask_in: i32, on_or_off: bool) -> i32 {
let cur_wants: i32 = self.wants.unwrap_or(0);
self.wants = match on_or_off {
true => Some(cur_wants | mask_in),
false => Some(cur_wants ^ mask_in),
};
self.wants.unwrap_or(0)
}
pub fn set_in(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLIN, on_or_off)
}
pub fn set_pri(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLPRI, on_or_off)
}
pub fn set_out(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLOUT, on_or_off)
}
pub fn set_err(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLERR, on_or_off)
}
pub fn set_hup(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLHUP, on_or_off)
}
pub fn set_rdnorm(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLRDNORM, on_or_off)
}
pub fn set_rdband(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLRDBAND, on_or_off)
}
pub fn set_wrnorm(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLWRNORM, on_or_off)
}
pub fn set_wrband(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLWRBAND, on_or_off)
}
pub fn set_msg(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLMSG, on_or_off)
}
pub fn set_rdhup(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLRDHUP, on_or_off)
}
pub fn set_wakeup(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLWAKEUP, on_or_off)
}
pub fn set_oneshot(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLONESHOT, on_or_off)
}
pub fn set_et(&mut self, on_or_off: bool) -> i32 {
self.turn_on_or_off(libc::EPOLLET, on_or_off)
}
pub fn get_mask_raw(&mut self) -> Option<i32> {
self.wants
}
pub fn set_mask_raw(&mut self, mask: i32) {
self.wants = Some(mask);
}
pub fn get_pending(&self) -> Option<i32> {
self.pending
}
}
#[derive(Debug)]
pub struct FdCommitResults<'fd> {
pub(crate) new_commits: u32,
pub(crate) change_commits: u32,
pub(crate) no_change: u32,
pub(crate) empty: u32,
pub(crate) errors_on_submit: Vec<&'fd HandledFd>,
}
impl<'fd> FdCommitResults<'fd> {
pub fn count_new(&self) -> u32 {
self.new_commits
}
pub fn count_changes(&self) -> u32 {
self.change_commits
}
pub fn count_no_changes(&self) -> u32 {
self.no_change
}
pub fn count_empty(&self) -> u32 {
self.empty
}
pub fn errors(&'fd self) -> &'fd Vec<&'fd HandledFd> {
&self.errors_on_submit
}
}
#[cfg(test)]
mod test {
use super::HandledFd;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, TcpListener};
use std::os::fd::AsRawFd;
fn handle_fd() -> HandledFd {
let s =
TcpListener::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0)).unwrap();
HandledFd::new(s.as_raw_fd())
}
#[test]
fn mask_fd_inouts() {
let mut fd = handle_fd();
fd.set_in(true);
assert_eq!(fd.set_out(true), 5);
assert_eq!(fd.set_in(false), 4);
assert_eq!(fd.set_out(false), 0);
}
#[test]
fn mask_fd_in() {
let mut fd = handle_fd();
assert_eq!(fd.set_in(true), 1);
assert_eq!(fd.set_in(false), 0);
}
#[test]
fn mask_fd_pri() {
let mut fd = handle_fd();
assert_eq!(fd.set_pri(true), 2);
assert_eq!(fd.set_pri(false), 0);
}
#[test]
fn mask_fd_out() {
let mut fd = handle_fd();
assert_eq!(fd.set_out(true), 4);
assert_eq!(fd.set_out(false), 0);
}
#[test]
fn mask_fd_err() {
let mut fd = handle_fd();
assert_eq!(fd.set_err(true), 8);
assert_eq!(fd.set_err(false), 0);
}
#[test]
fn mask_fd_hup() {
let mut fd = handle_fd();
assert_eq!(fd.set_hup(true), 0x00000010);
assert_eq!(fd.set_hup(false), 0);
}
#[test]
fn mask_fd_rdnorm() {
let mut fd = handle_fd();
assert_eq!(fd.set_rdnorm(true), 0x00000040);
assert_eq!(fd.set_rdnorm(false), 0);
}
#[test]
fn mask_fd_rdband() {
let mut fd = handle_fd();
assert_eq!(fd.set_rdband(true), 0x00000080);
assert_eq!(fd.set_rdband(false), 0);
}
#[test]
fn mask_fd_wrnorm() {
let mut fd = handle_fd();
assert_eq!(fd.set_wrnorm(true), 0x00000100);
assert_eq!(fd.set_wrnorm(false), 0);
}
#[test]
fn mask_fd_wrband() {
let mut fd = handle_fd();
assert_eq!(fd.set_wrband(true), 0x00000200);
assert_eq!(fd.set_wrband(false), 0);
}
#[test]
fn mask_fd_msg() {
let mut fd = handle_fd();
assert_eq!(fd.set_msg(true), 0x00000400);
assert_eq!(fd.set_msg(false), 0);
}
#[test]
fn mask_fd_rdhup() {
let mut fd = handle_fd();
assert_eq!(fd.set_rdhup(true), 0x00002000);
assert_eq!(fd.set_rdhup(false), 0);
}
#[test]
fn mask_fd_wakeup() {
let mut fd = handle_fd();
assert_eq!(fd.set_wakeup(true), 0x20000000);
assert_eq!(fd.set_wakeup(false), 0);
}
#[test]
fn mask_fd_oneshot() {
let mut fd = handle_fd();
assert_eq!(fd.set_oneshot(true), 0x40000000);
assert_eq!(fd.set_oneshot(false), 0);
}
#[test]
fn mask_fd_et() {
let mut fd = handle_fd();
assert_eq!(fd.set_et(true) as u32, 0x80000000);
assert_eq!(fd.set_et(false), 0);
}
}