use std::collections::VecDeque;
use std::net::SocketAddr;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use std::time::Duration;
use dashmap::DashMap;
use tokio::sync::{Mutex, OwnedSemaphorePermit, Semaphore};
use crate::proto::auth::Credential;
use crate::proto::conn::{NfsConnection, ReconnectStrategy};
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) struct PoolKey {
pub host: SocketAddr,
pub export: String,
pub uid: u32,
pub gid: u32,
}
struct PoolInner {
pools: DashMap<PoolKey, Arc<Mutex<VecDeque<NfsConnection>>>>,
max_per_key: usize,
max_total: usize,
admission: Arc<Semaphore>,
stale_threshold: Duration,
proxy: Option<String>,
}
#[derive(Clone, Debug)]
pub(crate) struct ConnectionPool {
inner: Arc<PoolInner>,
}
impl std::fmt::Debug for PoolInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PoolInner").field("max_per_key", &self.max_per_key).field("max_total", &self.max_total).field("outstanding", &self.max_total.saturating_sub(self.admission.available_permits())).finish_non_exhaustive()
}
}
impl ConnectionPool {
#[must_use]
pub(crate) fn new(max_per_key: usize, max_total: usize, stale_threshold: Duration) -> Self {
Self { inner: Arc::new(PoolInner { pools: DashMap::new(), max_per_key, max_total, admission: Arc::new(Semaphore::new(max_total)), stale_threshold, proxy: None }) }
}
#[must_use]
pub(crate) fn default_config() -> Self {
Self::new(4, 256, Duration::from_secs(5))
}
#[must_use]
pub(crate) fn with_proxy(proxy: String) -> Self {
Self { inner: Arc::new(PoolInner { pools: DashMap::new(), max_per_key: 4, max_total: 256, admission: Arc::new(Semaphore::new(256)), stale_threshold: Duration::from_secs(5), proxy: Some(proxy) }) }
}
pub(crate) async fn checkout(&self, key: PoolKey, credential: Credential, reconnect: ReconnectStrategy) -> anyhow::Result<PooledConnection> {
let permit = Arc::clone(&self.inner.admission).acquire_owned().await.map_err(|e| anyhow::anyhow!("connection pool closed: {e}"))?;
if let Some(mut conn) = self.try_pop(&key).await {
restamp_credential(&mut conn, credential);
return Ok(PooledConnection { conn: Some(conn), pool: self.clone(), key, _permit: permit });
}
let conn = NfsConnection::connect(key.host, &key.export, credential, reconnect, self.inner.proxy.as_deref()).await?;
Ok(PooledConnection { conn: Some(conn), pool: self.clone(), key, _permit: permit })
}
pub(crate) fn checkin(&self, key: PoolKey, conn: NfsConnection) {
if conn.health.poisoned {
return; }
let queue = {
let entry = self.inner.pools.entry(key).or_insert_with(|| Arc::new(Mutex::new(VecDeque::new())));
Arc::clone(&entry)
};
if let Ok(mut q) = queue.try_lock()
&& q.len() < self.inner.max_per_key
{
q.push_back(conn);
}
}
pub(crate) async fn checkout_for(&self, key: PoolKey, credential: Credential, reconnect: ReconnectStrategy, direct_nfs_port: Option<u16>) -> anyhow::Result<PooledConnection> {
match direct_nfs_port {
Some(port) => self.checkout_direct(key, port, credential, reconnect).await,
None => self.checkout(key, credential, reconnect).await,
}
}
pub(crate) async fn checkout_direct(&self, key: PoolKey, nfs_port: u16, credential: Credential, reconnect: ReconnectStrategy) -> anyhow::Result<PooledConnection> {
let permit = Arc::clone(&self.inner.admission).acquire_owned().await.map_err(|e| anyhow::anyhow!("connection pool closed: {e}"))?;
if let Some(mut conn) = self.try_pop(&key).await {
restamp_credential(&mut conn, credential);
return Ok(PooledConnection { conn: Some(conn), pool: self.clone(), key, _permit: permit });
}
let conn = NfsConnection::connect_direct(key.host, nfs_port, credential, reconnect, self.inner.proxy.as_deref()).await?;
Ok(PooledConnection { conn: Some(conn), pool: self.clone(), key, _permit: permit })
}
async fn try_pop(&self, key: &PoolKey) -> Option<NfsConnection> {
let queue = {
let entry = self.inner.pools.get(key)?;
Arc::clone(&entry)
};
let mut conn = {
let mut q = queue.lock().await;
q.pop_back()?
};
if conn.is_stale(self.inner.stale_threshold) && !conn.health_check().await {
return None;
}
Some(conn)
}
}
fn restamp_credential(conn: &mut NfsConnection, credential: Credential) {
conn.update_credential(credential);
}
pub(crate) struct PooledConnection {
conn: Option<NfsConnection>,
pool: ConnectionPool,
key: PoolKey,
_permit: OwnedSemaphorePermit,
}
impl std::fmt::Debug for PooledConnection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PooledConnection").field("key", &self.key).finish_non_exhaustive()
}
}
impl Drop for PooledConnection {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
self.pool.checkin(self.key.clone(), conn);
}
}
}
impl Deref for PooledConnection {
type Target = NfsConnection;
fn deref(&self) -> &Self::Target {
self.conn.as_ref().unwrap_or_else(|| unreachable!("connection must be present while guard is alive"))
}
}
impl DerefMut for PooledConnection {
fn deref_mut(&mut self) -> &mut Self::Target {
self.conn.as_mut().unwrap_or_else(|| unreachable!("connection must be present while guard is alive"))
}
}