use std::{
io, mem,
os::fd,
process, ptr,
sync::{self, atomic, mpsc},
thread,
};
use crate::{
backend::{
bound,
kqueue::{self, engine::event, errno, file},
},
driver::{self, flight},
};
mod submission;
pub(crate) use submission::Submission;
#[derive(Clone, Copy)]
#[repr(transparent)]
struct TransferKey(flight::raw::Echo);
struct Job {
submission: file::Submission,
key: TransferKey,
}
#[derive(Clone, Copy)]
enum Priority {
File,
Kevent,
}
#[derive(Clone, Copy)]
struct Wake {
kq: fd::RawFd,
}
pub(in crate::backend::kqueue) struct Lane {
jobs: Option<mpsc::SyncSender<Job>>,
completions: mpsc::Receiver<submission::Completion>,
ready: sync::Arc<atomic::AtomicUsize>,
worker: Option<thread::JoinHandle<()>>,
inflight: usize,
capacity: usize,
priority: Priority,
}
const _: () = {
assert!(mem::size_of::<TransferKey>() == mem::size_of::<flight::raw::Echo>());
assert!(mem::align_of::<TransferKey>() == mem::align_of::<flight::raw::Echo>());
assert!(mem::size_of::<Priority>() == 1);
};
unsafe impl Send for TransferKey {}
unsafe impl Send for Job {}
impl Job {
fn execute(self) -> submission::Completion {
self.submission.execute(self.key.0)
}
}
impl Priority {
fn starts_file(self) -> bool {
matches!(self, Self::File)
}
fn alternate(self) -> Self {
match self {
Self::File => Self::Kevent,
Self::Kevent => Self::File,
}
}
}
impl Wake {
fn trigger(self) {
let change = libc::kevent {
ident: kqueue::WAKE_IDENT,
filter: libc::EVFILT_USER,
flags: libc::EV_ENABLE,
fflags: libc::NOTE_TRIGGER,
data: 0,
udata: ptr::null_mut(),
};
loop {
let result =
unsafe { libc::kevent(self.kq, &change, 1, ptr::null_mut(), 0, ptr::null()) };
if result == 0 {
return;
}
if errno::Errno::last().raw() != libc::EINTR {
process::abort();
}
}
}
}
impl Lane {
pub(in crate::backend::kqueue) fn new(capacity: usize, kq: fd::RawFd) -> io::Result<Self> {
if capacity == 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"dope-kqueue: file queue capacity is zero",
));
}
let (jobs, worker_jobs) = mpsc::sync_channel::<Job>(capacity);
let (worker_completions, completions) =
mpsc::sync_channel::<submission::Completion>(capacity);
let ready = sync::Arc::new(atomic::AtomicUsize::new(0));
let worker_ready = sync::Arc::clone(&ready);
let worker = thread::Builder::new()
.name("dope-kqueue-file".into())
.spawn(move || {
let wake = Wake { kq };
while let Ok(job) = worker_jobs.recv() {
let completion = job.execute();
match worker_completions.try_send(completion) {
Ok(()) => {}
Err(mpsc::TrySendError::Full(_)) => process::abort(),
Err(mpsc::TrySendError::Disconnected(_)) => return,
}
worker_ready.fetch_add(1, atomic::Ordering::Release);
wake.trigger();
}
})?;
Ok(Self {
jobs: Some(jobs),
completions,
ready,
worker: Some(worker),
inflight: 0,
capacity,
priority: Priority::File,
})
}
pub(super) fn submit<'owner, 'd: 'owner>(
&mut self,
submission: bound::Bound<'owner, 'd, file::Submission>,
) -> Result<flight::Flight<'d>, driver::SubmitError> {
if self.inflight >= self.capacity {
return Err(driver::SubmitError);
}
let Some(jobs) = &self.jobs else {
return Err(driver::SubmitError);
};
let (submission, reservation) = submission.into_parts();
let job = Job {
submission,
key: TransferKey(reservation.key()),
};
match jobs.try_send(job) {
Ok(()) => {
self.inflight += 1;
Ok(reservation.commit())
}
Err(mpsc::TrySendError::Full(_) | mpsc::TrySendError::Disconnected(_)) => {
Err(driver::SubmitError)
}
}
}
pub(in crate::backend::kqueue) fn starts_batch(&mut self) -> bool {
let starts_file = self.priority.starts_file();
self.priority = self.priority.alternate();
starts_file
}
pub(in crate::backend::kqueue) fn ready_count(&self) -> usize {
self.ready.load(atomic::Ordering::Acquire)
}
pub(in crate::backend::kqueue) fn pop(&mut self) -> Option<event::Completion> {
if self.ready_count() == 0 {
return None;
}
let completion = match self.completions.try_recv() {
Ok(completion) => completion,
Err(mpsc::TryRecvError::Empty | mpsc::TryRecvError::Disconnected) => process::abort(),
};
let previous = self.ready.fetch_sub(1, atomic::Ordering::AcqRel);
if previous == 0 || self.inflight == 0 {
process::abort();
}
self.inflight -= 1;
Some(completion.into_event())
}
pub(in crate::backend::kqueue) fn shutdown(&mut self) {
drop(self.jobs.take());
let Some(worker) = self.worker.take() else {
return;
};
if worker.join().is_err() {
process::abort();
}
}
}
impl Drop for Lane {
fn drop(&mut self) {
self.shutdown();
while let Some(completion) = self.pop() {
drop(completion);
}
}
}