Skip to main content

sentinel_driver/pool/
config.rs

1use std::fmt;
2use std::sync::Arc;
3use std::time::Duration;
4
5use futures_core::future::BoxFuture;
6
7use crate::error::Result;
8use crate::pool::health::HealthCheckStrategy;
9use crate::Connection;
10
11/// Called once per newly created connection, after TCP + TLS + auth completes.
12///
13/// Use for session setup like `SET search_path`. Error → connection discarded, pool retries.
14pub type ConnectCallback = Arc<dyn Fn(&mut Connection) -> BoxFuture<'_, Result<()>> + Send + Sync>;
15
16/// Called before returning a connection from the pool.
17///
18/// Return `false` to reject — connection discarded, pool tries next idle or creates new.
19/// Error → connection discarded.
20pub type AcquireCallback =
21    Arc<dyn Fn(&mut Connection) -> BoxFuture<'_, Result<bool>> + Send + Sync>;
22
23/// Called when a connection returns to the pool.
24///
25/// Return `false` to discard instead of returning to idle queue.
26/// Error → connection discarded.
27pub type ReleaseCallback =
28    Arc<dyn Fn(&mut Connection) -> BoxFuture<'_, Result<bool>> + Send + Sync>;
29
30/// Configuration for the connection pool.
31///
32/// Supports lifecycle callbacks for connection setup, validation, and cleanup.
33/// All callbacks are optional and default to `None`.
34#[derive(Clone)]
35pub struct PoolConfig {
36    pub max_connections: usize,
37    pub min_connections: usize,
38    pub connect_timeout: Duration,
39    pub idle_timeout: Option<Duration>,
40    pub max_lifetime: Option<Duration>,
41    pub health_check: HealthCheckStrategy,
42    pub acquire_timeout: Duration,
43    pub after_connect: Option<ConnectCallback>,
44    pub before_acquire: Option<AcquireCallback>,
45    pub after_release: Option<ReleaseCallback>,
46}
47
48impl fmt::Debug for PoolConfig {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        f.debug_struct("PoolConfig")
51            .field("max_connections", &self.max_connections)
52            .field("min_connections", &self.min_connections)
53            .field("connect_timeout", &self.connect_timeout)
54            .field("idle_timeout", &self.idle_timeout)
55            .field("max_lifetime", &self.max_lifetime)
56            .field("health_check", &self.health_check)
57            .field("acquire_timeout", &self.acquire_timeout)
58            .field("after_connect", &self.after_connect.as_ref().map(|_| ".."))
59            .field(
60                "before_acquire",
61                &self.before_acquire.as_ref().map(|_| ".."),
62            )
63            .field("after_release", &self.after_release.as_ref().map(|_| ".."))
64            .finish()
65    }
66}
67
68impl Default for PoolConfig {
69    fn default() -> Self {
70        Self {
71            max_connections: num_cpus(),
72            min_connections: 1,
73            connect_timeout: Duration::from_secs(10),
74            idle_timeout: Some(Duration::from_secs(600)),
75            max_lifetime: Some(Duration::from_secs(3600)),
76            health_check: HealthCheckStrategy::Fast,
77            acquire_timeout: Duration::from_secs(30),
78            after_connect: None,
79            before_acquire: None,
80            after_release: None,
81        }
82    }
83}
84
85impl PoolConfig {
86    pub fn new() -> Self {
87        Self::default()
88    }
89
90    /// Maximum number of connections in the pool.
91    ///
92    /// Default: 2 * number of CPUs.
93    pub fn max_connections(mut self, n: usize) -> Self {
94        self.max_connections = n;
95        self
96    }
97
98    /// Minimum number of idle connections to maintain.
99    ///
100    /// The pool will create connections in the background to maintain this minimum.
101    /// Default: 1.
102    pub fn min_connections(mut self, n: usize) -> Self {
103        self.min_connections = n;
104        self
105    }
106
107    /// Timeout for establishing new connections.
108    ///
109    /// Default: 10 seconds.
110    pub fn connect_timeout(mut self, timeout: Duration) -> Self {
111        self.connect_timeout = timeout;
112        self
113    }
114
115    /// Maximum time a connection can sit idle before being closed.
116    ///
117    /// Set to `None` to disable idle timeout. Default: 600 seconds.
118    pub fn idle_timeout(mut self, timeout: Option<Duration>) -> Self {
119        self.idle_timeout = timeout;
120        self
121    }
122
123    /// Maximum total lifetime of a connection before it's recycled.
124    ///
125    /// Set to `None` to disable max lifetime. Default: 3600 seconds.
126    pub fn max_lifetime(mut self, lifetime: Option<Duration>) -> Self {
127        self.max_lifetime = lifetime;
128        self
129    }
130
131    /// Strategy for checking connection health on checkout.
132    ///
133    /// Default: `Fast` (flag-based, no query).
134    pub fn health_check(mut self, strategy: HealthCheckStrategy) -> Self {
135        self.health_check = strategy;
136        self
137    }
138
139    /// Timeout for acquiring a connection from the pool.
140    ///
141    /// If the pool is full and no connection becomes available within this
142    /// duration, an error is returned. Default: 30 seconds.
143    pub fn acquire_timeout(mut self, timeout: Duration) -> Self {
144        self.acquire_timeout = timeout;
145        self
146    }
147
148    /// Set a callback that runs once per newly created connection.
149    ///
150    /// Called after TCP + TLS + auth completes, before the connection enters
151    /// the pool. Use for session setup like `SET search_path`.
152    ///
153    /// If the callback returns an error, the connection is discarded and
154    /// the pool retries with a new connection.
155    ///
156    /// # Example
157    ///
158    /// ```rust,no_run
159    /// # use sentinel_driver::pool::config::PoolConfig;
160    /// PoolConfig::new()
161    ///     .after_connect(|conn| Box::pin(async move {
162    ///         conn.execute("SET search_path TO myapp", &[]).await?;
163    ///         Ok(())
164    ///     }));
165    /// ```
166    pub fn after_connect<F>(mut self, callback: F) -> Self
167    where
168        F: Fn(&mut Connection) -> BoxFuture<'_, Result<()>> + Send + Sync + 'static,
169    {
170        self.after_connect = Some(Arc::new(callback));
171        self
172    }
173
174    /// Set a callback that runs before returning a connection from the pool.
175    ///
176    /// Called after health check passes. Return `false` to reject the
177    /// connection — it will be discarded and the pool tries the next idle
178    /// connection or creates a new one.
179    ///
180    /// # Example
181    ///
182    /// ```rust,no_run
183    /// # use sentinel_driver::pool::config::PoolConfig;
184    /// PoolConfig::new()
185    ///     .before_acquire(|conn| Box::pin(async move {
186    ///         Ok(!conn.is_broken())
187    ///     }));
188    /// ```
189    pub fn before_acquire<F>(mut self, callback: F) -> Self
190    where
191        F: Fn(&mut Connection) -> BoxFuture<'_, Result<bool>> + Send + Sync + 'static,
192    {
193        self.before_acquire = Some(Arc::new(callback));
194        self
195    }
196
197    /// Set a callback that runs when a connection returns to the pool.
198    ///
199    /// Called before the connection enters the idle queue. Return `false`
200    /// to discard the connection instead of returning it.
201    ///
202    /// # Example
203    ///
204    /// ```rust,no_run
205    /// # use sentinel_driver::pool::config::PoolConfig;
206    /// PoolConfig::new()
207    ///     .after_release(|conn| Box::pin(async move {
208    ///         Ok(true) // always return to pool
209    ///     }));
210    /// ```
211    pub fn after_release<F>(mut self, callback: F) -> Self
212    where
213        F: Fn(&mut Connection) -> BoxFuture<'_, Result<bool>> + Send + Sync + 'static,
214    {
215        self.after_release = Some(Arc::new(callback));
216        self
217    }
218}
219
220fn num_cpus() -> usize {
221    std::thread::available_parallelism().map_or(8, |n| n.get() * 2)
222}