use std::collections::VecDeque;
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Weak};
use std::time::Instant;
use parking_lot::RwLock;
use rustc_hash::FxHashMap;
pub const CLEANUP_INTERVAL_MS: u64 = 5 * 60 * 1000;
pub trait CleanableNode: Sized + Send + Sync + 'static {
type ChildKey: Copy + Eq + Hash + Send + Sync + 'static;
fn has_any_workers(&self) -> bool;
fn children(&self) -> &FxHashMap<Self::ChildKey, Arc<RwLock<Self>>>;
fn remove_child(&mut self, key: &Self::ChildKey);
}
pub struct CleanupState {
clock_origin: Instant,
last_cleanup_elapsed_ms: AtomicU64,
scheduled: AtomicBool,
}
impl CleanupState {
pub fn new() -> Self {
Self {
clock_origin: Instant::now(),
last_cleanup_elapsed_ms: AtomicU64::new(0),
scheduled: AtomicBool::new(false),
}
}
pub fn elapsed_ms(&self) -> u64 {
self.clock_origin.elapsed().as_millis() as u64
}
pub fn try_schedule(&self) -> bool {
let now_ms = self.elapsed_ms();
let last_ms = self.last_cleanup_elapsed_ms.load(Ordering::Relaxed);
if now_ms.saturating_sub(last_ms) < CLEANUP_INTERVAL_MS {
return false;
}
self.scheduled
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
pub fn cancel(&self) {
self.scheduled.store(false, Ordering::Release);
}
}
impl Default for CleanupState {
fn default() -> Self {
Self::new()
}
}
pub struct CleanupGuard<'a> {
state: &'a CleanupState,
completed_elapsed_ms: Option<u64>,
}
impl<'a> CleanupGuard<'a> {
pub fn new(state: &'a CleanupState) -> Self {
Self {
state,
completed_elapsed_ms: None,
}
}
pub fn mark_completed(&mut self) {
self.completed_elapsed_ms = Some(self.state.elapsed_ms());
}
}
impl Drop for CleanupGuard<'_> {
fn drop(&mut self) {
if let Some(elapsed_ms) = self.completed_elapsed_ms {
self.state
.last_cleanup_elapsed_ms
.store(elapsed_ms, Ordering::Relaxed);
}
self.state.scheduled.store(false, Ordering::Release);
}
}
struct CleanupEdge<N: CleanableNode> {
parent: Weak<RwLock<N>>,
key: N::ChildKey,
child: Weak<RwLock<N>>,
}
pub fn sweep_stale_children<N: CleanableNode>(root: &Arc<RwLock<N>>) {
let mut queue: VecDeque<Arc<RwLock<N>>> = VecDeque::from([root.clone()]);
let mut edges: Vec<CleanupEdge<N>> = Vec::new();
while let Some(parent) = queue.pop_front() {
let guard = parent.read();
for (&key, child) in guard.children() {
queue.push_back(child.clone());
edges.push(CleanupEdge {
parent: Arc::downgrade(&parent),
key,
child: Arc::downgrade(child),
});
}
}
for edge in edges.into_iter().rev() {
let (Some(parent), Some(child)) = (edge.parent.upgrade(), edge.child.upgrade()) else {
continue;
};
let mut parent_guard = parent.write();
let still_attached = parent_guard
.children()
.get(&edge.key)
.is_some_and(|current| Arc::ptr_eq(current, &child));
if !still_attached {
continue;
}
let Some(child_guard) = child.try_write() else {
continue;
};
if child_guard.has_any_workers() || !child_guard.children().is_empty() {
continue;
}
if Arc::strong_count(&child) != 2 {
continue;
}
parent_guard.remove_child(&edge.key);
drop(child_guard);
}
}