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}