use std::collections::HashMap;
use std::sync::Arc;
use crate::sync::{AtomicU8, AtomicUsize, Ordering};
use std::time::{Duration, Instant};
use fibre::mpmc;
use fibre::sync::HybridMutex;
use crate::Error;
use crate::conn::{Conn, Proto};
use crate::dial::{self, Dialer};
use crate::notify::Notify;
const WAIT_LIMIT: Duration = Duration::from_millis(100);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Via {
pub proxy: crate::proxy::Proxy,
pub tunnel: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct Key {
pub https: bool,
pub host: String,
pub port: u16,
pub via: Option<Via>,
}
impl Key {
pub(crate) fn for_target(uri: &http::Uri, proxies: &crate::proxy::Proxies) -> Result<Key, Error> {
let origin = Key::from_uri(uri)?;
Ok(match proxies.for_target(origin.https, &origin.host) {
None => origin,
Some(proxy) if origin.https => Key { via: Some(Via { proxy: proxy.clone(), tunnel: true }), ..origin },
Some(proxy) => Key { https: false, host: proxy.host.clone(), port: proxy.port, via: Some(Via { proxy: proxy.clone(), tunnel: false }) },
})
}
pub(crate) fn forwarded(&self) -> Option<&crate::proxy::Proxy> {
self.via.as_ref().filter(|v| !v.tunnel).map(|v| &v.proxy)
}
fn from_uri(uri: &http::Uri) -> Result<Key, Error> {
let https = match uri.scheme_str() {
Some("https") => true,
Some("http") => false,
other => return Err(Error::Url(format!("unsupported scheme {other:?}"))),
};
let host = uri.host().ok_or_else(|| Error::Url("no host".into()))?.trim_start_matches('[').trim_end_matches(']').to_string();
let port = uri.port_u16().unwrap_or(if https { 443 } else { 80 });
Ok(Key { https, host, port, via: None })
}
}
struct Host {
idle_tx: mpmc::Sender<Arc<Conn>>,
idle_rx: mpmc::Receiver<Arc<Conn>>,
notify: Notify,
count: AtomicUsize,
dialing: AtomicUsize,
proto: AtomicU8,
shared: HybridMutex<Vec<Arc<Conn>>>,
}
#[derive(Debug, Clone)]
pub struct PoolBuilder {
max_per_host: usize,
idle_timeout: Duration,
peek_after: Duration,
}
impl Default for PoolBuilder {
fn default() -> Self {
Self { max_per_host: 32, idle_timeout: Duration::from_secs(90), peek_after: Duration::from_secs(1) }
}
}
impl PoolBuilder {
pub fn max_per_host(mut self, n: usize) -> Self {
self.max_per_host = n.max(1);
self
}
pub fn idle_timeout(mut self, d: Duration) -> Self {
self.idle_timeout = d;
self
}
pub fn peek_after(mut self, d: Duration) -> Self {
self.peek_after = d;
self
}
pub fn build(self) -> Pool {
Pool { inner: Arc::new(PoolInner { hosts: HybridMutex::new(HashMap::new()), config: self, opened: AtomicUsize::new(0) }) }
}
}
struct PoolInner {
hosts: HybridMutex<HashMap<Key, Arc<Host>>>,
config: PoolBuilder,
opened: AtomicUsize,
}
#[derive(Clone)]
pub struct Pool {
inner: Arc<PoolInner>,
}
impl Default for Pool {
fn default() -> Self {
Pool::builder().build()
}
}
pub(crate) struct Lease {
pub conn: Arc<Conn>,
host: Arc<Host>,
pub reused: bool,
}
impl Lease {
pub(crate) fn release(self) {
let host = self.host.clone();
if self.conn.proto == Proto::H1 {
if self.conn.is_closed() {
host.count.fetch_sub(1, Ordering::AcqRel);
} else {
self.conn.mark_idle();
if host.idle_tx.try_send(self.conn).is_err() {
host.count.fetch_sub(1, Ordering::AcqRel);
}
}
}
host.notify.notify_all();
}
}
enum Found {
Lease(Lease),
Dial,
Wait,
}
impl Pool {
pub fn builder() -> PoolBuilder {
PoolBuilder::default()
}
pub fn connections_opened(&self) -> usize {
self.inner.opened.load(Ordering::Relaxed)
}
fn host(&self, key: &Key) -> Arc<Host> {
let mut hosts = self.inner.hosts.lock();
hosts
.entry(key.clone())
.or_insert_with(|| {
let (idle_tx, idle_rx) = mpmc::bounded(self.inner.config.max_per_host);
Arc::new(Host { idle_tx, idle_rx, notify: Notify::default(), count: AtomicUsize::new(0), dialing: AtomicUsize::new(0), proto: AtomicU8::new(0), shared: HybridMutex::new(Vec::new()) })
})
.clone()
}
fn usable(&self, conn: &Conn) -> bool {
conn.usable(self.inner.config.peek_after, self.inner.config.idle_timeout)
}
fn find(&self, host: &Arc<Host>, shared: &mut Vec<Arc<Conn>>) -> Found {
shared.retain(|c| {
let keep = !c.is_closed();
if !keep {
host.count.fetch_sub(1, Ordering::AcqRel);
}
keep
});
if let Some(conn) = shared.iter().find(|c| c.has_room()) {
return Found::Lease(Lease { conn: conn.clone(), host: host.clone(), reused: true });
}
while let Ok(conn) = host.idle_rx.try_recv() {
if self.usable(&conn) {
return Found::Lease(Lease { conn, host: host.clone(), reused: true });
}
host.count.fetch_sub(1, Ordering::AcqRel);
}
if host.proto.load(Ordering::Acquire) != 1 && host.dialing.load(Ordering::Acquire) > 0 {
return Found::Wait;
}
let max = self.inner.config.max_per_host;
if host.count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |n| (n < max).then_some(n + 1)).is_ok() {
host.dialing.fetch_add(1, Ordering::AcqRel);
return Found::Dial;
}
Found::Wait
}
fn dialed(&self, host: &Arc<Host>, result: Result<Arc<Conn>, Error>) -> Result<Lease, Error> {
let lease = match result {
Ok(conn) => {
self.inner.opened.fetch_add(1, Ordering::Relaxed);
host.proto.store(if conn.proto == Proto::H2 { 2 } else { 1 }, Ordering::Release);
if conn.proto == Proto::H2 {
host.shared.lock().push(conn.clone());
}
Ok(Lease { conn, host: host.clone(), reused: false })
}
Err(e) => {
host.count.fetch_sub(1, Ordering::AcqRel);
Err(e)
}
};
host.dialing.fetch_sub(1, Ordering::AcqRel);
host.notify.notify_all();
lease
}
fn limit(deadline: Option<Instant>) -> Result<Duration, Error> {
match deadline {
Some(d) => {
let now = Instant::now();
if now >= d {
return Err(Error::Timeout);
}
Ok((d - now).min(WAIT_LIMIT))
}
None => Ok(WAIT_LIMIT),
}
}
pub(crate) fn checkout_sync(&self, key: &Key, dialer: &Dialer, deadline: Option<Instant>) -> Result<Lease, Error> {
let host = self.host(key);
loop {
let seen = host.notify.epoch();
let found = {
let mut shared = host.shared.lock();
self.find(&host, &mut shared)
};
match found {
Found::Lease(lease) => return Ok(lease),
Found::Dial => return self.dialed(&host, dial::dial_sync(dialer, key, deadline)),
Found::Wait => host.notify.wait_sync(seen, Self::limit(deadline)?),
}
}
}
pub(crate) async fn checkout_async(&self, key: &Key, dialer: &Dialer, deadline: Option<Instant>) -> Result<Lease, Error> {
let host = self.host(key);
loop {
let seen = host.notify.epoch();
let found = {
let mut shared = host.shared.lock_async().await;
self.find(&host, &mut shared)
};
match found {
Found::Lease(lease) => return Ok(lease),
Found::Dial => return self.dialed(&host, dial::dial_async(dialer, key, deadline).await),
Found::Wait => host.notify.wait_async(seen, Self::limit(deadline)?).await,
}
}
}
}