Эх сурвалжийг харах

net: Bound host registry retention

x 3 долоо хоног өмнө
parent
commit
b0fae1452e
1 өөрчлөгдсөн 108 нэмэгдсэн , 4 устгасан
  1. 108 4
      src/net/hosts.rs

+ 108 - 4
src/net/hosts.rs

@@ -80,6 +80,16 @@ const BLACKLIST_MAX_LEN: usize = 10000;
 /// 24 hours is appropriate for long-running daemons.
 const REGISTRY_PRUNE_AGE_SECS: u64 = 86400;
 
+/// Maximum number of lifecycle entries retained under normal operation.
+///
+/// Only `Free` entries are evicted. If active lifecycle states alone exceed this
+/// limit, they are retained until their operations finish.
+const REGISTRY_MAX_LEN: usize = 20000;
+
+/// Capacity pruning removes enough old `Free` entries to avoid sorting the
+/// registry on every subsequent insertion once it reaches the high-water mark.
+const REGISTRY_PRUNE_TARGET_LEN: usize = 18000;
+
 pub type HostsPtr = Arc<Hosts>;
 
 /// Mutually exclusive states for host lifecycle management.
@@ -653,6 +663,20 @@ impl Hosts {
 
         if let Ok(ref state) = result {
             registry.insert(addr, state.clone());
+
+            if registry.len() > REGISTRY_MAX_LEN {
+                let pruned = Self::prune_registry_capacity(
+                    &mut registry,
+                    REGISTRY_MAX_LEN,
+                    REGISTRY_PRUNE_TARGET_LEN,
+                );
+                if pruned > 0 {
+                    debug!(
+                        target: "net::hosts::try_register",
+                        "Pruned {pruned} entries after reaching registry capacity",
+                    );
+                }
+            }
         }
 
         result
@@ -668,8 +692,10 @@ impl Hosts {
 
     /// Prune stale entries from the registry.
     ///
-    /// Removes hosts that have been in `Free` state longer than `REGISTRY_PRUNE_AGE_SECS`.
-    /// This prevents unbounded growth of the registry over long-running sessions.
+    /// Removes hosts that have been in `Free` state longer than
+    /// `REGISTRY_PRUNE_AGE_SECS`, then enforces `REGISTRY_MAX_LEN`. Capacity
+    /// pruning removes the oldest `Free` entries first and never evicts active
+    /// lifecycle states.
     ///
     /// Returns the number of entries pruned.
     pub fn prune_registry(&self) -> usize {
@@ -691,10 +717,43 @@ impl Hosts {
             true
         });
 
+        Self::prune_registry_capacity(&mut registry, REGISTRY_MAX_LEN, REGISTRY_PRUNE_TARGET_LEN);
+
         let pruned = before - registry.len();
         if pruned > 0 {
-            debug!(target: "net::hosts::prune_registry", "Pruned {pruned} stale entries");
+            debug!(target: "net::hosts::prune_registry", "Pruned {pruned} registry entries");
+        }
+        pruned
+    }
+
+    fn prune_registry_capacity(
+        registry: &mut HashMap<Url, HostState>,
+        max_len: usize,
+        target_len: usize,
+    ) -> usize {
+        debug_assert!(target_len <= max_len);
+
+        if registry.len() <= max_len {
+            return 0
+        }
+
+        let remove_count = registry.len() - target_len;
+        let mut free_entries = registry
+            .iter()
+            .filter_map(|(url, state)| match state {
+                HostState::Free(age) => Some((url.clone(), *age)),
+                _ => None,
+            })
+            .collect::<Vec<_>>();
+        free_entries.sort_unstable_by_key(|(_, age)| *age);
+
+        let mut pruned = 0;
+        for (url, _) in free_entries.into_iter().take(remove_count) {
+            if registry.remove(&url).is_some() {
+                pruned += 1;
+            }
         }
+
         pruned
     }
 
@@ -1369,7 +1428,7 @@ mod tests {
     }
 
     #[test]
-    fn test_prune_registry() {
+    fn test_prune_registry_by_age() {
         let hosts = make_hosts();
         let now = UNIX_EPOCH.elapsed().unwrap().as_secs();
 
@@ -1397,4 +1456,49 @@ mod tests {
         assert!(registry.contains_key(&new_url));
         assert!(registry.contains_key(&active_url));
     }
+
+    #[test]
+    fn test_prune_registry_by_capacity() {
+        let hosts = make_hosts();
+        let oldest_connect = Url::parse("tcp://oldest-connect.example.com:123").unwrap();
+        let oldest_refine = Url::parse("tcp://oldest-refine.example.com:123").unwrap();
+        let recent = Url::parse("tcp://recent.example.com:123").unwrap();
+        let connect = Url::parse("tcp://connect.example.com:123").unwrap();
+        let refine = Url::parse("tcp://refine.example.com:123").unwrap();
+
+        {
+            let mut registry = hosts.registry.lock();
+            registry.insert(oldest_connect.clone(), HostState::Free(1));
+            registry.insert(oldest_refine.clone(), HostState::Free(2));
+            registry.insert(recent.clone(), HostState::Free(3));
+            registry.insert(connect.clone(), HostState::Connect);
+            registry.insert(refine.clone(), HostState::Refine);
+
+            let pruned = Hosts::prune_registry_capacity(&mut registry, 4, 3);
+            assert_eq!(pruned, 2);
+            assert_eq!(registry.len(), 3);
+            assert!(!registry.contains_key(&oldest_connect));
+            assert!(!registry.contains_key(&oldest_refine));
+            assert!(registry.contains_key(&recent));
+            assert!(matches!(registry.get(&connect), Some(HostState::Connect)));
+            assert!(matches!(registry.get(&refine), Some(HostState::Refine)));
+        }
+
+        assert!(hosts.try_register(oldest_connect, HostState::Connect).is_ok());
+        assert!(hosts.try_register(oldest_refine, HostState::Refine).is_ok());
+    }
+
+    #[test]
+    fn test_unregister_repeated_url_reuses_registry_entry() {
+        let hosts = make_hosts();
+        let url = Url::parse("tcp://repeated.example.com:123").unwrap();
+
+        hosts.registry.lock().insert(url.clone(), HostState::Free(0));
+        hosts.unregister(&url).unwrap();
+        hosts.unregister(&url).unwrap();
+
+        let registry = hosts.registry.lock();
+        assert_eq!(registry.len(), 1);
+        assert!(matches!(registry.get(&url), Some(HostState::Free(age)) if *age > 0));
+    }
 }