use std::any::Any;
use std::collections::VecDeque;
use std::io;
use std::mem;
use std::os::fd::RawFd;
use std::rc::Rc;
use std::task::{Poll, Waker};
use std::time::Duration;
use io_uring::{cqueue, opcode, squeue, types, IoUring};
use crate::buf::BufPool;
use crate::slab::Slab;
pub(crate) const BGID: u16 = 0;
const UD_IGNORE: u64 = u64::MAX;
const UD_WAKE: u64 = u64::MAX - 1;
const RECV_BACKLOG_LIMIT: usize = 8;
const ACCEPT_BACKLOG_LIMIT: usize = 1024;
fn backlog_limit(kind: MultiKind) -> usize {
match kind {
MultiKind::Accept => ACCEPT_BACKLOG_LIMIT,
MultiKind::Recv => RECV_BACKLOG_LIMIT,
}
}
fn holds_pool_buffer(kind: MultiKind, res: i32, flags: u32) -> bool {
kind == MultiKind::Recv && res > 0 && cqueue::buffer_select(flags).is_some()
}
pub(crate) enum Park {
Poll,
Wait(Option<Duration>),
}
enum Lifecycle {
Waiting(Waker),
Done(i32, u32),
Ignored(Option<Box<dyn Any>>),
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum MultiKind {
Accept,
Recv,
}
enum Queued {
Raw(i32, u32),
Heap(Vec<u8>),
}
struct Multi {
kind: MultiKind,
queue: VecDeque<Queued>,
waker: Option<Waker>,
armed: bool,
pausing: bool,
orphan: bool,
}
enum OpState {
Single(Lifecycle),
Multi(Multi),
}
pub(crate) enum MultiPoll {
Item(i32, u32),
Heap(Vec<u8>),
NeedArm,
Pending,
}
pub(crate) struct Driver {
ring: IoUring,
ops: Slab<OpState>,
inflight: usize,
pool: Rc<BufPool>,
cqes: Vec<(u64, i32, u32)>,
to_pause: Vec<usize>,
starved: bool,
pooled_queued: usize,
graveyard: Vec<Box<dyn Any>>,
wake_fd: RawFd,
wake_buf: Box<u64>,
wake_armed: bool,
closing: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum RingTier {
Defer,
Coop,
Basic,
}
fn first_tier() -> RingTier {
match std::env::var("GIONG_RING_TIER").as_deref() {
Ok("coop") => RingTier::Coop,
Ok("basic") => RingTier::Basic,
_ => RingTier::Defer,
}
}
fn build_ring(entries: u32) -> io::Result<IoUring> {
build_ring_from(entries, first_tier())
}
fn build_ring_from(entries: u32, first: RingTier) -> io::Result<IoUring> {
let cq = entries.saturating_mul(4);
if first <= RingTier::Defer {
let mut b = IoUring::builder();
b.setup_single_issuer()
.setup_defer_taskrun()
.setup_taskrun_flag()
.setup_submit_all()
.setup_cqsize(cq)
.setup_clamp();
if let Ok(ring) = b.build(entries) {
return Ok(ring);
}
}
if first <= RingTier::Coop {
let mut b = IoUring::builder();
b.setup_coop_taskrun()
.setup_taskrun_flag()
.setup_submit_all()
.setup_cqsize(cq)
.setup_clamp();
if let Ok(ring) = b.build(entries) {
return Ok(ring);
}
}
let mut b = IoUring::builder();
b.setup_cqsize(cq).setup_clamp();
b.build(entries)
}
impl Driver {
pub(crate) fn new(entries: u32, pool: Rc<BufPool>, wake_fd: RawFd) -> io::Result<Self> {
let ring = build_ring(entries)?;
unsafe {
ring.submitter()
.register_buf_ring_with_flags(pool.ring_addr(), pool.count(), BGID, 0)
.map_err(|e| {
io::Error::new(
e.kind(),
format!("giong needs io_uring buffer rings (Linux >= 6.0): {e}"),
)
})?;
}
Ok(Self {
ring,
ops: Slab::new(),
inflight: 0,
pool,
cqes: Vec::with_capacity(256),
to_pause: Vec::new(),
starved: false,
pooled_queued: 0,
graveyard: Vec::new(),
wake_fd,
wake_buf: Box::new(0),
wake_armed: false,
closing: false,
})
}
fn push(&mut self, sqe: squeue::Entry) {
let mut attempts = 0u32;
loop {
if unsafe { self.ring.submission().push(&sqe) }.is_ok() {
break;
}
match self.ring.submit() {
Ok(_) => {}
Err(e) => match e.raw_os_error() {
Some(libc::EINTR) => {}
Some(libc::EBUSY) | Some(libc::EAGAIN) => self.reap(),
_ => panic!("giong: io_uring submit failed: {e}"),
},
}
attempts += 1;
assert!(
attempts < 100_000,
"giong: unable to make progress submitting to io_uring"
);
}
self.inflight += 1;
}
pub(crate) fn submit_op(&mut self, sqe: squeue::Entry, waker: &Waker) -> usize {
let key = self
.ops
.insert(OpState::Single(Lifecycle::Waiting(waker.clone())));
self.push(sqe.user_data(key as u64));
key
}
pub(crate) fn poll_op(&mut self, key: usize, waker: &Waker) -> Poll<(i32, u32)> {
match self.ops.get_mut(key) {
Some(OpState::Single(Lifecycle::Done(res, flags))) => {
let out = (*res, *flags);
self.ops.remove(key);
Poll::Ready(out)
}
Some(OpState::Single(Lifecycle::Waiting(w))) => {
if !w.will_wake(waker) {
*w = waker.clone();
}
Poll::Pending
}
_ => panic!("giong: operation polled in an invalid state"),
}
}
pub(crate) fn cancel_op(&mut self, key: usize, data: Option<Box<dyn Any>>) {
match self.ops.get_mut(key) {
Some(OpState::Single(lc @ Lifecycle::Waiting(_))) => {
*lc = Lifecycle::Ignored(data);
self.push(
opcode::AsyncCancel::new(key as u64)
.build()
.user_data(UD_IGNORE),
);
}
Some(OpState::Single(Lifecycle::Done(..))) => {
self.ops.remove(key);
if let Some(data) = data {
self.graveyard.push(data);
}
}
_ => {
mem::forget(data);
}
}
}
pub(crate) fn new_multi(&mut self, kind: MultiKind) -> usize {
self.ops.insert(OpState::Multi(Multi {
kind,
queue: VecDeque::new(),
waker: None,
armed: false,
pausing: false,
orphan: false,
}))
}
pub(crate) fn arm_multi(&mut self, key: usize, sqe: squeue::Entry) {
match self.ops.get_mut(key) {
Some(OpState::Multi(m)) if !m.armed => m.armed = true,
_ => panic!("giong: arming an invalid multishot operation"),
}
self.push(sqe.user_data(key as u64));
}
pub(crate) fn poll_multi(&mut self, key: usize, waker: &Waker) -> MultiPoll {
let Some(OpState::Multi(m)) = self.ops.get_mut(key) else {
panic!("giong: polling an invalid multishot operation");
};
match m.queue.pop_front() {
Some(Queued::Raw(res, flags)) => {
if holds_pool_buffer(m.kind, res, flags) {
self.pooled_queued -= 1;
}
return MultiPoll::Item(res, flags);
}
Some(Queued::Heap(data)) => return MultiPoll::Heap(data),
None => {}
}
if !m.armed {
return MultiPoll::NeedArm;
}
match &mut m.waker {
Some(w) if w.will_wake(waker) => {}
slot => *slot = Some(waker.clone()),
}
MultiPoll::Pending
}
pub(crate) fn drop_multi(&mut self, key: usize) {
let Some(OpState::Multi(m)) = self.ops.get_mut(key) else {
return;
};
let kind = m.kind;
let armed = m.armed;
let queued = mem::take(&mut m.queue);
m.waker = None;
m.orphan = true;
for item in queued {
if let Queued::Raw(res, flags) = item {
if holds_pool_buffer(kind, res, flags) {
self.pooled_queued -= 1;
}
Self::release(&self.pool, kind, res, flags);
}
}
if !armed {
self.ops.remove(key);
} else {
self.push(
opcode::AsyncCancel::new(key as u64)
.build()
.user_data(UD_IGNORE),
);
}
}
fn release(pool: &BufPool, kind: MultiKind, res: i32, flags: u32) {
match kind {
MultiKind::Accept => {
if res >= 0 {
unsafe { libc::close(res) };
}
}
MultiKind::Recv => {
if let Some(bid) = cqueue::buffer_select(flags) {
pool.recycle(bid);
}
}
}
}
pub(crate) fn evacuate(&mut self) {
if self.pooled_queued == 0 {
return;
}
self.pooled_queued = 0;
let pool = &self.pool;
for op in self.ops.iter_mut() {
let OpState::Multi(m) = op else { continue };
if m.kind != MultiKind::Recv {
continue;
}
for item in m.queue.iter_mut() {
if let Queued::Raw(res, flags) = *item {
if let (true, Some(bid)) = (res > 0, cqueue::buffer_select(flags)) {
*item = Queued::Heap(pool.copy_out(bid, res as usize));
pool.recycle(bid);
}
}
}
}
}
#[cfg(not(giong_tsan))]
pub(crate) fn close_fd(&mut self, fd: RawFd) {
self.push(
opcode::Close::new(types::Fd(fd))
.build()
.user_data(UD_IGNORE),
);
}
#[cfg(giong_tsan)]
pub(crate) fn close_fd(&mut self, fd: RawFd) {
unsafe { libc::close(fd) };
}
pub(crate) fn pool(&self) -> &Rc<BufPool> {
&self.pool
}
pub(crate) fn has_completions(&mut self) -> bool {
if self.ring.submission().taskrun() {
return true;
}
!self.ring.completion().is_empty()
}
fn arm_wake(&mut self) {
let buf = &mut *self.wake_buf as *mut u64 as *mut u8;
let sqe = opcode::Read::new(types::Fd(self.wake_fd), buf, 8)
.build()
.user_data(UD_WAKE);
self.wake_armed = true;
self.push(sqe);
}
pub(crate) fn park(&mut self, mode: Park) -> Vec<Box<dyn Any>> {
if !self.wake_armed && !self.closing {
self.arm_wake();
}
let res = match mode {
Park::Poll => {
let (queued, pending) = {
let sq = self.ring.submission();
(sq.len(), sq.taskrun() || sq.cq_overflow())
};
if queued > 0 || pending {
self.ring.submit_and_wait(0).map(drop)
} else {
Ok(())
}
}
Park::Wait(None) => self.ring.submit_and_wait(1).map(drop),
Park::Wait(Some(dur)) => {
let ts = types::Timespec::from(dur);
let args = types::SubmitArgs::new().timespec(&ts);
self.ring.submitter().submit_with_args(1, &args).map(drop)
}
};
if let Err(e) = res {
match e.raw_os_error() {
Some(libc::EINTR) | Some(libc::ETIME) | Some(libc::EBUSY) | Some(libc::EAGAIN) => {}
_ => panic!("giong: io_uring_enter failed: {e}"),
}
}
self.reap();
mem::take(&mut self.graveyard)
}
fn reap(&mut self) {
let mut batch = mem::take(&mut self.cqes);
loop {
batch.clear();
{
let mut cq = self.ring.completion();
cq.sync();
for cqe in &mut cq {
batch.push((cqe.user_data(), cqe.result(), cqe.flags()));
}
}
if batch.is_empty() {
break;
}
for &(user_data, res, flags) in batch.iter() {
self.dispatch(user_data, res, flags);
}
let to_pause = mem::take(&mut self.to_pause);
for key in to_pause {
self.push(
opcode::AsyncCancel::new(key as u64)
.build()
.user_data(UD_IGNORE),
);
}
if mem::take(&mut self.starved) {
self.evacuate();
}
}
self.cqes = batch;
}
fn dispatch(&mut self, user_data: u64, res: i32, flags: u32) {
let more = cqueue::more(flags);
if !more {
self.inflight = self.inflight.saturating_sub(1);
}
match user_data {
UD_IGNORE => {}
UD_WAKE => self.wake_armed = false,
key => {
let key = key as usize;
let remove = match self.ops.get_mut(key) {
None => false,
Some(OpState::Single(lc)) => {
match mem::replace(lc, Lifecycle::Done(res, flags)) {
Lifecycle::Waiting(waker) => {
waker.wake();
false
}
Lifecycle::Ignored(data) => {
if let Some(data) = data {
self.graveyard.push(data);
}
true
}
Lifecycle::Done(..) => false,
}
}
Some(OpState::Multi(m)) => {
if m.kind == MultiKind::Recv && cqueue::buffer_select(flags).is_some() {
self.pool.taken();
}
if !more {
m.armed = false;
}
if m.orphan {
Self::release(&self.pool, m.kind, res, flags);
!more
} else {
let paused = !more && mem::replace(&mut m.pausing, false);
let recv = m.kind == MultiKind::Recv;
if recv && res == -libc::ENOBUFS {
self.starved = true;
Self::release(&self.pool, m.kind, res, flags);
} else if paused && res == -libc::ECANCELED {
Self::release(&self.pool, m.kind, res, flags);
} else {
let limit = backlog_limit(m.kind);
match cqueue::buffer_select(flags) {
Some(bid) if recv && res > 0 && m.queue.len() >= limit => {
m.queue.push_back(Queued::Heap(
self.pool.copy_out(bid, res as usize),
));
self.pool.recycle(bid);
}
_ => {
if holds_pool_buffer(m.kind, res, flags) {
self.pooled_queued += 1;
}
m.queue.push_back(Queued::Raw(res, flags));
}
}
if m.armed && m.queue.len() % limit == 0 {
m.pausing = true;
self.to_pause.push(key);
}
}
if let Some(waker) = m.waker.take() {
waker.wake();
}
false
}
}
};
if remove {
self.ops.remove(key);
}
}
}
}
pub(crate) fn shutdown(&mut self) -> bool {
self.closing = true;
for _ in 0..100 {
match self.ring.submit_and_wait(0) {
Ok(_) => {}
Err(e)
if matches!(
e.raw_os_error(),
Some(libc::EINTR | libc::EBUSY | libc::EAGAIN)
) => {}
Err(_) => return false,
}
self.reap();
if self.inflight == 0 {
return true;
}
let _ = self.ring.submitter().register_sync_cancel(
Some(types::Timespec::from(Duration::from_millis(10))),
types::CancelBuilder::any().all(),
);
let ts = types::Timespec::from(Duration::from_millis(10));
let args = types::SubmitArgs::new().timespec(&ts);
let _ = self.ring.submitter().submit_with_args(1, &args);
}
self.reap();
self.inflight == 0
}
pub(crate) fn take_graveyard(&mut self) -> Vec<Box<dyn Any>> {
mem::take(&mut self.graveyard)
}
pub(crate) fn inflight(&self) -> usize {
self.inflight
}
pub(crate) fn tracked_ops(&self) -> usize {
self.ops.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_ring_tier_builds() {
let coop = build_ring_from(64, RingTier::Coop).expect("coop tier");
assert!(!coop.params().is_setup_single_issuer());
let basic = build_ring_from(64, RingTier::Basic).expect("basic tier");
assert!(!basic.params().is_setup_single_issuer());
build_ring_from(64, RingTier::Defer).expect("default tier");
}
}