use core::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::io;
use std::os::fd::RawFd;
use std::time::Duration;
use super::recv::RecvGroup;
use crate::fs::ring::FileOp;
use crate::io::source::ops::Done;
use crate::io::source::ring::{Arrival, RingMem, RingOp};
use crate::io::sys;
use crate::rt::park::Notifier;
use crate::rt::park::ring::RingPoller;
use crate::syscore::linux::iouring::files::Files;
use crate::syscore::linux::iouring::ops::Ops;
use crate::syscore::linux::iouring::ring::sys::{
IORING_CQE_F_MORE, IOSQE_FIXED_FILE, cqe_buffer_id,
};
use crate::syscore::linux::iouring::ring::{IoUringCqe, RingConfig, Sqe};
use exits::ExitOp;
pub(crate) struct ShardRing {
poller: RefCell<RingPoller>,
ops: RefCell<Ops<RingOp>>,
file_ops: RefCell<Ops<FileOp>>,
exit_ops: RefCell<Ops<ExitOp>>,
waitid: bool,
files: RefCell<Files>,
room: RefCell<VecDeque<(u64, Sqe)>>,
in_kernel: Cell<usize>,
cap: usize,
cqes: RefCell<Vec<IoUringCqe>>,
recv: RefCell<Option<RecvGroup>>,
#[cfg(test)]
counts: Cell<(usize, usize, usize)>,
#[cfg(test)]
file_calls: Cell<usize>,
}
impl ShardRing {
pub(crate) fn new(cfg: &RingConfig, files: u32) -> io::Result<Self> {
let poller = RingPoller::new(cfg, files)?;
let recv = match cfg.recv_buffers {
0 => None,
entries => match RecvGroup::new(poller.ring(), entries, cfg.recv_buffer_size) {
Ok(group) => Some(group),
Err(err) if err.kind() == io::ErrorKind::InvalidInput => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("io_uring recv_buffers and recv_buffer_size: {err}"),
));
}
Err(_) => None,
},
};
let arrivals = recv
.as_ref()
.map_or(0, |g: &RecvGroup| usize::from(g.entries()));
let cap = (poller.cq_entries() as usize)
.saturating_sub(arrivals)
.max(1);
let waitid = poller.ring().register_probe().is_ok_and(|ops| {
ops.contains(crate::syscore::linux::iouring::ring::sys::Opcode::Waitid)
});
Ok(Self {
poller: RefCell::new(poller),
ops: RefCell::new(Ops::new((1 << 31) - 1)),
file_ops: RefCell::new(Ops::new(1 << 30)),
exit_ops: RefCell::new(Ops::new(1 << 30)),
waitid,
files: RefCell::new(Files::new(files)),
room: RefCell::new(VecDeque::new()),
in_kernel: Cell::new(0),
cap,
cqes: RefCell::new(Vec::with_capacity(256)),
recv: RefCell::new(recv),
#[cfg(test)]
counts: Cell::new((0, 0, 0)),
#[cfg(test)]
file_calls: Cell::new(0),
})
}
pub(crate) fn recv_group(&self) -> Option<u16> {
self.recv.borrow().as_ref().map(RecvGroup::bgid)
}
pub(crate) fn recv_buf_size(&self) -> Option<u32> {
self.recv.borrow().as_ref().map(RecvGroup::buf_size)
}
pub(crate) fn notifier(&self) -> Notifier {
self.poller.borrow().notifier()
}
pub(crate) fn submit(&self, op: RingOp, sqe: Sqe) -> u64 {
self.files.borrow_mut().named(op.file);
let sqe = match self.files.borrow().copy_of(op.file) {
Some(copy) => sqe.fd(copy),
None => sqe.flags(IOSQE_FIXED_FILE),
};
op.source.op_out();
#[cfg(test)]
if matches!(
op.mem,
RingMem::RecvMulti { .. } | RingMem::RecvFromMulti { .. } | RingMem::AcceptMulti { .. }
) {
let (armed, arrived, dry) = self.counts.get();
self.counts.set((armed + 1, arrived, dry));
}
let inserted = self.ops.borrow_mut().insert(op);
let user_data = match inserted {
Ok(user_data) => user_data,
Err(op) => {
self.end(op, -libc::ENOBUFS);
return IGNORED;
}
};
self.enter(user_data, sqe);
user_data
}
fn enter(&self, user_data: u64, sqe: Sqe) {
let sqe = sqe.user_data(user_data);
if self.room.borrow().is_empty() && self.in_kernel.get() < self.cap {
self.in_kernel.set(self.in_kernel.get() + 1);
self.poller.borrow_mut().push(sqe);
} else {
self.room.borrow_mut().push_back((user_data, sqe));
}
}
pub(crate) fn submit_file(&self, mut op: FileOp) {
#[cfg(test)]
self.file_calls.set(self.file_calls.get() + 1);
let sqe = op.entry();
let inserted = self.file_ops.borrow_mut().insert(op);
let user_data = match inserted {
Ok(user_data) => user_data | FILE_TAG,
Err(op) => {
drop(op.complete(-libc::ECANCELED));
return;
}
};
self.enter(user_data, sqe);
}
fn complete_file(&self, user_data: u64, res: i32) {
let Some(op) = self.file_ops.borrow_mut().remove(user_data & !FILE_TAG) else {
return;
};
self.in_kernel.set(self.in_kernel.get().saturating_sub(1));
self.admit_waiting();
if let Some(rest) = op.complete(res) {
self.submit_file(rest);
}
}
fn admit_waiting(&self) {
while self.in_kernel.get() < self.cap {
let Some((_, sqe)) = self.room.borrow_mut().pop_front() else {
return;
};
self.in_kernel.set(self.in_kernel.get() + 1);
self.poller.borrow_mut().push(sqe);
}
}
pub(crate) fn cancel(&self, user_data: u64) {
let waiting = {
let mut room = self.room.borrow_mut();
room.iter()
.position(|(u, _)| *u == user_data)
.and_then(|at| room.remove(at))
};
if waiting.is_some() {
if let Some(op) = self.ops.borrow_mut().remove(user_data) {
self.end(op, -libc::ECANCELED);
}
return;
}
if self.ops.borrow_mut().get_mut(user_data).is_some() {
self.poller
.borrow_mut()
.push(Sqe::async_cancel(user_data).user_data(IGNORED));
}
}
pub(crate) fn attach(&self, fd: RawFd) -> io::Result<u32> {
let fixed = self.files.borrow_mut().take();
match fixed {
Some(slot) => match self.poller.borrow().update_file(slot, fd) {
Ok(()) => Ok(slot),
Err(err) => {
self.files.borrow_mut().release(slot);
Err(err)
}
},
None => {
let copy = sys::duplicate(fd)?;
Ok(self.files.borrow_mut().take_copy(copy))
}
}
}
pub(crate) fn detach(&self, slot: u32, out: [Option<u64>; 2]) {
for user_data in out.into_iter().flatten() {
self.cancel(user_data);
}
let freed = self.files.borrow_mut().release(slot);
self.freed(slot, freed);
}
fn freed(&self, slot: u32, freed: bool) {
if freed && self.files.borrow().is_fixed(slot) {
let _ = self.poller.borrow().update_file(slot, -1);
}
}
pub(crate) fn wait(&self, timeout: Option<Duration>) -> io::Result<(usize, bool)> {
self.take_back();
let mut cqes = core::mem::take(&mut *self.cqes.borrow_mut());
let woken = self.poller.borrow_mut().wait(timeout, &mut cqes);
let handled = cqes.len();
for cqe in cqes.drain(..) {
self.complete(cqe);
}
*self.cqes.borrow_mut() = cqes;
Ok((handled, woken?))
}
fn complete(&self, cqe: IoUringCqe) {
if cqe.user_data & FILE_TAG != 0 {
if cqe.user_data & EXIT_TAG != 0 {
self.complete_exit(cqe.user_data, cqe.res);
} else {
self.complete_file(cqe.user_data, cqe.res);
}
return;
}
if let Some(bid) = cqe_buffer_id(cqe.flags) {
self.arrived(cqe.user_data, bid, cqe.res);
} else if cqe.res >= 0 {
self.accepted(cqe.user_data, cqe.res);
}
if cqe.flags & IORING_CQE_F_MORE != 0 {
return;
}
let Some(op) = self.ops.borrow_mut().remove(cqe.user_data) else {
return;
};
self.in_kernel.set(self.in_kernel.get().saturating_sub(1));
self.admit_waiting();
self.end(op, cqe.res);
}
fn arrived(&self, user_data: u64, bid: u16, res: i32) {
let mut stop = false;
let delivery = {
let mut recv = self.recv.borrow_mut();
let Some(group) = recv.as_mut() else {
return;
};
let mut ops = self.ops.borrow_mut();
let held = ops.get_mut(user_data).and_then(|op| match &mut op.mem {
RingMem::RecvMulti { held, stopping } => Some((op.epoch, held, stopping, false)),
RingMem::RecvFromMulti { held, stopping, .. } => {
Some((op.epoch, held, stopping, true))
}
_ => None,
});
match (u32::try_from(res), held) {
(Ok(len @ 1..), Some((epoch, held, stopping, datagram))) => {
match group.hand_out(user_data, bid, len) {
Some(chunk) => {
*held += len as usize;
if *held > group.cap() && !*stopping {
*stopping = true;
stop = true;
}
ops.get_mut(user_data).map(|op| {
(std::sync::Arc::clone(&op.source), chunk, epoch, datagram)
})
}
None => None,
}
}
_ => {
group.give_back(bid);
None
}
}
};
if stop {
self.cancel(user_data);
}
if let Some((source, chunk, epoch, datagram)) = delivery {
#[cfg(test)]
{
let (armed, arrived, dry) = self.counts.get();
self.counts.set((armed, arrived + 1, dry));
}
let arrival = if datagram {
match crate::io::source::ring::datagram_of(chunk, source.kind()) {
Some((payload, from)) => Arrival::Datagram(payload, from),
None => return,
}
} else {
Arrival::Chunk(chunk)
};
source.ring_arrival(arrival, epoch);
}
}
fn accepted(&self, user_data: u64, res: i32) {
let delivery = {
let mut ops = self.ops.borrow_mut();
let Some(op) = ops.get_mut(user_data) else {
return;
};
let RingMem::AcceptMulti { handed, stopping } = &mut op.mem else {
return;
};
*handed += 1;
let stop = *handed > ACCEPT_CAP && !*stopping;
if stop {
*stopping = true;
}
let conn =
unsafe { <std::os::fd::OwnedFd as std::os::fd::FromRawFd>::from_raw_fd(res) };
(std::sync::Arc::clone(&op.source), conn, op.epoch, stop)
};
let (source, conn, epoch, stop) = delivery;
if stop {
self.cancel(user_data);
}
source.ring_arrival(Arrival::Conn(conn), epoch);
}
fn take_back(&self) {
let mut recv = self.recv.borrow_mut();
let Some(group) = recv.as_mut() else {
return;
};
let mut ops = self.ops.borrow_mut();
group.take_back(|user_data, len| {
if let Some(RingMem::RecvMulti { held, .. } | RingMem::RecvFromMulti { held, .. }) =
ops.get_mut(user_data).map(|op| &mut op.mem)
{
*held = held.saturating_sub(len as usize);
}
});
}
fn end(&self, op: RingOp, res: i32) {
#[cfg(test)]
if res == -libc::ENOBUFS && matches!(op.mem, RingMem::RecvMulti { .. }) {
let (armed, arrived, dry) = self.counts.get();
self.counts.set((armed, arrived, dry + 1));
}
let freed = self.files.borrow_mut().done(op.file);
self.freed(op.file, freed);
let kind = op.source.kind();
op.source.op_done(Done {
outcome: op.mem.complete(res, kind),
epoch: op.epoch,
read: op.read,
});
}
pub(crate) fn shut_down(&self) {
let waiting: Vec<u64> = self.room.borrow_mut().drain(..).map(|(u, _)| u).collect();
for user_data in waiting {
if user_data & (FILE_TAG | EXIT_TAG) == FILE_TAG | EXIT_TAG {
self.drop_waiting_exit(user_data);
} else if user_data & FILE_TAG != 0 {
let op = self.file_ops.borrow_mut().remove(user_data & !FILE_TAG);
if let Some(op) = op {
drop(op.complete(-libc::ECANCELED));
}
} else {
let op = self.ops.borrow_mut().remove(user_data);
if let Some(op) = op {
self.end(op, -libc::ECANCELED);
}
}
}
if self.in_flight() > 0 {
self.poller
.borrow_mut()
.push(Sqe::async_cancel_any(true).user_data(IGNORED));
}
self.settle(|| {});
let stuck: Vec<RingOp> = self.ops.borrow_mut().drain().collect();
let stuck_files: Vec<FileOp> = self.file_ops.borrow_mut().drain().collect();
let stuck_exits: Vec<ExitOp> = self.exit_ops.borrow_mut().drain().collect();
LEAKED.fetch_add(
stuck.len() + stuck_files.len() + stuck_exits.len(),
core::sync::atomic::Ordering::Relaxed,
);
core::mem::forget(stuck_files);
core::mem::forget(stuck_exits);
if !stuck.is_empty() {
core::mem::forget(self.recv.borrow_mut().take());
}
core::mem::forget(stuck);
}
}
impl ShardRing {
pub(crate) fn in_flight(&self) -> usize {
self.ops.borrow().len() + self.file_ops.borrow().len() + self.exit_ops.borrow().len()
}
pub(crate) fn overflows(&self) -> u64 {
self.poller.borrow().overflows()
}
pub(crate) fn settle(&self, mut between: impl FnMut()) {
let deadline = std::time::Instant::now() + SHUTDOWN_WAIT;
loop {
between();
if self.in_flight() == 0 {
return;
}
let left = deadline.saturating_duration_since(std::time::Instant::now());
if left.is_zero()
|| self
.wait(Some(left.min(Duration::from_millis(10))))
.is_err()
{
return;
}
}
}
}
const FILE_TAG: u64 = 1 << 31;
const EXIT_TAG: u64 = 1 << 30;
const ACCEPT_CAP: usize = 256;
const IGNORED: u64 = u32::MAX as u64;
const SHUTDOWN_WAIT: Duration = Duration::from_secs(1);
static LEAKED: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[cfg(test)]
pub(crate) fn leaked() -> usize {
LEAKED.load(core::sync::atomic::Ordering::Relaxed)
}
#[cfg(test)]
impl ShardRing {
pub(crate) fn files_in_use(&self) -> (usize, usize) {
let files = self.files.borrow();
(files.in_use(), files.copies_in_use())
}
pub(crate) fn multishot_counts(&self) -> (usize, usize, usize) {
self.counts.get()
}
pub(crate) fn held(&self) -> usize {
self.take_back();
self.recv.borrow().as_ref().map_or(0, RecvGroup::out)
}
pub(crate) fn file_calls(&self) -> usize {
self.file_calls.get()
}
pub(crate) fn receives_in_flight(&self) -> usize {
self.ops
.borrow()
.iter()
.filter(|op| {
matches!(
op.mem,
RingMem::RecvMulti { .. }
| RingMem::RecvFromMulti { .. }
| RingMem::AcceptMulti { .. }
)
})
.count()
}
pub(crate) fn recv_cap(&self) -> Option<usize> {
self.recv.borrow().as_ref().map(RecvGroup::cap)
}
}
#[cfg(test)]
thread_local! {
pub(crate) static TABLE_FOR_TEST: Cell<Option<u32>> = const { Cell::new(None) };
}
pub(crate) fn table_len() -> u32 {
#[cfg(test)]
if let Some(len) = TABLE_FOR_TEST.with(Cell::get) {
return len;
}
if !crate::syscore::linux::iouring::files::release_at_once() {
return 0;
}
let mut limit: libc::rlimit = unsafe { core::mem::zeroed() };
let soft = if unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) } == 0 {
limit.rlim_cur
} else {
1024
};
u32::try_from(soft)
.unwrap_or(MAX_TABLE)
.clamp(64, MAX_TABLE)
}
const MAX_TABLE: u32 = 1 << 14;
pub(crate) mod exits;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_recv;