implement index pool auto clean on limits overflow

This commit is contained in:
yggverse 2025-06-28 20:12:57 +03:00
parent 56830be4a9
commit 9643adbe99
4 changed files with 69 additions and 19 deletions

View file

@ -23,8 +23,11 @@ impl Session {
debug: Debug::init(config)?, debug: Debug::init(config)?,
public: Public::init(config)?, public: Public::init(config)?,
request: Request::init( request: Request::init(
// do not init `Connection` event if its features not in use // do not int request collector if its features not in use
template.welcome.contains("{hosts}") || template.welcome.contains("{hits}"), template.welcome.contains("{hosts}")
|| template.welcome.contains("{hits}")
|| template.index.contains("{hosts}")
|| template.index.contains("{hits}"),
), ),
template, template,
}) })

View file

@ -7,22 +7,69 @@ use std::{
sync::RwLock, sync::RwLock,
}; };
/// Collect peer requests to print stats and visitors count /// Collect peer requests for stats and visitors count
pub struct Request(Option<RwLock<HashMap<IpAddr, Vec<Query>>>>); pub struct Request {
index: Option<RwLock<HashMap<IpAddr, Vec<Query>>>>,
// prevent log file overflow by recording error events once
is_max_peers_reported: RwLock<bool>,
is_max_peer_queries_reported: RwLock<bool>,
}
impl Request { impl Request {
pub fn init(is_enabled: bool) -> Self { pub fn init(is_enabled: bool) -> Self {
if is_enabled { Self {
Self(Some(RwLock::new(HashMap::with_capacity(100)))) index: if is_enabled {
} else { Some(RwLock::new(HashMap::with_capacity(100)))
Self(None) } 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) { pub fn add(&self, peer: &SocketAddr, query: &str) {
if let Some(ref this) = self.0 { if let Some(ref this) = self.index {
this.write() let mut index = this.write().unwrap();
.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()) .entry(peer.ip())
.and_modify(|c| c.push(Query::new(query))) .and_modify(|c| c.push(Query::new(query)))
.or_insert(vec![Query::new(query)]); .or_insert(vec![Query::new(query)]);
@ -30,8 +77,8 @@ impl Request {
} }
pub fn count(&self) -> usize { pub fn count(&self) -> usize {
if let Some(ref this) = self.0 { if let Some(ref i) = self.index {
this.read().unwrap().len() i.read().unwrap().len()
} else { } else {
0 0
} }
@ -39,8 +86,8 @@ impl Request {
pub fn total(&self, query_prefix: Option<&str>) -> usize { pub fn total(&self, query_prefix: Option<&str>) -> usize {
let mut t = 0; let mut t = 0;
if let Some(ref this) = self.0 { if let Some(ref i) = self.index {
for queries in this.read().unwrap().values() { for queries in i.read().unwrap().values() {
match query_prefix { match query_prefix {
Some(p) => { Some(p) => {
for q in queries { for q in queries {

View file

@ -1,14 +1,14 @@
use std::time::SystemTime; use chrono::{DateTime, Local};
pub struct Query { pub struct Query {
pub time: SystemTime, pub time: DateTime<Local>,
pub value: String, pub value: String,
} }
impl Query { impl Query {
pub fn new(value: &str) -> Self { pub fn new(value: &str) -> Self {
Self { Self {
time: SystemTime::now(), time: Local::now(),
value: value.to_string(), value: value.to_string(),
} }
} }

View file

@ -1,8 +1,8 @@
pub struct Template { pub struct Template {
access_denied: Vec<u8>, access_denied: Vec<u8>,
index: String,
internal_server_error: Vec<u8>, internal_server_error: Vec<u8>,
not_found: Vec<u8>, not_found: Vec<u8>,
pub index: String,
pub welcome: String, pub welcome: String,
} }