Skip to main content

do_memory_storage_turso/pool/
config.rs

1//! Connection pool configuration and statistics
2//!
3//! Provides configuration structs, statistics tracking, and connection guards.
4
5use do_memory_core::{Error, Result};
6use libsql::Connection;
7use parking_lot::RwLock;
8use std::sync::Arc;
9use std::sync::atomic::{AtomicU64, AtomicUsize};
10use std::time::Duration;
11use tokio::sync::OwnedSemaphorePermit;
12
13/// Configuration for connection pool
14#[derive(Debug, Clone)]
15pub struct PoolConfig {
16    /// Minimum number of connections to maintain in the pool
17    pub min_connections: usize,
18    /// Maximum number of concurrent connections
19    pub max_connections: usize,
20    /// Maximum time to wait for a connection (seconds)
21    pub connection_timeout: Duration,
22    /// Enable connection health checks
23    pub enable_health_check: bool,
24    /// Health check timeout
25    pub health_check_timeout: Duration,
26    /// Timeout for acquiring a connection from the pool (milliseconds).
27    /// Alias for `connection_timeout` — preferred for new code.
28    pub acquire_timeout_ms: u64,
29    /// Idle timeout for connections (milliseconds).
30    /// Connections idle longer than this may be dropped. 0 = no idle timeout.
31    pub idle_timeout_ms: u64,
32}
33
34impl Default for PoolConfig {
35    fn default() -> Self {
36        Self {
37            min_connections: 1,
38            max_connections: 10,
39            connection_timeout: Duration::from_secs(5),
40            enable_health_check: true,
41            health_check_timeout: Duration::from_secs(2),
42            acquire_timeout_ms: 5000,
43            idle_timeout_ms: 0,
44        }
45    }
46}
47
48/// Atomic pool metrics for lock-free monitoring
49///
50/// Uses atomic types for safe concurrent access without locking.
51/// Suitable for real-time dashboards and health-check endpoints.
52#[derive(Debug)]
53pub struct PoolMetrics {
54    /// Current number of active (checked out) connections
55    pub active_connections: AtomicUsize,
56    /// Total connections acquired (cumulative)
57    pub total_acquired: AtomicU64,
58    /// Total wait time for acquiring connections (milliseconds)
59    pub total_wait_ms: AtomicU64,
60    /// Number of reconnection attempts
61    pub reconnect_count: AtomicU64,
62}
63
64impl Default for PoolMetrics {
65    fn default() -> Self {
66        Self {
67            active_connections: AtomicUsize::new(0),
68            total_acquired: AtomicU64::new(0),
69            total_wait_ms: AtomicU64::new(0),
70            reconnect_count: AtomicU64::new(0),
71        }
72    }
73}
74
75impl PoolMetrics {
76    /// Record a connection acquisition
77    pub fn record_acquire(&self, wait_ms: u64) {
78        self.active_connections
79            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
80        self.total_acquired
81            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
82        self.total_wait_ms
83            .fetch_add(wait_ms, std::sync::atomic::Ordering::Relaxed);
84    }
85
86    /// Record a connection release
87    pub fn record_release(&self) {
88        self.active_connections
89            .fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
90    }
91
92    /// Record a reconnection attempt
93    pub fn record_reconnect(&self) {
94        self.reconnect_count
95            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
96    }
97
98    /// Snapshot current metrics as plain values
99    pub fn snapshot(&self) -> PoolMetricsSnapshot {
100        PoolMetricsSnapshot {
101            active_connections: self
102                .active_connections
103                .load(std::sync::atomic::Ordering::Relaxed),
104            total_acquired: self
105                .total_acquired
106                .load(std::sync::atomic::Ordering::Relaxed),
107            total_wait_ms: self
108                .total_wait_ms
109                .load(std::sync::atomic::Ordering::Relaxed),
110            reconnect_count: self
111                .reconnect_count
112                .load(std::sync::atomic::Ordering::Relaxed),
113        }
114    }
115}
116
117/// Snapshot of pool metrics (plain values, safe to send across threads)
118#[derive(Debug, Clone, Default)]
119pub struct PoolMetricsSnapshot {
120    /// Current active connections
121    pub active_connections: usize,
122    /// Total connections acquired
123    pub total_acquired: u64,
124    /// Total wait time in milliseconds
125    pub total_wait_ms: u64,
126    /// Number of reconnection attempts
127    pub reconnect_count: u64,
128}
129
130/// Pool statistics for monitoring (legacy, non-atomic)
131#[derive(Debug, Clone, Default)]
132pub struct PoolStatistics {
133    /// Total connections created
134    pub total_created: usize,
135    /// Total connections that passed health check
136    pub total_health_checks_passed: usize,
137    /// Total connections failed health check
138    pub total_health_checks_failed: usize,
139    /// Current number of active (checked out) connections
140    pub active_connections: usize,
141    /// Total checkout wait time (milliseconds)
142    pub total_wait_time_ms: u64,
143    /// Number of checkouts
144    pub total_checkouts: usize,
145    /// Average wait time per checkout (milliseconds)
146    pub avg_wait_time_ms: u64,
147}
148
149impl PoolStatistics {
150    pub fn update_averages(&mut self) {
151        if self.total_checkouts > 0 {
152            self.avg_wait_time_ms = self.total_wait_time_ms / self.total_checkouts as u64;
153        }
154    }
155}
156
157/// A guard that returns a permit to the pool when dropped
158#[derive(Debug)]
159pub struct PooledConnection {
160    pub(super) connection: Option<Connection>,
161    pub(super) _permit: OwnedSemaphorePermit,
162    pub(super) stats: Arc<RwLock<PoolStatistics>>,
163    pub(super) metrics: Option<Arc<PoolMetrics>>,
164}
165
166impl PooledConnection {
167    /// Get a reference to the underlying connection
168    pub fn connection(&self) -> Option<&Connection> {
169        self.connection.as_ref()
170    }
171
172    /// Take ownership of the connection
173    pub fn into_inner(mut self) -> Result<Connection> {
174        self.connection
175            .take()
176            .ok_or_else(|| Error::Storage("Connection already taken".to_string()))
177    }
178}
179
180impl Drop for PooledConnection {
181    fn drop(&mut self) {
182        // Decrement active connections when the guard is dropped
183        // Using parking_lot's RwLock which supports blocking operations in Drop
184        let mut stats = self.stats.write();
185        if stats.active_connections > 0 {
186            stats.active_connections -= 1;
187        }
188        // Also update atomic metrics if available
189        if let Some(ref metrics) = self.metrics {
190            metrics.record_release();
191        }
192    }
193}