From 9643adbe9906eec6ea768d2df53a9e4f79dc102a Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 28 Jun 2025 20:12:57 +0300 Subject: [PATCH] implement index pool auto clean on limits overflow --- src/session.rs | 7 +++- src/session/request.rs | 73 +++++++++++++++++++++++++++++------- src/session/request/query.rs | 6 +-- src/session/template.rs | 2 +- 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/session.rs b/src/session.rs index 3091b73..4fd5271 100644 --- a/src/session.rs +++ b/src/session.rs @@ -23,8 +23,11 @@ impl Session { debug: Debug::init(config)?, public: Public::init(config)?, request: Request::init( - // do not init `Connection` event if its features not in use - template.welcome.contains("{hosts}") || template.welcome.contains("{hits}"), + // do not int request collector if its features not in use + template.welcome.contains("{hosts}") + || template.welcome.contains("{hits}") + || template.index.contains("{hosts}") + || template.index.contains("{hits}"), ), template, }) diff --git a/src/session/request.rs b/src/session/request.rs index 5b9fd21..7afc84b 100644 --- a/src/session/request.rs +++ b/src/session/request.rs @@ -7,22 +7,69 @@ use std::{ sync::RwLock, }; -/// Collect peer requests to print stats and visitors count -pub struct Request(Option>>>); +/// Collect peer requests for stats and visitors count +pub struct Request { + index: Option>>>, + // prevent log file overflow by recording error events once + is_max_peers_reported: RwLock, + is_max_peer_queries_reported: RwLock, +} impl Request { pub fn init(is_enabled: bool) -> Self { - if is_enabled { - Self(Some(RwLock::new(HashMap::with_capacity(100)))) - } else { - Self(None) + Self { + index: if is_enabled { + Some(RwLock::new(HashMap::with_capacity(100))) + } else { + None + }, + is_max_peers_reported: RwLock::new(false), + is_max_peer_queries_reported: RwLock::new(false), } } pub fn add(&self, peer: &SocketAddr, query: &str) { - if let Some(ref this) = self.0 { - this.write() - .unwrap() + if let Some(ref this) = self.index { + let mut index = this.write().unwrap(); + + // Critical limits to forcefully free one memory slot(s) for the new record + // * the query len is already limited by the read buffer (1024 bytes * LIMIT) + // * make it optional @TODO + const INDEX_MAX_PEERS: usize = 1000; + const INDEX_MAX_PEER_QUERIES: usize = 1000; + + if index.len() >= INDEX_MAX_PEERS { + let mut r = self.is_max_peers_reported.write().unwrap(); + if !*r { + *r = true; + eprintln!("Max peers index limit ({INDEX_MAX_PEERS}) reached"); + } + let k = *index.keys().next().unwrap(); + index.remove(&k); // * there is no difference which one key to free for the HashMap + } + for queries in index.values_mut() { + if queries.len() >= INDEX_MAX_PEER_QUERIES { + let mut r = self.is_max_peer_queries_reported.write().unwrap(); + if !*r { + *r = true; + eprintln!( + "Max queries limit ({INDEX_MAX_PEER_QUERIES}) reached for `{peer}`" + ); + } + queries.truncate(INDEX_MAX_PEER_QUERIES - 1) // free last slot for one query + } + } + + // Cleanup deprecated records + // * this is expensive for each request, use schedule instead @TODO + let d = chrono::Local::now().date_naive(); + for queries in index.values_mut() { + queries.retain(|q| q.time.date_naive() == d) + } + index.retain(|_, q| !q.is_empty()); + + // handle new record + index .entry(peer.ip()) .and_modify(|c| c.push(Query::new(query))) .or_insert(vec![Query::new(query)]); @@ -30,8 +77,8 @@ impl Request { } pub fn count(&self) -> usize { - if let Some(ref this) = self.0 { - this.read().unwrap().len() + if let Some(ref i) = self.index { + i.read().unwrap().len() } else { 0 } @@ -39,8 +86,8 @@ impl Request { pub fn total(&self, query_prefix: Option<&str>) -> usize { let mut t = 0; - if let Some(ref this) = self.0 { - for queries in this.read().unwrap().values() { + if let Some(ref i) = self.index { + for queries in i.read().unwrap().values() { match query_prefix { Some(p) => { for q in queries { diff --git a/src/session/request/query.rs b/src/session/request/query.rs index 0d4d0f3..bb9f4e1 100644 --- a/src/session/request/query.rs +++ b/src/session/request/query.rs @@ -1,14 +1,14 @@ -use std::time::SystemTime; +use chrono::{DateTime, Local}; pub struct Query { - pub time: SystemTime, + pub time: DateTime, pub value: String, } impl Query { pub fn new(value: &str) -> Self { Self { - time: SystemTime::now(), + time: Local::now(), value: value.to_string(), } } diff --git a/src/session/template.rs b/src/session/template.rs index b7921f2..5ddf7d6 100644 --- a/src/session/template.rs +++ b/src/session/template.rs @@ -1,8 +1,8 @@ pub struct Template { access_denied: Vec, - index: String, internal_server_error: Vec, not_found: Vec, + pub index: String, pub welcome: String, }