use std::sync::Arc;
use std::sync::atomic::{
AtomicUsize,
Ordering,
};
use arc_swap::ArcSwap;
use quanta::Instant;
use tokio::sync::RwLock as TokioRwLock;
use super::Forwarder;
use crate::session::Session;
pub(super) struct PooledSession {
pub(super) session: Arc<Session>,
pub(super) created_at: Instant,
#[expect(
dead_code,
reason = "stored for diagnostic logging and future pod-affinity checks"
)]
pub(super) pod_uid: String,
}
pub(super) struct SessionPool {
pub(super) entries: Vec<PooledSession>,
pub(super) target_pod_uid: Option<String>,
pub(super) prefetch_in_flight: bool,
pub(super) opening_count: Arc<AtomicUsize>,
pub(super) snapshot: Arc<ArcSwap<Vec<Arc<Session>>>>,
}
impl SessionPool {
pub(super) fn new() -> Self {
Self {
entries: Vec::new(),
target_pod_uid: None,
prefetch_in_flight: false,
opening_count: Arc::new(AtomicUsize::new(0)),
snapshot: Arc::new(ArcSwap::from_pointee(Vec::new())),
}
}
pub(super) fn refresh_snapshot(&self) {
let sessions: Vec<Arc<Session>> = self
.entries
.iter()
.map(|e| Arc::clone(&e.session))
.collect();
self.snapshot.store(Arc::new(sessions));
}
pub(super) fn has_pending_or_available(&self) -> bool {
self.opening_count.load(Ordering::Relaxed) > 0
|| self
.entries
.iter()
.any(|e| !e.session.cancellation_token().is_cancelled())
}
}
#[must_use = "OpeningSlot decrements opening_count on drop; bind to a variable"]
pub(super) struct OpeningSlot {
counter: Arc<AtomicUsize>,
}
impl OpeningSlot {
pub(super) fn new(counter: Arc<AtomicUsize>) -> Self {
counter.fetch_add(1, Ordering::AcqRel);
Self { counter }
}
}
impl Drop for OpeningSlot {
fn drop(&mut self) {
self.counter.fetch_sub(1, Ordering::AcqRel);
}
}
pub(super) async fn drain_and_cancel_all(sessions: &TokioRwLock<SessionPool>) {
let drained: Vec<PooledSession> = {
let mut pool = sessions.write().await;
pool.target_pod_uid = None;
let drained = pool.entries.drain(..).collect();
pool.refresh_snapshot();
drained
};
for pooled in drained {
pooled.session.cancellation_token().cancel();
}
}
impl Forwarder {
pub(super) async fn retire_dead_sessions(&self) {
let snapshots: Vec<(Arc<Session>, bool)> = {
let pool = self.sessions.read().await;
pool.entries
.iter()
.map(|e| {
(
Arc::clone(&e.session),
e.session.cancellation_token().is_cancelled(),
)
})
.collect()
};
let mut dead_indices = Vec::new();
for (i, (session, cancelled)) in snapshots.iter().enumerate() {
if *cancelled || session.is_drained() {
dead_indices.push(i);
}
}
if dead_indices.is_empty() {
return;
}
let retired: Vec<Arc<Session>> = {
let mut write = self.sessions.write().await;
let mut retired_sessions = Vec::new();
let mut alive = Vec::with_capacity(write.entries.len());
for (i, entry) in write.entries.drain(..).enumerate() {
if dead_indices.contains(&i) {
retired_sessions.push(Arc::clone(&entry.session));
} else {
alive.push(entry);
}
}
write.entries = alive;
if write.entries.is_empty() {
write.target_pod_uid = None;
}
write.refresh_snapshot();
retired_sessions
};
for retired_session in retired {
retired_session.cancellation_token().cancel();
}
}
pub(super) fn find_reusable_session(&self) -> Option<Arc<Session>> {
let snap = self.session_snap.load();
for session in snap.iter() {
if !session.cancellation_token().is_cancelled() && !session.is_full() {
return Some(Arc::clone(session));
}
}
None
}
pub(super) async fn try_reuse_session(
&self, target_port: u16, pod_uid: &str, pod_name: &str,
) -> Option<Arc<Session>> {
let snap = self.session_snap.load();
for session in snap.iter() {
if !session.cancellation_token().is_cancelled() && !session.is_full() {
let chosen = Arc::clone(session);
drop(snap);
self.maybe_prefetch(
&chosen,
target_port,
pod_name.to_string(),
pod_uid.to_string(),
)
.await;
return Some(chosen);
}
}
None
}
pub(super) async fn reserve_new_slot(&self) -> Result<OpeningSlot, crate::error::Error> {
let pool = self.sessions.write().await;
let projected = pool.entries.len() + pool.opening_count.load(Ordering::Relaxed);
if projected >= self.config.max_sessions {
return Err(crate::error::Error::CapacityExhausted {
in_use: projected,
capacity: self.config.max_sessions,
});
}
Ok(OpeningSlot::new(Arc::clone(&pool.opening_count)))
}
pub(super) fn next_call_id(&self) -> u64 {
self.call_counter.fetch_add(1, Ordering::Relaxed)
}
}