use std::sync::Arc;
use std::time::Duration;
use super::super::{IoOp, Shared};
use super::{ExitOp, FILE_TAG, FileOp, IGNORED, RecvGroup, RingOp, ShardRing, Sqe};
use crate::sync::oneshot;
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)
}
impl ShardRing {
pub(crate) fn cancel_all(&self) {
let waiting: Vec<u64> = self.room.borrow_mut().drain(..).map(|(u, _)| u).collect();
for user_data in waiting {
let op = self.file_ops.borrow_mut().remove(user_data & !FILE_TAG);
if let Some(op) = op {
drop(op.complete(-libc::ECANCELED));
}
}
if self.in_flight() > 0 {
self.poller
.borrow_mut()
.push(Sqe::async_cancel_any(true).user_data(IGNORED));
}
}
pub(crate) fn shut_down(&self) -> usize {
self.cancel_all();
self.settle(|| {});
self.forget_stuck()
}
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(None, Some(left.min(Duration::from_millis(10))))
.is_err()
{
return;
}
}
}
fn forget_stuck(&self) -> usize {
let stuck: Vec<RingOp> = self.ops.borrow_mut().drain().collect();
let mut stuck_files: Vec<FileOp> = self.file_ops.borrow_mut().drain().collect();
stuck_files.iter_mut().for_each(FileOp::give_up);
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);
let group = self.recv.borrow_mut().take();
let freed = if stuck.is_empty() {
let freed = group.as_ref().map_or(0, RecvGroup::reservation);
drop(group);
freed
} else {
core::mem::forget(group);
0
};
core::mem::forget(stuck);
freed
}
}
struct Kept {
ring: ShardRing,
shared: Arc<Shared>,
}
unsafe impl Send for Kept {}
pub(crate) fn hand_off(ring: ShardRing, shared: Arc<Shared>) {
if !ring.reapable() {
ring.shut_down();
shared.close_io();
return;
}
let kept = Kept { ring, shared };
if let Some(pool) = crate::rt::runtime::blocking_pool_inside() {
pool.spawn(move || kept.keep()).detach();
return;
}
let (give, take) = oneshot::channel::<Kept>();
let started = std::thread::Builder::new()
.name("nuclei-ring-keeper".into())
.spawn(move || {
if let Ok(kept) = take.blocking_recv() {
kept.keep();
}
});
let unkept = match started {
Ok(_) => give.send(kept).err(),
Err(_) => Some(kept),
};
if let Some(kept) = unkept {
kept.ring.shut_down();
kept.shared.close_io();
}
}
impl Kept {
fn keep(self) {
let Kept { ring, shared } = self;
while ring.in_flight() > 0 {
apply_mailbox(&ring, &shared);
if ring.wait(None, None).is_err() {
ring.forget_stuck();
break;
}
}
drop(ring);
shared.close_io();
}
}
fn apply_mailbox(ring: &ShardRing, shared: &Shared) {
shared.io_ops.take_all(|op| match op {
IoOp::RingDetach(slot, out, lease) => ring.detach(slot, out, lease),
IoOp::RingCancel(user_data) => {
ring.cancel(user_data);
}
other => drop(other),
});
}