fix(rustproxy-http,rustproxy-metrics): fix domain-scoped request host detection and harden connection metrics cleanup
This commit is contained in:
@@ -144,10 +144,14 @@ impl FailureState {
|
||||
}
|
||||
|
||||
fn all_expired(&self) -> bool {
|
||||
let h2_expired = self.h2.as_ref()
|
||||
let h2_expired = self
|
||||
.h2
|
||||
.as_ref()
|
||||
.map(|r| r.failed_at.elapsed() >= r.cooldown)
|
||||
.unwrap_or(true);
|
||||
let h3_expired = self.h3.as_ref()
|
||||
let h3_expired = self
|
||||
.h3
|
||||
.as_ref()
|
||||
.map(|r| r.failed_at.elapsed() >= r.cooldown)
|
||||
.unwrap_or(true);
|
||||
h2_expired && h3_expired
|
||||
@@ -355,9 +359,13 @@ impl ProtocolCache {
|
||||
|
||||
let record = entry.get_mut(protocol);
|
||||
let (consecutive, new_cooldown) = match record {
|
||||
Some(existing) if existing.failed_at.elapsed() < existing.cooldown.saturating_mul(2) => {
|
||||
Some(existing)
|
||||
if existing.failed_at.elapsed() < existing.cooldown.saturating_mul(2) =>
|
||||
{
|
||||
// Still within the "recent" window — escalate
|
||||
let c = existing.consecutive_failures.saturating_add(1)
|
||||
let c = existing
|
||||
.consecutive_failures
|
||||
.saturating_add(1)
|
||||
.min(PROTOCOL_FAILURE_ESCALATION_CAP);
|
||||
(c, escalate_cooldown(c))
|
||||
}
|
||||
@@ -394,8 +402,13 @@ impl ProtocolCache {
|
||||
if protocol == DetectedProtocol::H1 {
|
||||
return false;
|
||||
}
|
||||
self.failures.get(key)
|
||||
.and_then(|entry| entry.get(protocol).map(|r| r.failed_at.elapsed() < r.cooldown))
|
||||
self.failures
|
||||
.get(key)
|
||||
.and_then(|entry| {
|
||||
entry
|
||||
.get(protocol)
|
||||
.map(|r| r.failed_at.elapsed() < r.cooldown)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
@@ -464,19 +477,18 @@ impl ProtocolCache {
|
||||
|
||||
/// Snapshot all non-expired cache entries for metrics/UI display.
|
||||
pub fn snapshot(&self) -> Vec<ProtocolCacheEntry> {
|
||||
self.cache.iter()
|
||||
self.cache
|
||||
.iter()
|
||||
.filter(|entry| entry.value().last_accessed_at.elapsed() < PROTOCOL_CACHE_TTL)
|
||||
.map(|entry| {
|
||||
let key = entry.key();
|
||||
let val = entry.value();
|
||||
let failure_info = self.failures.get(key);
|
||||
|
||||
let (h2_sup, h2_cd, h2_cons) = Self::suppression_info(
|
||||
failure_info.as_deref().and_then(|f| f.h2.as_ref()),
|
||||
);
|
||||
let (h3_sup, h3_cd, h3_cons) = Self::suppression_info(
|
||||
failure_info.as_deref().and_then(|f| f.h3.as_ref()),
|
||||
);
|
||||
let (h2_sup, h2_cd, h2_cons) =
|
||||
Self::suppression_info(failure_info.as_deref().and_then(|f| f.h2.as_ref()));
|
||||
let (h3_sup, h3_cd, h3_cons) =
|
||||
Self::suppression_info(failure_info.as_deref().and_then(|f| f.h3.as_ref()));
|
||||
|
||||
ProtocolCacheEntry {
|
||||
host: key.host.clone(),
|
||||
@@ -507,7 +519,13 @@ impl ProtocolCache {
|
||||
/// Insert a protocol detection result with an optional H3 port.
|
||||
/// Logs protocol transitions when overwriting an existing entry.
|
||||
/// No suppression check — callers must check before calling.
|
||||
fn insert_internal(&self, key: ProtocolCacheKey, protocol: DetectedProtocol, h3_port: Option<u16>, reason: &str) {
|
||||
fn insert_internal(
|
||||
&self,
|
||||
key: ProtocolCacheKey,
|
||||
protocol: DetectedProtocol,
|
||||
h3_port: Option<u16>,
|
||||
reason: &str,
|
||||
) {
|
||||
// Check for existing entry to log protocol transitions
|
||||
if let Some(existing) = self.cache.get(&key) {
|
||||
if existing.protocol != protocol {
|
||||
@@ -522,7 +540,9 @@ impl ProtocolCache {
|
||||
|
||||
// Evict oldest entry if at capacity
|
||||
if self.cache.len() >= PROTOCOL_CACHE_MAX_ENTRIES && !self.cache.contains_key(&key) {
|
||||
let oldest = self.cache.iter()
|
||||
let oldest = self
|
||||
.cache
|
||||
.iter()
|
||||
.min_by_key(|entry| entry.value().last_accessed_at)
|
||||
.map(|entry| entry.key().clone());
|
||||
if let Some(oldest_key) = oldest {
|
||||
@@ -531,13 +551,16 @@ impl ProtocolCache {
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
self.cache.insert(key, CachedEntry {
|
||||
protocol,
|
||||
detected_at: now,
|
||||
last_accessed_at: now,
|
||||
last_probed_at: now,
|
||||
h3_port,
|
||||
});
|
||||
self.cache.insert(
|
||||
key,
|
||||
CachedEntry {
|
||||
protocol,
|
||||
detected_at: now,
|
||||
last_accessed_at: now,
|
||||
last_probed_at: now,
|
||||
h3_port,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Reduce a failure record's remaining cooldown to `target`, if it currently
|
||||
@@ -582,26 +605,34 @@ impl ProtocolCache {
|
||||
interval.tick().await;
|
||||
|
||||
// Clean expired cache entries (sliding TTL based on last_accessed_at)
|
||||
let expired: Vec<ProtocolCacheKey> = cache.iter()
|
||||
let expired: Vec<ProtocolCacheKey> = cache
|
||||
.iter()
|
||||
.filter(|entry| entry.value().last_accessed_at.elapsed() >= PROTOCOL_CACHE_TTL)
|
||||
.map(|entry| entry.key().clone())
|
||||
.collect();
|
||||
|
||||
if !expired.is_empty() {
|
||||
debug!("Protocol cache cleanup: removing {} expired entries", expired.len());
|
||||
debug!(
|
||||
"Protocol cache cleanup: removing {} expired entries",
|
||||
expired.len()
|
||||
);
|
||||
for key in expired {
|
||||
cache.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean fully-expired failure entries
|
||||
let expired_failures: Vec<ProtocolCacheKey> = failures.iter()
|
||||
let expired_failures: Vec<ProtocolCacheKey> = failures
|
||||
.iter()
|
||||
.filter(|entry| entry.value().all_expired())
|
||||
.map(|entry| entry.key().clone())
|
||||
.collect();
|
||||
|
||||
if !expired_failures.is_empty() {
|
||||
debug!("Protocol cache cleanup: removing {} expired failure entries", expired_failures.len());
|
||||
debug!(
|
||||
"Protocol cache cleanup: removing {} expired failure entries",
|
||||
expired_failures.len()
|
||||
);
|
||||
for key in expired_failures {
|
||||
failures.remove(&key);
|
||||
}
|
||||
@@ -609,7 +640,8 @@ impl ProtocolCache {
|
||||
|
||||
// Safety net: cap failures map at 2× max entries
|
||||
if failures.len() > PROTOCOL_CACHE_MAX_ENTRIES * 2 {
|
||||
let oldest: Vec<ProtocolCacheKey> = failures.iter()
|
||||
let oldest: Vec<ProtocolCacheKey> = failures
|
||||
.iter()
|
||||
.filter(|e| e.value().all_expired())
|
||||
.map(|e| e.key().clone())
|
||||
.take(failures.len() - PROTOCOL_CACHE_MAX_ENTRIES)
|
||||
|
||||
Reference in New Issue
Block a user