fix: cap concurrent GeoIP blocking threads with Semaphore

Root cause: WebSocketProvider opens 24 flow-stat WebSocket connections
simultaneously. Each connection on every refresh calls get_ipv4/ipv6_flow_data,
which calls join_all() on GeoIP lookups for every IP in the flow map. On cache
miss, each lookup calls task::spawn_blocking unconditionally — so 24 connections
× N flows × cold cache = unbounded OS thread creation.

Fix: add OwnedSemaphorePermit (MAX_CONCURRENT_DB_LOOKUPS = 8) to GeoIpService.
The permit is moved into spawn_blocking so it is held for the full duration of
the blocking call and released when the closure returns. Cached hits and private
IPs are unaffected (no semaphore needed).

https://claude.ai/code/session_01Wen51749mAnwBEfh7XmvUw
This commit is contained in:
Claude 2026-05-19 09:19:20 +00:00
parent 7fe77d235b
commit 2c0a4a8b4d
No known key found for this signature in database

View File

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use maxminddb::{geoip2, MaxMindDbError, Reader};
use tokio::sync::RwLock;
use tokio::sync::{RwLock, Semaphore};
use lru::LruCache;
use std::num::NonZeroUsize;
use tokio::task;
@ -11,9 +11,12 @@ use tokio::task;
use crate::model::geo_stats::GeoLocation;
use crate::utils::ip_address;
const MAX_CONCURRENT_DB_LOOKUPS: usize = 8;
pub struct GeoIpService {
reader: Arc<Reader<Vec<u8>>>,
cache: Arc<RwLock<LruCache<IpAddr, Option<GeoLocation>>>>,
lookup_sem: Arc<Semaphore>,
}
impl GeoIpService {
@ -33,6 +36,7 @@ impl GeoIpService {
Ok(Self {
reader: Arc::new(reader),
cache: Arc::new(RwLock::new(LruCache::new(cache_capacity))),
lookup_sem: Arc::new(Semaphore::new(MAX_CONCURRENT_DB_LOOKUPS)),
})
}
@ -55,8 +59,15 @@ impl GeoIpService {
}
}
let permit = self.lookup_sem.clone().acquire_owned().await
.map_err(|_| MaxMindDbError::InvalidDatabase {
message: "GeoIP semaphore closed".to_string(),
offset: None,
})?;
let reader = self.reader.clone();
let result = task::spawn_blocking(move || {
let _permit = permit;
Self::lookup_from_db_blocking(&reader, ip)
})
.await