use std::{
collections::VecDeque,
io,
os::fd::{AsRawFd, IntoRawFd, RawFd},
ptr::null_mut,
};
use io_uring::{
Submitter,
opcode::{self},
squeue::{Flags, PushError},
types::{self, CancelBuilder, Timespec},
};
use slotmap::{Key, SlotMap};
use socket2::{SockAddr, Socket};
use crate::{
buf_pool::Buffer,
buf_ring::Bgid,
event_loop::{ConnectionId, EventData, EventId, EventType},
file_store::{DiskOp, DiskOpType},
};
pub trait SubmissionQueue {
fn sync(&mut self);
fn capacity(&self) -> usize;
fn len(&self) -> usize;
fn is_full(&self) -> bool;
unsafe fn push(&mut self, entry: &io_uring::squeue::Entry) -> Result<(), PushError>;
}
impl SubmissionQueue for io_uring::SubmissionQueue<'_> {
fn sync(&mut self) {
self.sync();
}
fn capacity(&self) -> usize {
self.capacity()
}
fn len(&self) -> usize {
self.len()
}
fn is_full(&self) -> bool {
self.is_full()
}
unsafe fn push(&mut self, entry: &io_uring::squeue::Entry) -> Result<(), PushError> {
unsafe { self.push(entry) }
}
}
pub struct BackloggedSubmissionQueue<Q> {
queue: Q,
backlog: VecDeque<io_uring::squeue::Entry>,
}
impl<Q: SubmissionQueue> BackloggedSubmissionQueue<Q> {
pub fn new(queue: Q) -> Self {
Self {
queue,
backlog: Default::default(),
}
}
pub fn push(&mut self, entry: io_uring::squeue::Entry) {
unsafe {
if self.queue.push(&entry).is_err() {
log::warn!("SQ buffer full, pushing to backlog");
self.backlog.push_back(entry);
}
}
}
pub fn push_backlog(&mut self, entry: io_uring::squeue::Entry) {
self.backlog.push_back(entry);
}
pub fn sync(&mut self) {
self.queue.sync();
}
pub fn remaining(&self) -> usize {
self.queue.capacity() - self.queue.len()
}
pub fn submit_and_drain_backlog(&mut self, submitter: &Submitter<'_>) -> io::Result<()> {
loop {
if self.queue.is_full() {
match submitter.submit() {
Ok(_) => (),
Err(ref err) if err.raw_os_error() == Some(libc::EBUSY) => {
log::warn!("Ring busy")
}
Err(err) => {
log::error!("Failed ring submission, aborting: {err}");
return Err(err);
}
}
}
self.queue.sync();
if self.backlog.is_empty() {
break Ok(());
}
let sq_remaining_capacity = self.remaining();
let num_to_drain = self.backlog.len().min(sq_remaining_capacity);
for sqe in self.backlog.drain(..num_to_drain) {
unsafe {
self.queue
.push(&sqe)
.expect("SQE should never be full when clearing backlog")
}
}
}
}
}
pub fn writev_to_connection<Q: SubmissionQueue>(
conn_id: ConnectionId,
fd: RawFd,
events: &mut SlotMap<EventId, EventData>,
sq: &mut BackloggedSubmissionQueue<Q>,
buffers: Vec<Buffer>,
io_vec_offset: usize,
) {
debug_assert!(io_vec_offset <= buffers.iter().map(|buf| buf.filled_slice().len()).sum());
let mut remaining_offset = io_vec_offset as i64;
let iovecs: Vec<libc::iovec> = buffers
.iter()
.map(|buf| buf.filled_slice())
.filter_map(|buf| {
remaining_offset -= buf.len() as i64;
if remaining_offset < 0 {
let relevant_buffer_length = (-remaining_offset) as usize;
let buffer_offset = buf.len() - relevant_buffer_length;
let io_vec = libc::iovec {
iov_base: unsafe { buf.as_ptr().add(buffer_offset) as *mut _ },
iov_len: relevant_buffer_length,
};
remaining_offset = 0;
Some(io_vec)
} else {
None
}
})
.collect();
let iovecs_len = iovecs.len();
let event_id = events.insert(EventData {
typ: EventType::ConnectedWriteV {
connection_idx: conn_id,
iovecs,
io_vec_offset,
},
buffers: Some(buffers),
});
let stable_iovec_ptr = match &events[event_id].typ {
EventType::ConnectedWriteV { iovecs, .. } => iovecs.as_ptr(),
_ => unreachable!(),
};
let write_op = opcode::Writev::new(types::Fd(fd), stable_iovec_ptr, iovecs_len as u32)
.build()
.user_data(event_id.data().as_ffi());
sq.push(write_op);
}
pub fn write<Q: SubmissionQueue>(
sq: &mut BackloggedSubmissionQueue<Q>,
events: &mut SlotMap<EventId, EventData>,
socket: Socket,
addr: SockAddr,
buffer: Buffer,
) {
let fd = socket.as_raw_fd();
let buffer_slice = buffer.filled_slice();
let buffer_ptr = buffer_slice.as_ptr();
let buffer_len = buffer_slice.len();
let write_event_id = events.insert(EventData {
typ: EventType::Write {
socket,
addr,
expected_write: buffer_len,
},
buffers: Some(vec![buffer]),
});
let write_op = opcode::Write::new(types::Fd(fd), buffer_ptr, buffer_len as u32)
.build()
.user_data(write_event_id.data().as_ffi());
sq.push(write_op);
}
pub fn disk_operation<Q: SubmissionQueue>(
events: &mut SlotMap<EventId, EventData>,
sq: &mut BackloggedSubmissionQueue<Q>,
disk_op: DiskOp,
inflight_disk_ops: &mut usize,
) {
let op = match disk_op.op_type {
DiskOpType::Write => {
let write_ptr = unsafe {
disk_op
.buffer
.raw_slice()
.as_ptr()
.add(disk_op.buffer_offset)
};
let write_len = disk_op.operation_len;
let event_id = events.insert(EventData {
typ: EventType::DiskWrite {
data: disk_op.buffer,
piece_idx: disk_op.piece_idx,
#[cfg(feature = "metrics")]
scheduled: std::time::Instant::now(),
},
buffers: None,
});
opcode::Write::new(types::Fd(disk_op.fd), write_ptr, write_len as u32)
.offset(disk_op.file_offset as u64)
.build()
.user_data(event_id.data().as_ffi())
}
DiskOpType::Read {
connection_idx,
piece_offset,
} => {
let read_ptr = unsafe {
disk_op
.buffer
.raw_slice()
.as_ptr()
.add(disk_op.buffer_offset)
};
let read_len = disk_op.operation_len;
let event_id = events.insert(EventData {
typ: EventType::DiskRead {
data: disk_op.buffer,
piece_idx: disk_op.piece_idx,
connection_idx,
piece_offset,
#[cfg(feature = "metrics")]
scheduled: std::time::Instant::now(),
},
buffers: None,
});
opcode::Read::new(types::Fd(disk_op.fd), read_ptr as *mut _, read_len as u32)
.offset(disk_op.file_offset as u64)
.build()
.user_data(event_id.data().as_ffi())
}
};
*inflight_disk_ops += 1;
sq.push(op);
}
pub fn close_socket<Q: SubmissionQueue>(
sq: &mut BackloggedSubmissionQueue<Q>,
socket: Socket,
maybe_connection_idx: Option<ConnectionId>,
events: &mut SlotMap<EventId, EventData>,
) {
let fd = socket.into_raw_fd();
cancel(
sq,
events,
CancelBuilder::fd(types::Fd(fd)).all(),
Some(Flags::IO_HARDLINK),
);
let event_id = events.insert(EventData {
typ: EventType::Close {
maybe_connection_idx,
},
buffers: None,
});
let close_op = opcode::Close::new(types::Fd(fd))
.build()
.user_data(event_id.data().as_ffi());
sq.push(close_op);
}
pub fn cancel<Q: SubmissionQueue>(
sq: &mut BackloggedSubmissionQueue<Q>,
events: &mut SlotMap<EventId, EventData>,
cancel_builder: CancelBuilder,
flags: Option<Flags>,
) {
let event_id = events.insert(EventData {
typ: EventType::Cancel,
buffers: None,
});
let cancel_op = opcode::AsyncCancel2::new(cancel_builder)
.build()
.user_data(event_id.data().as_ffi());
let cancel_op = if let Some(flags) = flags {
cancel_op.flags(flags)
} else {
cancel_op
};
sq.push(cancel_op);
}
pub fn recv<Q: SubmissionQueue>(
sq: &mut BackloggedSubmissionQueue<Q>,
event_data_idx: EventId,
fd: RawFd,
bgid: Bgid,
timeout: &Timespec,
) {
log::debug!("Starting recv");
let read_op = opcode::Recv::new(types::Fd(fd), null_mut(), 0)
.buf_group(bgid)
.build()
.user_data(event_data_idx.data().as_ffi())
.flags(io_uring::squeue::Flags::BUFFER_SELECT | io_uring::squeue::Flags::IO_LINK);
let timeout_op = opcode::LinkTimeout::new(timeout)
.build()
.user_data(event_data_idx.data().as_ffi());
if sq.remaining() >= 2 {
sq.push(read_op);
sq.push(timeout_op);
sq.sync();
} else {
sq.push_backlog(read_op);
sq.push_backlog(timeout_op);
}
}
pub fn recv_multishot<Q: SubmissionQueue>(
sq: &mut BackloggedSubmissionQueue<Q>,
event_data_idx: EventId,
fd: RawFd,
bgid: Bgid,
) {
log::debug!("Starting recv multishot: {event_data_idx:?}");
let read_op = opcode::RecvMulti::new(types::Fd(fd), bgid)
.build()
.user_data(event_data_idx.data().as_ffi())
.flags(io_uring::squeue::Flags::BUFFER_SELECT);
sq.push(read_op);
}