chateau 0.3.2

Tower primitives for Servers and Clients with ergonomic APIs
Documentation
use std::{
    collections::VecDeque,
    marker::PhantomData,
    time::{Duration, Instant},
};

use tracing::trace;

use super::PoolableConnection;

#[derive(Debug)]
struct Idle<T> {
    at: Instant,
    inner: T,
}

impl<T> Idle<T> {
    fn new(inner: T) -> Self {
        Self {
            at: Instant::now(),
            inner,
        }
    }
}

#[derive(Debug)]
pub(super) struct IdleConnections<T, B> {
    inner: VecDeque<Idle<T>>,
    _marker: PhantomData<fn(B)>,
}

impl<T, B> Default for IdleConnections<T, B> {
    fn default() -> Self {
        Self {
            inner: VecDeque::new(),
            _marker: PhantomData,
        }
    }
}

impl<T, B> IdleConnections<T, B> {
    pub(super) fn push(&mut self, inner: T, max_idle: Option<usize>) {
        self.inner.push_front(Idle::new(inner));
        if let Some(max_idle) = max_idle {
            self.inner.truncate(max_idle);
        }
    }

    pub(super) fn pop(&mut self, idle_timeout: Option<Duration>) -> Option<T>
    where
        T: PoolableConnection<B>,
        B: Send + 'static,
    {
        let mut empty = false;
        let mut idle_entry = None;

        if !self.is_empty() {
            let exipred = idle_timeout
                .filter(|timeout| timeout.as_secs_f64() > 0.0)
                .and_then(|timeout| {
                    let now: Instant = Instant::now();
                    now.checked_sub(timeout)
                });

            trace!("checking {} idle connections", self.len());

            while let Some(entry) = self.inner.pop_front() {
                if exipred.map(|expired| entry.at < expired).unwrap_or(false) {
                    trace!("found expired connection");
                    empty = true;
                    break;
                }

                if entry.inner.is_open() {
                    trace!("found idle connection");
                    idle_entry = Some(entry.inner);
                    break;
                } else {
                    trace!("found closed connection");
                }
            }

            empty |= self.is_empty();
        }

        if empty {
            self.clear();
        }

        idle_entry
    }

    pub(super) fn len(&self) -> usize {
        self.inner.len()
    }

    pub(super) fn is_empty(&self) -> bool {
        self.inner.is_empty()
    }

    pub(super) fn clear(&mut self) {
        self.inner.clear();
    }
}

#[cfg(all(test, feature = "mock"))]
mod test {
    use std::thread;

    use super::*;
    use crate::client::conn::protocol::mock::MockSender;

    #[test]
    fn verify_idle() {
        let conn = MockSender::single();
        let idle = Idle::new(conn);

        let dbg = format!("{idle:?}");
        assert!(dbg.starts_with("Idle { at: Instant {"));
    }

    #[test]
    fn verify_idle_connections() {
        let mut idle = IdleConnections::default();
        assert_eq!(idle.len(), 0);
        assert!(idle.is_empty());

        let conn = MockSender::single();
        idle.push(conn, None);

        assert_eq!(idle.len(), 1);
        assert!(!idle.is_empty());

        let conn = idle.pop(None);
        assert!(conn.is_some());
        assert_eq!(idle.len(), 0);
        assert!(idle.is_empty());
    }

    #[test]
    fn test_idle_connections() {
        let mut idle = IdleConnections::default();
        assert_eq!(idle.len(), 0);

        let conn1 = MockSender::single();
        let conn2 = MockSender::single();
        let conn3 = MockSender::single();

        idle.push(conn1, None);
        idle.push(conn2, None);
        idle.push(conn3, None);

        assert_eq!(idle.len(), 3);

        let conn = idle.pop(None);
        assert!(conn.is_some());
        assert_eq!(idle.len(), 2);

        let conn = idle.pop(None);
        assert!(conn.is_some());
        assert_eq!(idle.len(), 1);

        let conn = idle.pop(None);
        assert!(conn.is_some());
        assert_eq!(idle.len(), 0);

        let conn = idle.pop(None);
        assert!(conn.is_none());
        assert_eq!(idle.len(), 0);
    }

    #[test]
    fn test_idle_connections_with_timeout() {
        let mut idle = IdleConnections::default();
        assert_eq!(idle.len(), 0);

        let conn1 = MockSender::single();
        let conn2 = MockSender::single();
        let conn3 = MockSender::single();

        idle.push(conn1, None);
        idle.push(conn2, None);
        idle.push(conn3, None);

        assert_eq!(idle.len(), 3);

        let conn = idle.pop(Some(Duration::from_secs(1)));
        assert!(conn.is_some());
        assert_eq!(idle.len(), 2);

        let conn = idle.pop(Some(Duration::from_secs(1)));
        assert!(conn.is_some());
        assert_eq!(idle.len(), 1);

        let conn = idle.pop(Some(Duration::from_secs(1)));
        assert!(conn.is_some());
        assert_eq!(idle.len(), 0);

        let conn = idle.pop(Some(Duration::from_secs(1)));
        assert!(conn.is_none());
        assert_eq!(idle.len(), 0);
    }

    #[test]
    fn test_idle_connections_with_timeout_expired() {
        let mut idle = IdleConnections::default();
        assert_eq!(idle.len(), 0);

        let conn1 = MockSender::single();
        let conn2 = MockSender::single();
        let conn3 = MockSender::single();

        idle.push(conn1, None);
        idle.push(conn2, None);
        idle.push(conn3, None);

        assert_eq!(idle.len(), 3);

        thread::sleep(Duration::from_millis(10));

        let conn = idle.pop(Some(Duration::from_millis(1)));
        assert!(conn.is_none());
        assert_eq!(idle.len(), 0);
    }

    #[test]
    fn test_idle_connections_enforce_max() {
        let mut idle = IdleConnections::default();
        assert_eq!(idle.len(), 0);

        let conn1 = MockSender::single();
        let conn2 = MockSender::single();
        let conn3 = MockSender::single();

        idle.push(conn1, Some(2));
        idle.push(conn2, Some(2));
        idle.push(conn3, Some(2));

        assert_eq!(idle.len(), 2);

        let conn = idle.pop(None);
        assert!(conn.is_some());
        assert_eq!(idle.len(), 1);
    }
}