use std::collections::HashMap;
use std::fmt;
use std::ops::Deref;
use std::ops::DerefMut;
use std::pin::Pin;
use std::sync::Arc;
use parking_lot::Mutex;
use tracing::trace;
mod checkout;
mod idle;
mod key;
mod lock;
pub mod manager;
pub(super) mod service;
use crate::BoxError;
pub(super) use self::checkout::Checkout;
use self::idle::IdleConnections;
pub(super) use self::key::Token;
use self::key::TokenMap;
use self::lock::WeakMutex;
use self::manager::ConnectionManager;
use self::manager::ConnectionManagerConfig;
use self::manager::InnerConnectionManager;
use super::conn::Connection;
use super::conn::Connector;
use super::conn::Protocol;
use super::conn::Transport;
#[derive(Debug, thiserror::Error)]
#[error("{inner}")]
pub struct KeyError {
#[source]
inner: BoxError,
}
impl KeyError {
pub fn new<E>(err: E) -> Self
where
E: Into<BoxError>,
{
Self { inner: err.into() }
}
}
pub trait Key<R>: Eq + std::hash::Hash + fmt::Debug {
fn build_key(request: &R) -> Result<Self, KeyError>
where
Self: Sized;
}
#[derive(Debug)]
pub(crate) struct Pool<C, R, K>
where
C: PoolableConnection<R>,
R: Send + 'static,
K: Key<R>,
{
inner: Arc<Mutex<PoolInner<C, R>>>,
keys: Arc<Mutex<TokenMap<K>>>,
}
impl<C, R, K> Clone for Pool<C, R, K>
where
R: Send + 'static,
C: PoolableConnection<R>,
K: Key<R>,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
keys: self.keys.clone(),
}
}
}
impl<C, R, K> Pool<C, R, K>
where
R: Send + 'static,
C: PoolableConnection<R>,
K: Key<R>,
{
pub(crate) fn new(config: ConnectionManagerConfig) -> Self {
Self {
inner: Arc::new(Mutex::new(PoolInner::new(config))),
keys: Arc::new(Mutex::new(TokenMap::default())),
}
}
}
impl<C, R, K> Default for Pool<C, R, K>
where
R: Send + 'static,
C: PoolableConnection<R>,
K: Key<R>,
{
fn default() -> Self {
Self::new(ConnectionManagerConfig::default())
}
}
impl<C, R, K> Pool<C, R, K>
where
R: Send + 'static,
C: PoolableConnection<R>,
K: Key<R>,
{
#[cfg_attr(not(tarpaulin), tracing::instrument(skip_all, fields(?key), level="debug"))]
pub(crate) fn checkout<T, P>(&self, key: K, connector: Connector<T, P, R>) -> Checkout<T, P, R>
where
T: Transport<R> + Send,
P: Protocol<T::IO, R, Connection = C> + Send + 'static,
C: PoolableConnection<R>,
{
let token = self.keys.lock().insert(key);
let mut inner = self.inner.lock();
let config = inner.config.clone();
let manager = inner
.connections
.entry(token)
.or_insert_with(|| ConnectionManager::new(config));
manager.checkout(connector)
}
}
#[derive(Debug)]
pub(in crate::client) struct PoolInner<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
config: Arc<ConnectionManagerConfig>,
connections: HashMap<Token, ConnectionManager<C, R>>,
}
impl<C, R> PoolInner<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn new(config: ConnectionManagerConfig) -> Self {
Self {
config: Arc::new(config),
connections: HashMap::new(),
}
}
}
pub trait PoolableStream: Unpin + Send + Sized + 'static {
fn can_share(&self) -> bool;
}
pub trait PoolableConnection<R>: Connection<R> + Unpin + Send + Sized + 'static
where
R: Send + 'static,
{
fn is_open(&self) -> bool;
fn can_share(&self) -> bool;
fn reuse(&mut self) -> Option<Self>;
}
type ManagerRef<C, R> = WeakMutex<InnerConnectionManager<C, R>>;
pub struct Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
connection: Option<C>,
manager: ManagerRef<C, R>,
}
impl<C, R> Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn take(mut self) -> Option<C> {
self.connection.take()
}
}
impl<C, R> fmt::Debug for Pooled<C, R>
where
C: fmt::Debug + PoolableConnection<R>,
R: Send + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Pooled").field(&self.connection).finish()
}
}
impl<C, R> Deref for Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
type Target = C;
fn deref(&self) -> &Self::Target {
self.connection
.as_ref()
.expect("connection only taken on Drop")
}
}
impl<C, R> DerefMut for Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.connection
.as_mut()
.expect("connection only taken on Drop")
}
}
impl<C, R> Connection<R> for Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
type Response = C::Response;
type Error = <C as Connection<R>>::Error;
type Future = C::Future;
fn send_request(&mut self, request: R) -> Self::Future {
self.connection
.as_mut()
.expect("connection only taken on Drop")
.send_request(request)
}
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
<C as Connection<R>>::poll_ready(
self.connection
.as_mut()
.expect("connection only taken on Drop"),
cx,
)
}
}
impl<C, R> PoolableConnection<R> for Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn reuse(&mut self) -> Option<Self> {
self.connection.as_mut().unwrap().reuse().map(|c| Pooled {
connection: Some(c),
manager: self.manager.clone(),
})
}
fn is_open(&self) -> bool {
self.connection.as_ref().unwrap().is_open()
}
fn can_share(&self) -> bool {
self.connection.as_ref().unwrap().can_share()
}
}
impl<C, R> Drop for Pooled<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn drop(&mut self) {
if let Some(connection) = self.connection.take() {
if !connection.can_share() {
tokio::spawn(WhenReady {
connection: Some(connection),
pool: self.manager.clone(),
});
}
}
}
}
#[derive(Debug)]
struct WhenReady<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
connection: Option<C>,
pool: ManagerRef<C, R>,
}
impl<C, R> std::future::Future for WhenReady<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<()> {
match self
.connection
.as_mut()
.expect("connection polled after drop")
.poll_ready(cx)
{
std::task::Poll::Ready(Ok(())) => std::task::Poll::Ready(()),
std::task::Poll::Ready(Err(err)) => {
tracing::trace!(error = %err, "connection errored while polling for readiness");
std::task::Poll::Ready(())
}
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}
impl<C, R> Drop for WhenReady<C, R>
where
C: PoolableConnection<R>,
R: Send + 'static,
{
fn drop(&mut self) {
if let Some(connection) = self.connection.take() {
if connection.is_open() {
if let Some(mut pool) = self.pool.lock() {
trace!("open connection returned to pool");
pool.push(connection, &self.pool);
}
}
}
}
}