use alloc::vec::Vec;
use ax_sync::Mutex;
use smoltcp::{
iface::{SocketHandle, SocketSet},
socket::tcp,
time::Instant,
};
use spin::LazyLock;
struct OrphanSocket {
handle: SocketHandle,
orphaned_at: Instant,
}
impl OrphanSocket {
fn linger_micros(&self, timestamp: Instant) -> i64 {
timestamp.total_micros() - self.orphaned_at.total_micros()
}
fn linger_expired(&self, timestamp: Instant) -> bool {
self.linger_micros(timestamp) >= ORPHAN_MAX_LINGER
}
}
#[derive(Clone, Copy)]
enum ReapReason {
Closed,
Expired,
}
static ORPHAN_SOCKETS: LazyLock<Mutex<Vec<OrphanSocket>>> =
LazyLock::new(|| Mutex::new(Vec::new()));
const ORPHAN_MAX_LINGER: i64 = 60_000_000; const ORPHAN_MAX_SOCKETS: usize = 1024;
pub(crate) fn add_orphan(handle: SocketHandle, timestamp: Instant) {
ORPHAN_SOCKETS.lock().push(OrphanSocket {
handle,
orphaned_at: timestamp,
});
}
pub(crate) fn reap_orphans(timestamp: Instant, sockets: &mut SocketSet<'_>) {
let mut removed = Vec::new();
let remaining_overflow = {
let mut orphans = ORPHAN_SOCKETS.lock();
orphans.retain(|orphan| {
let socket = sockets.get_mut::<tcp::Socket>(orphan.handle);
let state = socket.state();
let reason = match state {
tcp::State::Closed => Some(ReapReason::Closed),
tcp::State::TimeWait => {
orphan
.linger_expired(timestamp)
.then_some(ReapReason::Expired)
}
tcp::State::LastAck | tcp::State::FinWait1 | tcp::State::FinWait2 => {
orphan
.linger_expired(timestamp)
.then_some(ReapReason::Expired)
}
tcp::State::Closing => orphan
.linger_expired(timestamp)
.then_some(ReapReason::Expired),
_ => {
let elapsed = orphan.linger_micros(timestamp);
if orphan.linger_expired(timestamp) {
warn!(
"Orphan socket {} in unexpected state {:?} after {}s, force removing",
orphan.handle,
socket.state(),
elapsed / 1_000_000
);
Some(ReapReason::Expired)
} else {
None
}
}
};
if let Some(reason) = reason {
removed.push((orphan.handle, reason));
false
} else {
true
}
});
orphans.len().saturating_sub(ORPHAN_MAX_SOCKETS)
};
for (handle, reason) in removed {
sockets.remove(handle);
match reason {
ReapReason::Closed => debug!("Reaped closed orphan socket {}", handle),
ReapReason::Expired => warn!("Reaped expired orphan socket {}", handle),
}
}
if remaining_overflow > 0 {
warn!(
"Orphan socket pool exceeds limit by {remaining_overflow}; keeping sockets that are \
still tearing down"
);
}
}
#[allow(dead_code)]
pub(crate) fn orphan_count() -> usize {
ORPHAN_SOCKETS.lock().len()
}