ME: Autonomous&Lifecycle-bounded Pool Convergence

Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
Alexey
2026-10-05 06:02:08 +03:00
parent 9c31e970e6
commit 556affc63a
37 changed files with 2546 additions and 685 deletions
+108
View File
@@ -0,0 +1,108 @@
use super::*;
const HTTP_FRESH_TTL: Duration = Duration::from_secs(600);
const HTTP_MAX_AGE: Duration = Duration::from_secs(1200);
/// A successful HTTP observation has an immutable monotonic timestamp.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(in crate::transport::middle_proxy) struct HttpNatObservation {
/// Successful detector result, never updated by a failed refresh.
pub(in crate::transport::middle_proxy) ip: IpAddr,
/// Original observation time used for both freshness and bounded stale fallback.
pub(super) observed_at: tokio::time::Instant,
}
impl HttpNatObservation {
fn usable(self) -> Option<IpAddr> {
(self.observed_at.elapsed() < HTTP_MAX_AGE).then_some(self.ip)
}
fn fresh(self) -> bool {
self.observed_at.elapsed() < HTTP_FRESH_TTL
}
}
impl MePool {
/// Reads a bounded cache snapshot without discovery or data-plane waiting.
pub(in crate::transport::middle_proxy) fn cached_http_nat_ip(&self) -> Option<IpAddr> {
self.nat_runtime
.nat_ip_detected
.try_read()
.ok()
.and_then(|cache| cache.and_then(HttpNatObservation::usable))
}
/// Refreshes once per pool, retaining stale success only within its original grace period.
pub(in crate::transport::middle_proxy) async fn maybe_detect_nat_ip(
self: &Arc<Self>,
local_ip: IpAddr,
) -> Option<IpAddr> {
if self.nat_runtime.nat_ip_cfg.is_some() {
return self.nat_runtime.nat_ip_cfg;
}
if !self.nat_runtime.nat_probe
|| !local_ip.is_ipv4()
|| !(is_bogon(local_ip) || local_ip.is_loopback() || local_ip.is_unspecified())
{
return None;
}
if let Some(cached) = *self.nat_runtime.nat_ip_detected.read().await
&& cached.fresh()
{
return Some(cached.ip);
}
let mut flight = self.nat_runtime.discovery.http.clone().lock_owned().await;
let cached = *self.nat_runtime.nat_ip_detected.read().await;
if let Some(cached) = cached
&& cached.fresh()
{
return Some(cached.ip);
}
if flight.is_some_and(|until| Instant::now() < until) {
return cached.and_then(HttpNatObservation::usable);
}
let pool = self.clone();
let (send, receive) = oneshot::channel();
// Discovery owns the flight; cancellation of a handshake cannot cancel publication.
self.lifecycle
.spawn_producer(async move {
let detected = detect_public_ipv4_http(&pool.nat_runtime.http_ip_detect_urls)
.await
.map(IpAddr::V4);
if let Some(ip) = detected {
*pool.nat_runtime.nat_ip_detected.write().await = Some(HttpNatObservation {
ip,
observed_at: tokio::time::Instant::now(),
});
*flight = None;
info!(public_ip = %ip, "Auto-detected public IP for NAT translation");
} else {
*flight = Some(Instant::now() + HTTP_FAILURE_TTL);
}
let _ = send.send(());
})
.ok()?;
let _ = receive.await;
self.cached_http_nat_ip()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test(start_paused = true)]
async fn observation_age_is_not_renewed_by_reads_or_failure() {
let cached = HttpNatObservation {
ip: "91.108.56.1".parse().unwrap(),
observed_at: tokio::time::Instant::now(),
};
tokio::time::advance(HTTP_FRESH_TTL).await;
assert!(!cached.fresh());
for _ in 0..100 {
assert_eq!(cached.usable(), Some(cached.ip));
}
tokio::time::advance(HTTP_MAX_AGE - HTTP_FRESH_TTL).await;
assert_eq!(cached.usable(), None);
}
}