Files
telemt/src/ip_tracker/cleanup.rs

149 lines
5.8 KiB
Rust

use super::*;
impl UserIpTracker {
/// Queues a deferred active IP cleanup for a later async drain.
pub fn enqueue_cleanup(&self, user: String, ip: IpAddr) {
self.observe_cleanup_poison_for_tests();
let shard_idx = Self::shard_idx(&user);
let cleanup_shard = &self.cleanup_shards[shard_idx];
match cleanup_shard.queue.lock() {
Ok(mut queue) => {
let user_queue = queue.entry(user).or_default();
let count = user_queue.entry(ip).or_insert(0);
if *count == 0 {
self.cleanup_queue_len.fetch_add(1, Ordering::Relaxed);
}
*count = count.saturating_add(1);
self.cleanup_deferred_releases
.fetch_add(1, Ordering::Relaxed);
}
Err(poisoned) => {
let mut queue = poisoned.into_inner();
let user_queue = queue.entry(user.clone()).or_default();
let count = user_queue.entry(ip).or_insert(0);
if *count == 0 {
self.cleanup_queue_len.fetch_add(1, Ordering::Relaxed);
}
*count = count.saturating_add(1);
self.cleanup_deferred_releases
.fetch_add(1, Ordering::Relaxed);
cleanup_shard.queue.clear_poison();
tracing::warn!(
"UserIpTracker cleanup_queue lock poisoned; recovered and enqueued IP cleanup for {} ({})",
user,
ip
);
}
}
}
#[cfg(test)]
pub(crate) fn cleanup_queue_len_for_tests(&self) -> usize {
self.cleanup_queue_len.load(Ordering::Relaxed) as usize
}
#[cfg(test)]
pub(crate) fn cleanup_queue_mutex_for_tests(
&self,
) -> Arc<Mutex<HashMap<(String, IpAddr), usize>>> {
Arc::clone(&self.cleanup_queue_poison_probe)
}
pub(crate) async fn drain_cleanup_queue(&self) {
if self.cleanup_queue_len.load(Ordering::Relaxed) == 0 {
return;
}
for shard_idx in 0..USER_IP_TRACKER_SHARDS {
self.drain_cleanup_shard(shard_idx).await;
}
}
pub(super) async fn drain_cleanup_for_user(&self, user: &str) {
if self.cleanup_queue_len.load(Ordering::Relaxed) == 0 {
return;
}
let shard_idx = Self::shard_idx(user);
let cleanup_shard = &self.cleanup_shards[shard_idx];
let to_remove = match cleanup_shard.queue.lock() {
Ok(mut queue) => queue.remove(user).unwrap_or_default(),
Err(poisoned) => {
let mut queue = poisoned.into_inner();
let drained = queue.remove(user).unwrap_or_default();
cleanup_shard.queue.clear_poison();
drained
}
};
if to_remove.is_empty() {
return;
}
self.cleanup_queue_len
.fetch_sub(to_remove.len() as u64, Ordering::Relaxed);
let mut shard = self.shards[shard_idx].write().await;
let mut removed_active_entries = 0usize;
for (ip, pending_count) in to_remove {
removed_active_entries = removed_active_entries.saturating_add(
Self::apply_active_cleanup(&mut shard.active_ips, user, ip, pending_count),
);
}
Self::decrement_counter(&self.active_entry_count, removed_active_entries);
}
pub(super) async fn drain_cleanup_shard(&self, shard_idx: usize) {
let Ok(_drain_guard) = self.cleanup_drain_locks[shard_idx].try_lock() else {
return;
};
let cleanup_shard = &self.cleanup_shards[shard_idx];
let to_remove = {
match cleanup_shard.queue.lock() {
Ok(mut queue) => {
if queue.is_empty() {
return;
}
let mut drained =
HashMap::with_capacity(queue.len().min(CLEANUP_DRAIN_BATCH_LIMIT));
for _ in 0..CLEANUP_DRAIN_BATCH_LIMIT {
let Some((user, ip, count)) = Self::pop_one_cleanup(&mut queue) else {
break;
};
self.cleanup_queue_len.fetch_sub(1, Ordering::Relaxed);
drained.insert((user, ip), count);
}
drained
}
Err(poisoned) => {
let mut queue = poisoned.into_inner();
if queue.is_empty() {
cleanup_shard.queue.clear_poison();
return;
}
let mut drained =
HashMap::with_capacity(queue.len().min(CLEANUP_DRAIN_BATCH_LIMIT));
for _ in 0..CLEANUP_DRAIN_BATCH_LIMIT {
let Some((user, ip, count)) = Self::pop_one_cleanup(&mut queue) else {
break;
};
self.cleanup_queue_len.fetch_sub(1, Ordering::Relaxed);
drained.insert((user, ip), count);
}
cleanup_shard.queue.clear_poison();
drained
}
}
};
drop(_drain_guard);
if to_remove.is_empty() {
return;
}
let mut shard = self.shards[shard_idx].write().await;
let mut removed_active_entries = 0usize;
for ((user, ip), pending_count) in to_remove {
removed_active_entries = removed_active_entries.saturating_add(
Self::apply_active_cleanup(&mut shard.active_ips, &user, ip, pending_count),
);
}
Self::decrement_counter(&self.active_entry_count, removed_active_entries);
}
}