feat(smart-proxy): add UDP transport support with QUIC/HTTP3 routing and datagram handler relay
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
//! Backend connection pool for HTTP/1.1 and HTTP/2.
|
||||
//! Backend connection pool for HTTP/1.1, HTTP/2, and HTTP/3 (QUIC).
|
||||
//!
|
||||
//! Reuses idle keep-alive connections to avoid per-request TCP+TLS handshakes.
|
||||
//! HTTP/2 connections are multiplexed (clone the sender for each request).
|
||||
//! HTTP/2 and HTTP/3 connections are multiplexed (clone the sender / share the connection).
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
@@ -19,9 +19,17 @@ const IDLE_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
/// Background eviction interval.
|
||||
const EVICTION_INTERVAL: Duration = Duration::from_secs(30);
|
||||
/// Maximum age for pooled HTTP/2 connections before proactive eviction.
|
||||
/// Prevents staleness from backends that close idle connections (e.g. nginx GOAWAY).
|
||||
/// 120s is well within typical server GOAWAY windows (nginx: ~60s idle, envoy: ~60s).
|
||||
const MAX_H2_AGE: Duration = Duration::from_secs(120);
|
||||
/// Maximum age for pooled QUIC/HTTP/3 connections.
|
||||
const MAX_H3_AGE: Duration = Duration::from_secs(120);
|
||||
|
||||
/// Protocol for pool key discrimination.
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum PoolProtocol {
|
||||
H1,
|
||||
H2,
|
||||
H3,
|
||||
}
|
||||
|
||||
/// Identifies a unique backend endpoint.
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
@@ -29,7 +37,7 @@ pub struct PoolKey {
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub use_tls: bool,
|
||||
pub h2: bool,
|
||||
pub protocol: PoolProtocol,
|
||||
}
|
||||
|
||||
/// An idle HTTP/1.1 sender with a timestamp for eviction.
|
||||
@@ -47,13 +55,22 @@ struct PooledH2 {
|
||||
generation: u64,
|
||||
}
|
||||
|
||||
/// A pooled QUIC/HTTP/3 connection (multiplexed like H2).
|
||||
pub struct PooledH3 {
|
||||
pub connection: quinn::Connection,
|
||||
pub created_at: Instant,
|
||||
pub generation: u64,
|
||||
}
|
||||
|
||||
/// Backend connection pool.
|
||||
pub struct ConnectionPool {
|
||||
/// HTTP/1.1 idle connections indexed by backend key.
|
||||
h1_pool: Arc<DashMap<PoolKey, Vec<IdleH1>>>,
|
||||
/// HTTP/2 multiplexed connections indexed by backend key.
|
||||
h2_pool: Arc<DashMap<PoolKey, PooledH2>>,
|
||||
/// Monotonic generation counter for H2 pool entries.
|
||||
/// HTTP/3 (QUIC) connections indexed by backend key.
|
||||
h3_pool: Arc<DashMap<PoolKey, PooledH3>>,
|
||||
/// Monotonic generation counter for H2/H3 pool entries.
|
||||
h2_generation: AtomicU64,
|
||||
/// Handle for the background eviction task.
|
||||
eviction_handle: Option<tokio::task::JoinHandle<()>>,
|
||||
@@ -64,16 +81,19 @@ impl ConnectionPool {
|
||||
pub fn new() -> Self {
|
||||
let h1_pool: Arc<DashMap<PoolKey, Vec<IdleH1>>> = Arc::new(DashMap::new());
|
||||
let h2_pool: Arc<DashMap<PoolKey, PooledH2>> = Arc::new(DashMap::new());
|
||||
let h3_pool: Arc<DashMap<PoolKey, PooledH3>> = Arc::new(DashMap::new());
|
||||
|
||||
let h1_clone = Arc::clone(&h1_pool);
|
||||
let h2_clone = Arc::clone(&h2_pool);
|
||||
let h3_clone = Arc::clone(&h3_pool);
|
||||
let eviction_handle = tokio::spawn(async move {
|
||||
Self::eviction_loop(h1_clone, h2_clone).await;
|
||||
Self::eviction_loop(h1_clone, h2_clone, h3_clone).await;
|
||||
});
|
||||
|
||||
Self {
|
||||
h1_pool,
|
||||
h2_pool,
|
||||
h3_pool,
|
||||
h2_generation: AtomicU64::new(0),
|
||||
eviction_handle: Some(eviction_handle),
|
||||
}
|
||||
@@ -173,10 +193,57 @@ impl ConnectionPool {
|
||||
gen
|
||||
}
|
||||
|
||||
// ── HTTP/3 (QUIC) pool methods ──
|
||||
|
||||
/// Try to get a pooled QUIC connection for the given key.
|
||||
/// QUIC connections are multiplexed — the connection is shared, not removed.
|
||||
pub fn checkout_h3(&self, key: &PoolKey) -> Option<(quinn::Connection, Duration)> {
|
||||
let entry = self.h3_pool.get(key)?;
|
||||
let pooled = entry.value();
|
||||
let age = pooled.created_at.elapsed();
|
||||
|
||||
if age >= MAX_H3_AGE {
|
||||
drop(entry);
|
||||
self.h3_pool.remove(key);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check if QUIC connection is still alive
|
||||
if pooled.connection.close_reason().is_some() {
|
||||
drop(entry);
|
||||
self.h3_pool.remove(key);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((pooled.connection.clone(), age))
|
||||
}
|
||||
|
||||
/// Register a QUIC connection in the pool. Returns the generation ID.
|
||||
pub fn register_h3(&self, key: PoolKey, connection: quinn::Connection) -> u64 {
|
||||
let gen = self.h2_generation.fetch_add(1, Ordering::Relaxed);
|
||||
self.h3_pool.insert(key, PooledH3 {
|
||||
connection,
|
||||
created_at: Instant::now(),
|
||||
generation: gen,
|
||||
});
|
||||
gen
|
||||
}
|
||||
|
||||
/// Remove a QUIC connection only if generation matches.
|
||||
pub fn remove_h3_if_generation(&self, key: &PoolKey, expected_gen: u64) {
|
||||
if let Some(entry) = self.h3_pool.get(key) {
|
||||
if entry.value().generation == expected_gen {
|
||||
drop(entry);
|
||||
self.h3_pool.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background eviction loop — runs every EVICTION_INTERVAL to remove stale connections.
|
||||
async fn eviction_loop(
|
||||
h1_pool: Arc<DashMap<PoolKey, Vec<IdleH1>>>,
|
||||
h2_pool: Arc<DashMap<PoolKey, PooledH2>>,
|
||||
h3_pool: Arc<DashMap<PoolKey, PooledH3>>,
|
||||
) {
|
||||
let mut interval = tokio::time::interval(EVICTION_INTERVAL);
|
||||
loop {
|
||||
@@ -206,6 +273,19 @@ impl ConnectionPool {
|
||||
for key in dead_h2 {
|
||||
h2_pool.remove(&key);
|
||||
}
|
||||
|
||||
// Evict dead or aged-out H3 (QUIC) connections
|
||||
let mut dead_h3 = Vec::new();
|
||||
for entry in h3_pool.iter() {
|
||||
if entry.value().connection.close_reason().is_some()
|
||||
|| entry.value().created_at.elapsed() >= MAX_H3_AGE
|
||||
{
|
||||
dead_h3.push(entry.key().clone());
|
||||
}
|
||||
}
|
||||
for key in dead_h3 {
|
||||
h3_pool.remove(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user