use std::io;
use std::sync::Arc;
use super::counted::LazyCounted;
use crate::syscore::linux::iouring::ops::NOT_AN_OP;
use crate::syscore::linux::iouring::ring::Ring;
use crate::syscore::linux::iouring::ring::buf_ring::{self, BufMem, BufRing};
const GROUP: u16 = 0;
const CAP_SHARE: usize = 8;
const MIN_CHARGE: u32 = 256;
#[cfg(test)]
thread_local! {
pub(crate) static INCREMENTAL_FOR_TEST: core::cell::Cell<Option<bool>> =
const { core::cell::Cell::new(None) };
}
pub(crate) struct RecvGroup {
ring: BufRing,
back: Arc<Returns>,
fills: Vec<Fill>,
local: Vec<Back>,
cap: usize,
incremental: bool,
#[cfg(test)]
out: usize,
#[cfg(test)]
readded: usize,
}
#[derive(Clone, Copy, Default)]
struct Fill {
used: u32,
live: u32,
done: bool,
}
#[derive(Clone, Copy)]
pub(crate) struct Back {
bid: u16,
owner: u64,
len: u32,
}
unsafe impl Send for RecvGroup {}
pub(crate) struct Returns {
mem: Arc<BufMem>,
queue: LazyCounted<Back>,
}
pub(crate) struct RingChunk {
back: Arc<Returns>,
base: core::ptr::NonNull<u8>,
bid: u16,
start: u32,
end: u32,
owner: u64,
len: u32,
}
unsafe impl Send for RingChunk {}
unsafe impl Sync for RingChunk {}
pub(crate) fn reservation(buffers: u16, size: u32) -> usize {
if buffers == 0 {
return 0;
}
buf_ring::reservation(buffers, size)
}
pub(crate) fn too_large(buffers: u16, size: u32, bytes: usize, cap: usize) -> io::Error {
crate::config::refuse(
"io_uring recv_buffers x recv_buffer_size",
format_args!("{buffers} x {size}, {bytes} bytes in all"),
format_args!(
"at most io_buffer_cap ({cap} bytes) less {} bytes for the shard's other IO buffers",
crate::buf::CLASSES[0]
),
)
}
pub(crate) fn went_without(buffers: u16, size: u32, err: &io::Error) {
static SAID: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
crate::rt::counters::receive_buffers_missing();
if SAID.swap(true, core::sync::atomic::Ordering::Relaxed) {
return;
}
let mut limit = libc::rlimit {
rlim_cur: 0,
rlim_max: 0,
};
let known = unsafe { libc::getrlimit(libc::RLIMIT_MEMLOCK, &mut limit) } == 0;
let limit = match (known, limit.rlim_cur) {
(false, _) => "unknown".to_string(),
(true, libc::RLIM_INFINITY) => "unlimited".to_string(),
(true, bytes) => format!("{} KiB", bytes / 1024),
};
log::warn!(
"io_uring receive buffers ({buffers} x {size} bytes) could not be set up ({err}); reads use pool buffers instead. The locked-memory limit is {limit}: raise it (ulimit -l) or lower recv_buffers or recv_buffer_size."
);
}
impl RecvGroup {
pub(crate) fn new(ring: &Ring, entries: u16, size: u32) -> io::Result<Self> {
#[cfg(test)]
let ask = INCREMENTAL_FOR_TEST.with(core::cell::Cell::get) != Some(false);
#[cfg(not(test))]
let ask = true;
let (mut buffers, incremental) =
match ask.then(|| BufRing::incremental(ring, GROUP, entries, size)) {
Some(Ok(buffers)) => (buffers, true),
_ => (BufRing::new(ring, GROUP, entries, size)?, false),
};
buffers.add_all()?;
buffers.commit();
let back = Arc::new(Returns {
mem: Arc::clone(buffers.memory()),
queue: LazyCounted::new(),
});
let total = usize::from(entries) * size as usize;
Ok(Self {
ring: buffers,
back,
fills: vec![Fill::default(); usize::from(entries)],
local: Vec::with_capacity(usize::from(entries)),
cap: (total / CAP_SHARE).max(size as usize),
incremental,
#[cfg(test)]
out: 0,
#[cfg(test)]
readded: 0,
})
}
pub(crate) fn incremental(&self) -> bool {
self.incremental
}
pub(crate) fn bgid(&self) -> u16 {
self.ring.bgid()
}
pub(crate) fn entries(&self) -> u16 {
self.ring.entries()
}
pub(crate) fn cap(&self) -> usize {
self.cap
}
pub(crate) fn buf_size(&self) -> u32 {
self.ring.buf_size()
}
pub(crate) fn reservation(&self) -> usize {
reservation(self.ring.entries(), self.ring.buf_size())
}
pub(crate) fn charge(&self, len: u32) -> usize {
(if self.incremental {
len.max(MIN_CHARGE)
} else {
len
}) as usize
}
pub(crate) fn hand_out(
&mut self,
user_data: u64,
bid: u16,
len: u32,
more: bool,
) -> Option<RingChunk> {
let size = self.ring.buf_size();
let fill = self.fills.get_mut(usize::from(bid))?;
let start = if self.incremental { fill.used } else { 0 };
let end = start.checked_add(len).filter(|&end| end <= size)?;
let base = self.back.mem.base(bid)?;
fill.used = end;
fill.live += 1;
fill.done = !more;
#[cfg(test)]
{
self.out += len as usize;
}
Some(RingChunk {
back: Arc::clone(&self.back),
base,
bid,
start,
end,
owner: user_data,
len,
})
}
pub(crate) fn give_back(&mut self, bid: u16, len: u32, more: bool) {
let size = self.ring.buf_size();
let Some(fill) = self.fills.get_mut(usize::from(bid)) else {
return;
};
fill.used = fill.used.saturating_add(len).min(size);
fill.done = !more;
if self.settle(bid) {
self.ring.commit();
}
}
fn settle(&mut self, bid: u16) -> bool {
let Some(fill) = self.fills.get_mut(usize::from(bid)) else {
return false;
};
if fill.live > 0 || !fill.done {
return false;
}
*fill = Fill::default();
#[cfg(test)]
{
self.readded += 1;
}
self.ring.add(bid).is_ok()
}
pub(crate) fn take_back(&mut self, mut release: impl FnMut(u64, usize)) {
let mut any = false;
while let Some(item) = self.local.pop() {
any |= self.take_in(item, &mut release);
}
if !self.back.queue.is_empty() {
let back = Arc::clone(&self.back);
back.queue
.take_all(|item| any |= self.take_in(item, &mut release));
}
if any {
self.ring.commit();
}
}
#[inline(always)]
pub(crate) fn has_returns(&self) -> bool {
!self.local.is_empty() || !self.back.queue.is_empty()
}
pub(crate) fn take_in_now(
&mut self,
back: &Arc<Returns>,
item: Back,
mut release: impl FnMut(u64, usize),
) -> bool {
if !Arc::ptr_eq(&self.back, back) {
return false;
}
if self.take_in(item, &mut release) {
self.ring.commit();
}
true
}
#[inline(always)]
fn take_in(&mut self, item: Back, release: &mut impl FnMut(u64, usize)) -> bool {
let Back { bid, owner, len } = item;
if owner != NOT_AN_OP {
release(owner, self.charge(len));
}
#[cfg(test)]
{
self.out -= len as usize;
}
if let Some(fill) = self.fills.get_mut(usize::from(bid)) {
fill.live = fill.live.saturating_sub(1);
}
self.settle(bid)
}
pub(crate) fn keep_local(&mut self, back: &Arc<Returns>, item: Back) -> bool {
let mine = Arc::ptr_eq(&self.back, back) && self.local.len() < self.local.capacity();
if mine {
self.local.push(item);
}
mine
}
}
#[cfg(test)]
impl RecvGroup {
pub(crate) fn out(&self) -> usize {
self.out
}
pub(crate) fn readded(&self) -> usize {
self.readded
}
pub(crate) fn live(&self) -> u32 {
self.fills.iter().map(|f| f.live).sum()
}
}
impl RingChunk {
pub(crate) fn bytes(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
self.base.as_ptr().add(self.start as usize),
(self.end - self.start) as usize,
)
}
}
pub(crate) fn narrow(&mut self, start: usize, end: usize) {
let (start, end) = (
u32::try_from(start).unwrap_or(u32::MAX),
u32::try_from(end).unwrap_or(u32::MAX),
);
self.end = end.min(self.end);
self.start = start.min(self.end);
}
pub(crate) fn consume(&mut self, n: usize) {
let n = u32::try_from(n).unwrap_or(u32::MAX);
self.start = self.start.saturating_add(n).min(self.end);
}
pub(crate) fn len(&self) -> usize {
(self.end - self.start) as usize
}
}
impl RingChunk {
pub(crate) fn charged(&self, group: &RecvGroup) -> usize {
group.charge(self.len)
}
}
impl RingChunk {
fn item(&self) -> Back {
Back {
bid: self.bid,
owner: self.owner,
len: self.len,
}
}
pub(crate) fn give_back(self, shard: Option<&super::Shard>) {
let item = self.item();
let kept = shard
.and_then(super::Shard::ring)
.is_some_and(|ring| ring.take_in_now(&self.back, item));
if !kept {
self.back.queue.push(item);
}
let chunk = core::mem::ManuallyDrop::new(self);
drop(unsafe { core::ptr::read(&chunk.back) });
}
}
impl Drop for RingChunk {
fn drop(&mut self) {
let item = self.item();
let kept = crate::rt::context::current_held()
.shard()
.and_then(crate::rt::shard::Shard::ring)
.is_some_and(|ring| ring.take_in_now(&self.back, item));
if !kept {
self.back.queue.push(item);
}
}
}
impl core::fmt::Debug for RingChunk {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("RingChunk")
.field("bid", &self.bid)
.field("len", &self.len())
.finish()
}
}