use core::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::io;
use std::os::fd::RawFd;
use std::sync::Arc;
use std::time::Duration;
#[cfg(feature = "io-stats")]
use std::time::Instant;
use super::Shard;
use super::recv::RecvGroup;
use crate::fs::ring::FileOp;
use crate::io::source::ops::Done;
use crate::io::source::requests::Lease;
use crate::io::source::ring::{RingMem, RingOp};
use crate::rt::backend::with_room;
#[cfg(feature = "io-stats")]
use crate::rt::counters::{IoKind, IoTimes};
use crate::rt::park::Notifier;
use crate::rt::park::ring::{CqeBatch, 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_BUF_MORE, IORING_CQE_F_BUFFER, IORING_CQE_F_MORE, IORING_CQE_F_NOTIF,
IOSQE_FIXED_FILE, KernelTimespec, 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,
send_zc: bool,
recover: recover::Recover,
zc_denied: Cell<bool>,
zero_copy_min: Cell<usize>,
reapable: bool,
files: RefCell<Files>,
table: Cell<u32>,
leaving: RefCell<Vec<(u32, Option<Lease>)>>,
room: RefCell<VecDeque<(u64, Sqe)>>,
in_kernel: Cell<usize>,
cap: usize,
writes: Cell<usize>,
recv: RefCell<Option<RecvGroup>>,
#[cfg(feature = "io-stats")]
timing: std::cell::OnceCell<Box<Timing>>,
#[cfg(test)]
counts: Cell<(usize, usize, usize)>,
#[cfg(test)]
polls_armed: Cell<usize>,
#[cfg(test)]
file_calls: Cell<usize>,
#[cfg(test)]
file_entries: RefCell<Vec<FileEntry>>,
#[cfg(test)]
zc_seen: Cell<(usize, usize)>,
}
impl ShardRing {
pub(crate) fn new(cfg: &RingConfig, files: u32) -> io::Result<Self> {
let poller = RingPoller::new(
&RingConfig {
taskrun_flag: crate::syscore::linux::iouring::ring::taskrun::flag_works(),
..*cfg
},
0,
)?;
let recv = match group_entries(cfg.recv_buffers, poller.cq_entries()) {
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(err) if err.kind() == io::ErrorKind::OutOfMemory => {
super::recv::went_without(entries, cfg.recv_buffer_size, &err);
None
}
Err(err) => return Err(err),
},
};
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);
use crate::syscore::linux::iouring::ring::sys::Opcode;
let probed = poller.ring().register_probe().unwrap_or_default();
let waitid = probed.contains(Opcode::Waitid);
let send_zc = probed.contains(Opcode::SendZc);
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) - 256)),
waitid,
send_zc,
recover: recover::Recover::default(),
zc_denied: Cell::new(false),
zero_copy_min: Cell::new(crate::io::source::ring::SEND_ZC_MIN),
reapable: cfg.multi_issuer,
files: RefCell::new(Files::new(files)),
table: Cell::new(files),
leaving: RefCell::new(Vec::new()),
room: RefCell::new(VecDeque::new()),
in_kernel: Cell::new(0),
cap,
writes: Cell::new(0),
recv: RefCell::new(recv),
#[cfg(feature = "io-stats")]
timing: std::cell::OnceCell::new(),
#[cfg(test)]
counts: Cell::new((0, 0, 0)),
#[cfg(test)]
polls_armed: Cell::new(0),
#[cfg(test)]
file_calls: Cell::new(0),
#[cfg(test)]
file_entries: RefCell::new(Vec::new()),
#[cfg(test)]
zc_seen: Cell::new((0, 0)),
})
}
pub(crate) fn for_shard(cfg: RingConfig) -> io::Result<Self> {
with_room(cfg.entries, MIN_RING_ENTRIES, |entries| {
Self::new(&RingConfig { entries, ..cfg }, table_len())
})
}
pub(crate) fn enable(&self) -> io::Result<()> {
self.poller.borrow_mut().enable()
}
pub(crate) fn expects_completions(&self) -> bool {
self.in_kernel.get() > 0 || self.writes.get() > 0 || !self.room.borrow().is_empty()
}
#[inline(always)]
pub(crate) fn take_in_now(
&self,
back: &Arc<super::recv::Returns>,
item: super::recv::Back,
) -> bool {
let Ok(mut recv) = self.recv.try_borrow_mut() else {
return false;
};
let Some(group) = recv.as_mut() else {
return false;
};
let Ok(mut ops) = self.ops.try_borrow_mut() else {
return group.keep_local(back, item);
};
match group.take_in_now(back, item) {
Some(Some((user_data, charged))) => {
release_charge(&mut ops, user_data, charged);
true
}
Some(None) => true,
None => false,
}
}
pub(crate) fn recv_group(&self) -> Option<u16> {
self.recv.borrow().as_ref().map(RecvGroup::bgid)
}
pub(crate) fn zc_stream(&self) -> bool {
self.send_zc && self.reapable && !self.zc_denied.get()
}
pub(crate) fn zero_copy_min(&self) -> usize {
self.zero_copy_min.get()
}
pub(crate) fn set_zero_copy_min(&self, bytes: usize) {
self.zero_copy_min.set(bytes);
}
pub(crate) fn reapable(&self) -> bool {
self.reapable
}
pub(crate) fn recv_group_incremental(&self) -> bool {
self.recv
.borrow()
.as_ref()
.is_some_and(RecvGroup::incremental)
}
pub(crate) fn recv_buf_size(&self) -> Option<u32> {
self.recv.borrow().as_ref().map(RecvGroup::buf_size)
}
pub(crate) fn datagrams_ahead(&self) -> bool {
!self.recv_group_incremental()
&& self
.recv_buf_size()
.is_some_and(|s| s >= crate::io::source::ring::DATAGRAM_ROOM)
}
pub(crate) fn recv_reservation(&self) -> usize {
self.recv
.borrow()
.as_ref()
.map_or(0, RecvGroup::reservation)
}
pub(crate) fn notifier(&self) -> Notifier {
self.poller.borrow().notifier()
}
#[inline]
pub(crate) fn submit(&self, op: RingOp, sqe: Sqe) -> u64 {
let sqe = match self.files.borrow_mut().name(op.file) {
Some(own) => sqe.fd(own),
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));
}
#[cfg(test)]
if matches!(op.mem, RingMem::ReadyMulti) {
self.polls_armed.set(self.polls_armed.get() + 1);
}
let write = counts_as_write(&op);
#[cfg(test)]
let full = REFUSE_INSERT_FOR_TEST.with(|n| n.replace(n.get().saturating_sub(1))) > 0;
#[cfg(not(test))]
let full = false;
let inserted = if full {
Err(op)
} else {
self.ops.borrow_mut().insert(op)
};
let user_data = match inserted {
Ok(user_data) => user_data,
Err(op) => {
self.end(None, op, -libc::ENOMEM);
return IGNORED;
}
};
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.stamp(user_data);
}
if write {
self.writes.set(self.writes.get() + 1);
}
self.enter(user_data, sqe, false);
user_data
}
pub(super) fn enter(&self, user_data: u64, sqe: Sqe, counted: bool) {
let sqe = sqe.user_data(user_data);
if !counted {
self.poller.borrow_mut().push(sqe);
} else 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();
#[cfg(test)]
self.file_entries.borrow_mut().push((
sqe.raw().opcode,
sqe.raw().len,
sqe.raw().off,
sqe.raw().op_flags,
));
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;
}
};
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.stamp(user_data);
}
self.enter(user_data, sqe, true);
}
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.swept_file_ended(user_data);
};
self.in_kernel.set(self.in_kernel.get().saturating_sub(1));
self.admit_waiting();
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.timed(user_data, Some(IoKind::File), res);
}
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) -> bool {
if self.ops.borrow_mut().get_mut(user_data).is_some() {
self.poller
.borrow_mut()
.push(Sqe::async_cancel(user_data).user_data(IGNORED));
return true;
}
false
}
pub(crate) fn attach(&self, fd: RawFd) -> io::Result<u32> {
if self.table.get() != 0 {
self.table_or_none();
}
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 => Ok(self.files.borrow_mut().take_own(fd)),
}
}
pub(crate) fn make_table(&self) -> io::Result<()> {
let slots = self.table.get();
if slots != 0 {
self.poller.borrow().ring().register_files_sparse(slots)?;
self.table.set(0);
}
Ok(())
}
#[cold]
#[inline(never)]
fn table_or_none(&self) {
if let Err(err) = self.make_table() {
self.table.set(0);
self.files.borrow_mut().without_table();
static SAID: core::sync::atomic::AtomicBool =
core::sync::atomic::AtomicBool::new(false);
if !SAID.swap(true, core::sync::atomic::Ordering::Relaxed) {
log::warn!(
"io_uring's table of fixed files could not be set up ({err}); sockets use their own descriptors instead."
);
}
}
}
pub(crate) fn detach(&self, slot: u32, out: [Option<u64>; 2], lease: Option<Lease>) {
let mut cancelled = false;
for user_data in out.into_iter().flatten() {
cancelled |= self.cancel(user_data);
}
let named = cancelled || self.files.borrow().is_named(slot);
if named && !self.poller.borrow_mut().submit_now().unwrap_or(false) {
self.leaving.borrow_mut().push((slot, lease));
return;
}
self.take_out(slot, lease);
}
fn take_out(&self, slot: u32, lease: Option<Lease>) {
if self.files.borrow().is_fixed(slot)
&& let Err(err) = self.take_file_out(slot)
{
self.retry_take_out(slot, &err);
} else {
self.files.borrow_mut().release(slot);
}
drop(lease);
}
fn take_file_out(&self, slot: u32) -> io::Result<()> {
#[cfg(test)]
if REFUSE_TAKE_OUT_FOR_TEST.with(|n| n.replace(n.get().saturating_sub(1))) > 0 {
return Err(io::Error::from_raw_os_error(libc::ENOMEM));
}
self.poller.borrow().update_file(slot, -1)
}
#[cold]
#[inline(never)]
fn retry_take_out(&self, slot: u32, err: &io::Error) {
static SAID: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
if !SAID.swap(true, core::sync::atomic::Ordering::Relaxed) {
log::warn!(
"io_uring could not take a closed file out of its table ({err}); trying again shortly."
);
}
self.leaving.borrow_mut().push((slot, None));
self.poller
.borrow_mut()
.push(Sqe::timeout(&RETRY_TAKE_OUT).user_data(IGNORED));
}
fn finish_leaving(&self) {
if self.leaving.borrow().is_empty()
|| !self.poller.borrow_mut().submit_now().unwrap_or(false)
{
return;
}
let leaving = core::mem::take(&mut *self.leaving.borrow_mut());
for (slot, lease) in leaving {
self.take_out(slot, lease);
}
}
pub(crate) fn wait(
&self,
shard: Option<&Shard>,
timeout: Option<Duration>,
) -> io::Result<(usize, bool)> {
self.take_back();
let mut batch = CqeBatch::new();
let mut woken = self.poller.borrow_mut().wait(timeout, &mut batch);
let mut handled = 0;
let _unwound = crate::io::side::Unwound::enter();
loop {
handled += batch.len();
for at in 0..batch.len() {
self.complete(shard, batch.get(at));
}
if !batch.is_full() {
break;
}
batch.clear();
let (woke_more, _) = self.poller.borrow_mut().reap(&mut batch);
woken = woken.map(|w| w || woke_more);
}
self.finish_leaving();
Ok((handled, woken?))
}
#[inline(always)]
fn complete(&self, shard: Option<&Shard>, cqe: IoUringCqe) {
#[cfg(test)]
if self.drops_for_test(&cqe) {
return;
}
if cqe.user_data & FILE_TAG != 0 {
if cqe.user_data & EXIT_TAG != 0 {
self.complete_exit(shard, cqe.user_data, cqe.res);
} else {
self.complete_file(cqe.user_data, cqe.res);
}
return;
}
let plain =
if cqe.flags & (IORING_CQE_F_MORE | IORING_CQE_F_NOTIF | IORING_CQE_F_BUFFER) == 0 {
self.ops.borrow_mut().remove_if(cqe.user_data, ends_plain)
} else {
None
};
if let Some(op) = plain {
if counts_as_write(&op) {
self.writes.set(self.writes.get().saturating_sub(1));
}
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.timed(cqe.user_data, io_kind(&op.mem), cqe.res);
}
self.end(shard, op, cqe.res);
return;
}
if let Some(bid) = cqe_buffer_id(cqe.flags) {
self.arrived(
shard,
cqe.user_data,
bid,
cqe.res,
cqe.flags & IORING_CQE_F_BUF_MORE != 0,
cqe.flags & IORING_CQE_F_MORE != 0,
);
} else if self.zero_copy(shard, &cqe) {
return;
} else if cqe.res >= 0 {
self.accepted(shard, 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.recover.late(cqe.user_data);
};
if let RingMem::Zombie | RingMem::RecvZombie { .. } = op.mem {
return self.bury(cqe.user_data, op);
}
if counts_as_write(&op) {
self.writes.set(self.writes.get().saturating_sub(1));
}
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.timed(cqe.user_data, io_kind(&op.mem), cqe.res);
}
self.end(shard, op, cqe.res);
}
#[cold]
#[inline(never)]
pub(crate) fn adopt(&self, shard: Option<&Shard>) {
if self.poller.borrow().sqpoll() {
return;
}
let mut pending = Vec::new();
self.poller
.borrow()
.pending(|user_data| pending.push(user_data));
pending.sort_unstable();
let old: Vec<u64> = self
.ops
.borrow()
.keys()
.filter(|user_data| pending.binary_search(user_data).is_err())
.collect();
self.poller.borrow_mut().adopt(IGNORED, &pending);
let _ = self.wait(shard, Some(Duration::ZERO));
let mut ended = Vec::new();
{
let mut ops = self.ops.borrow_mut();
for user_data in old {
let Some(op) = ops.get_mut(user_data) else {
continue;
};
let mem = core::mem::replace(&mut op.mem, RingMem::Ready);
let (zombie, outcome) = match mem.adopted(op.source.kind()) {
Ok(adopted) => adopted,
Err(mem) => {
op.mem = mem;
continue;
}
};
op.mem = zombie;
self.poller
.borrow_mut()
.push(Sqe::async_cancel(user_data).user_data(IGNORED));
if counts_as_write(op) {
self.writes.set(self.writes.get().saturating_sub(1));
}
op.source.op_out();
let read = core::mem::replace(&mut op.read, true);
ended.push((Arc::clone(&op.source), op.epoch, read, outcome));
}
}
for (source, epoch, read, outcome) in ended {
source.op_done(
Done {
outcome,
epoch,
read,
},
shard,
);
}
}
#[cold]
#[inline(never)]
fn bury(&self, user_data: u64, op: RingOp) {
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.timed(user_data, None, 0);
}
#[cfg(not(feature = "io-stats"))]
let _ = user_data;
self.files.borrow_mut().done(op.file);
op.source.op_gone();
}
fn zero_copy(&self, shard: Option<&Shard>, cqe: &IoUringCqe) -> bool {
let notif = cqe.flags & IORING_CQE_F_NOTIF != 0;
let mut ops = self.ops.borrow_mut();
let Some(op) = ops.get_mut(cqe.user_data) else {
return false;
};
let waits = match &mut op.mem {
RingMem::SendZc { notified, .. } if notif => {
*notified = true;
return true;
}
RingMem::SendZc { notified, .. } => cqe.flags & IORING_CQE_F_MORE != 0 && !*notified,
RingMem::ZcHeld(_) if notif => {
let held = ops.remove(cqe.user_data);
drop(ops);
#[cfg(test)]
self.zc_seen
.set((self.zc_seen.get().0, self.zc_seen.get().1 + 1));
drop(held);
return true;
}
_ => return false,
};
let (source, file, epoch) = (Arc::clone(&op.source), op.file, op.epoch);
let ended = if waits {
op.mem.hold_for_notification();
None
} else {
ops.remove(cqe.user_data)
};
drop(ops);
#[cfg(test)]
if waits {
self.zc_seen
.set((self.zc_seen.get().0 + 1, self.zc_seen.get().1));
}
self.writes.set(self.writes.get().saturating_sub(1));
let mut res = cqe.res;
if zc_refused(res) {
self.zc_denied.set(true);
res = -libc::ECANCELED;
}
#[cfg(feature = "io-stats")]
if self.timing.get().is_some() {
self.timed(cqe.user_data, Some(IoKind::Write), res);
}
let outcome = crate::io::source::ring::zc_outcome(res);
self.hand_over(shard, &source, file, epoch, false, outcome);
drop(ended);
true
}
#[cfg(feature = "io-stats")]
pub(super) fn time_with(&self, times: Arc<IoTimes>) {
let _ = self.timing.set(Box::new(Timing {
times,
stamps: RefCell::new(std::collections::HashMap::new()),
}));
}
#[cfg(feature = "io-stats")]
#[cold]
#[inline(never)]
fn stamp(&self, user_data: u64) {
if let Some(timing) = self.timing.get() {
timing.stamps.borrow_mut().insert(user_data, Instant::now());
}
}
#[cfg(feature = "io-stats")]
#[cold]
#[inline(never)]
fn timed(&self, user_data: u64, kind: Option<IoKind>, res: i32) {
let stamp = self
.timing
.get()
.and_then(|timing| timing.stamps.borrow_mut().remove(&user_data));
if let (Some(at), Some(kind)) = (stamp, kind) {
self.time(kind, res, at);
}
}
#[cfg(feature = "io-stats")]
fn time(&self, kind: IoKind, res: i32, issued: Instant) {
if let Some(times) = self.timing.get().map(|timing| &timing.times)
&& res != -libc::ECANCELED
&& res != -libc::EAGAIN
&& !(matches!(kind, IoKind::Connect) && res == -libc::EALREADY)
{
times.add(kind, issued.elapsed());
}
}
fn end(&self, shard: Option<&Shard>, op: RingOp, res: i32) {
let RingOp {
source,
file,
epoch,
read,
mem,
} = op;
#[cfg(test)]
if res == -libc::ENOBUFS && matches!(mem, RingMem::RecvMulti { .. }) {
let (armed, arrived, dry) = self.counts.get();
self.counts.set((armed, arrived, dry + 1));
}
let outcome = mem.complete(res, source.kind());
self.hand_over(shard, &source, file, epoch, read, outcome);
}
#[inline]
fn hand_over(
&self,
shard: Option<&Shard>,
source: &Arc<crate::io::source::Source>,
file: u32,
epoch: u32,
read: bool,
outcome: crate::io::source::ops::Outcome,
) {
self.files.borrow_mut().done(file);
source.op_done(
Done {
outcome,
epoch,
read,
},
shard,
);
}
}
#[cfg(feature = "io-stats")]
struct Timing {
times: Arc<IoTimes>,
stamps: RefCell<std::collections::HashMap<u64, Instant>>,
}
#[cfg(feature = "io-stats")]
fn io_kind(mem: &RingMem) -> Option<IoKind> {
match mem {
RingMem::Recv(_)
| RingMem::RecvLent(_)
| RingMem::RecvFrom(..)
| RingMem::RecvLentFrom(..) => Some(IoKind::Read),
RingMem::Send(_)
| RingMem::SendLent(_)
| RingMem::SendTo(..)
| RingMem::SendLentTo(..)
| RingMem::SendPinned(_) => Some(IoKind::Write),
RingMem::Accept(_) => Some(IoKind::Accept),
RingMem::Connect(..) => Some(IoKind::Connect),
_ => None,
}
}
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()
}
}
fn zc_refused(res: i32) -> bool {
[
-libc::ENOBUFS,
-libc::ENOMEM,
-libc::EPERM,
-libc::EOPNOTSUPP,
]
.contains(&res)
}
fn group_entries(configured: u16, cq_entries: u32) -> u16 {
if configured == 0 || !configured.is_power_of_two() {
return configured;
}
let half = cq_entries / 2;
if half == 0 {
return 0;
}
let fit = 1u32 << half.ilog2();
configured.min(u16::try_from(fit.min(1 << 15)).unwrap_or(1 << 15))
}
#[inline(always)]
fn release_charge(ops: &mut Ops<RingOp>, user_data: u64, charged: usize) {
if let Some(
RingMem::RecvMulti { held, .. }
| RingMem::RecvFromMulti { held, .. }
| RingMem::RecvZombie { held, .. },
) = ops.get_mut(user_data).map(|op| &mut op.mem)
{
*held = held.saturating_sub(charged);
}
}
fn ends_plain(op: &RingOp) -> bool {
!matches!(
op.mem,
RingMem::SendZc { .. }
| RingMem::ZcHeld(_)
| RingMem::RecvMulti { .. }
| RingMem::RecvFromMulti { .. }
| RingMem::AcceptMulti { .. }
| RingMem::ReadyMulti
| RingMem::Zombie
| RingMem::RecvZombie { .. }
)
}
fn counts_as_write(op: &RingOp) -> bool {
!op.read && !matches!(op.mem, RingMem::ZcHeld(_))
}
#[cfg(test)]
pub(crate) type FileEntry = (u8, u32, u64, u32);
const FILE_TAG: u64 = 1 << 31;
const EXIT_TAG: u64 = 1 << 30;
const ACCEPT_CAP: usize = 256;
const IGNORED: u64 = u32::MAX as u64;
pub(crate) const RECOVER: u64 = IGNORED | (1 << 32);
const SWEEP: u64 = IGNORED | (2 << 32);
impl Drop for ShardRing {
fn drop(&mut self) {
self.recover.keep_leftovers();
}
}
static RETRY_TAKE_OUT: KernelTimespec = KernelTimespec {
tv_sec: 0,
tv_nsec: 10_000_000,
};
#[cfg(test)]
thread_local! {
pub(crate) static REFUSE_TAKE_OUT_FOR_TEST: Cell<u32> = const { Cell::new(0) };
}
#[cfg(test)]
thread_local! {
pub(crate) static REFUSE_INSERT_FOR_TEST: Cell<u32> = const { Cell::new(0) };
}
#[cfg(test)]
impl ShardRing {
pub(crate) fn files_in_use(&self) -> (usize, usize) {
let files = self.files.borrow();
(files.in_use(), files.own_in_use())
}
pub(crate) fn table_to_make(&self) -> u32 {
self.table.get()
}
pub(crate) fn multishot_counts(&self) -> (usize, usize, usize) {
self.counts.get()
}
pub(crate) fn polls_armed(&self) -> usize {
self.polls_armed.get()
}
pub(crate) fn held(&self) -> usize {
self.take_back();
self.recv.borrow().as_ref().map_or(0, RecvGroup::out)
}
pub(crate) fn live_chunks(&self) -> (u32, bool) {
self.take_back();
self.recv
.borrow()
.as_ref()
.map_or((0, false), |g| (g.live(), g.incremental()))
}
pub(crate) fn zc_seen(&self) -> (usize, usize) {
self.zc_seen.get()
}
pub(crate) fn file_calls(&self) -> usize {
self.file_calls.get()
}
pub(crate) fn take_file_entries(&self) -> Vec<FileEntry> {
core::mem::take(&mut *self.file_entries.borrow_mut())
}
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 group_buffers(&self) -> Option<u16> {
self.recv.borrow().as_ref().map(RecvGroup::entries)
}
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).min(MAX_TABLE)
}
const MAX_TABLE: u32 = 1 << 14;
const MIN_RING_ENTRIES: u32 = crate::config::MIN_QUEUE_LEN;
const CHECK_TABLE: u32 = 64;
const CHECK_BUFFERS: u16 = 2;
pub(crate) fn check(cfg: RingConfig) -> io::Result<()> {
let group = cfg.recv_buffers != 0;
let small = RingConfig {
entries: MIN_RING_ENTRIES,
cq_entries: None,
recv_buffers: cfg.recv_buffers.min(CHECK_BUFFERS),
recv_buffer_size: if group {
crate::config::MIN_RECV_BUFFER_SIZE
} else {
cfg.recv_buffer_size
},
..cfg
};
let files = table_len().min(CHECK_TABLE);
let ring = ShardRing::new(&small, files)?;
ring.make_table()?;
ring.enable()
}
pub(crate) mod exits;
pub(crate) mod keeper;
mod recover;
#[cfg(test)]
pub(crate) use keeper::leaked;
mod arrivals;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_armed;
#[cfg(test)]
mod tests_cap;
#[cfg(test)]
mod tests_end;
#[cfg(test)]
mod tests_inc;
#[cfg(test)]
mod tests_recv;
#[cfg(test)]
mod tests_reserve;
#[cfg(test)]
mod tests_slab;
#[cfg(test)]
mod tests_zc;