1pub mod replica;
46pub use replica::{ReplicaPool, ReplicaStrategy};
47
48pub mod sharding;
49pub use sharding::{ModuloShardChooser, QueryHints, ShardChooser, ShardedPool, ShardedPoolStats};
50
51use std::collections::VecDeque;
52use std::future::Future;
53use std::sync::atomic::{AtomicU64, Ordering};
54use std::sync::{Arc, Condvar, Mutex, Weak};
55use std::time::{Duration, Instant};
56
57use asupersync::{CancelReason, Cx, Outcome, runtime::RuntimeBuilder};
58use sqlmodel_core::error::{ConnectionError, ConnectionErrorKind, PoolError, PoolErrorKind};
59use sqlmodel_core::{Connection, Error};
60
61#[derive(Debug, Clone)]
63pub struct PoolConfig {
64 pub min_connections: usize,
66 pub max_connections: usize,
68 pub idle_timeout_ms: u64,
70 pub acquire_timeout_ms: u64,
72 pub max_lifetime_ms: u64,
74 pub test_on_checkout: bool,
76 pub test_on_return: bool,
78}
79
80impl Default for PoolConfig {
81 fn default() -> Self {
82 Self {
83 min_connections: 1,
84 max_connections: 10,
85 idle_timeout_ms: 600_000, acquire_timeout_ms: 30_000, max_lifetime_ms: 1_800_000, test_on_checkout: true,
89 test_on_return: false,
90 }
91 }
92}
93
94impl PoolConfig {
95 #[must_use]
97 pub fn new(max_connections: usize) -> Self {
98 Self {
99 max_connections,
100 ..Default::default()
101 }
102 }
103
104 #[must_use]
106 pub fn min_connections(mut self, n: usize) -> Self {
107 self.min_connections = n;
108 self
109 }
110
111 #[must_use]
113 pub fn idle_timeout(mut self, ms: u64) -> Self {
114 self.idle_timeout_ms = ms;
115 self
116 }
117
118 #[must_use]
120 pub fn acquire_timeout(mut self, ms: u64) -> Self {
121 self.acquire_timeout_ms = ms;
122 self
123 }
124
125 #[must_use]
127 pub fn max_lifetime(mut self, ms: u64) -> Self {
128 self.max_lifetime_ms = ms;
129 self
130 }
131
132 #[must_use]
134 pub fn test_on_checkout(mut self, enabled: bool) -> Self {
135 self.test_on_checkout = enabled;
136 self
137 }
138
139 #[must_use]
141 pub fn test_on_return(mut self, enabled: bool) -> Self {
142 self.test_on_return = enabled;
143 self
144 }
145}
146
147#[derive(Debug, Clone, Default)]
149pub struct PoolStats {
150 pub total_connections: usize,
152 pub idle_connections: usize,
154 pub active_connections: usize,
156 pub pending_requests: usize,
158 pub connections_created: u64,
160 pub connections_closed: u64,
162 pub acquires: u64,
164 pub timeouts: u64,
166}
167
168#[derive(Debug)]
170struct ConnectionMeta<C> {
171 conn: C,
173 created_at: Instant,
175 last_used: Instant,
177}
178
179impl<C> ConnectionMeta<C> {
180 fn new(conn: C) -> Self {
181 let now = Instant::now();
182 Self {
183 conn,
184 created_at: now,
185 last_used: now,
186 }
187 }
188
189 fn touch(&mut self) {
190 self.last_used = Instant::now();
191 }
192
193 fn age(&self) -> Duration {
194 self.created_at.elapsed()
195 }
196
197 fn idle_time(&self) -> Duration {
198 self.last_used.elapsed()
199 }
200}
201
202struct PoolInner<C: Connection> {
204 config: PoolConfig,
206 idle: VecDeque<ConnectionMeta<C>>,
208 active_count: usize,
210 total_count: usize,
212 waiter_count: usize,
214 closed: bool,
216}
217
218impl<C: Connection> PoolInner<C> {
219 fn new(config: PoolConfig) -> Self {
220 Self {
221 config,
222 idle: VecDeque::new(),
223 active_count: 0,
224 total_count: 0,
225 waiter_count: 0,
226 closed: false,
227 }
228 }
229
230 fn can_create_new(&self) -> bool {
231 !self.closed && self.total_count < self.config.max_connections
232 }
233
234 fn stats(&self) -> PoolStats {
235 PoolStats {
236 total_connections: self.total_count,
237 idle_connections: self.idle.len(),
238 active_connections: self.active_count,
239 pending_requests: self.waiter_count,
240 ..Default::default()
241 }
242 }
243}
244
245struct PoolShared<C: Connection> {
247 inner: Mutex<PoolInner<C>>,
249 conn_available: Condvar,
251 connections_created: AtomicU64,
253 connections_closed: AtomicU64,
254 acquires: AtomicU64,
255 timeouts: AtomicU64,
256}
257
258impl<C: Connection> PoolShared<C> {
259 fn new(config: PoolConfig) -> Self {
260 Self {
261 inner: Mutex::new(PoolInner::new(config)),
262 conn_available: Condvar::new(),
263 connections_created: AtomicU64::new(0),
264 connections_closed: AtomicU64::new(0),
265 acquires: AtomicU64::new(0),
266 timeouts: AtomicU64::new(0),
267 }
268 }
269
270 fn lock_or_recover(&self) -> std::sync::MutexGuard<'_, PoolInner<C>> {
279 self.inner.lock().unwrap_or_else(|poisoned| {
280 tracing::error!(
281 "Pool mutex poisoned; recovering for read-only access. \
282 A thread panicked while holding the lock."
283 );
284 poisoned.into_inner()
285 })
286 }
287
288 #[allow(clippy::result_large_err)] fn lock_or_error(
295 &self,
296 operation: &'static str,
297 ) -> Result<std::sync::MutexGuard<'_, PoolInner<C>>, Error> {
298 self.inner
299 .lock()
300 .map_err(|_| Error::Pool(PoolError::poisoned(operation)))
301 }
302}
303
304fn close_connection_blocking<C: Connection>(conn: C, context: &'static str) {
305 let runtime = match RuntimeBuilder::current_thread().build() {
306 Ok(runtime) => runtime,
307 Err(error) => {
308 tracing::warn!(
309 context,
310 error = %error,
311 "failed to build runtime while closing pooled connection"
312 );
313 drop(conn);
314 return;
315 }
316 };
317 let cx = Cx::for_testing();
318 if let Err(error) = runtime.block_on(async { conn.close_for_pool(&cx).await }) {
319 tracing::warn!(
320 context,
321 error = %error,
322 "failed to close pooled connection explicitly"
323 );
324 }
325}
326
327fn close_connection_metas<C: Connection>(
328 metas: impl IntoIterator<Item = ConnectionMeta<C>>,
329 context: &'static str,
330) {
331 for meta in metas {
332 close_connection_blocking(meta.conn, context);
333 }
334}
335
336pub struct Pool<C: Connection> {
351 shared: Arc<PoolShared<C>>,
352}
353
354impl<C: Connection> Pool<C> {
355 #[must_use]
357 pub fn new(config: PoolConfig) -> Self {
358 Self {
359 shared: Arc::new(PoolShared::new(config)),
360 }
361 }
362
363 #[must_use]
365 pub fn config(&self) -> PoolConfig {
366 let inner = self.shared.lock_or_recover();
367 inner.config.clone()
368 }
369
370 #[must_use]
372 pub fn stats(&self) -> PoolStats {
373 let inner = self.shared.lock_or_recover();
374 let mut stats = inner.stats();
375 stats.connections_created = self.shared.connections_created.load(Ordering::Relaxed);
376 stats.connections_closed = self.shared.connections_closed.load(Ordering::Relaxed);
377 stats.acquires = self.shared.acquires.load(Ordering::Relaxed);
378 stats.timeouts = self.shared.timeouts.load(Ordering::Relaxed);
379 stats
380 }
381
382 #[must_use]
384 pub fn at_capacity(&self) -> bool {
385 let inner = self.shared.lock_or_recover();
386 inner.total_count >= inner.config.max_connections
387 }
388
389 #[must_use]
391 pub fn is_closed(&self) -> bool {
392 let inner = self.shared.lock_or_recover();
393 inner.closed
394 }
395
396 pub async fn acquire<F, Fut>(&self, cx: &Cx, factory: F) -> Outcome<PooledConnection<C>, Error>
411 where
412 F: Fn() -> Fut,
413 Fut: Future<Output = Outcome<C, Error>>,
414 {
415 let deadline = Instant::now() + Duration::from_millis(self.config().acquire_timeout_ms);
416 let test_on_checkout = self.config().test_on_checkout;
417 let max_lifetime = Duration::from_millis(self.config().max_lifetime_ms);
418 let idle_timeout = Duration::from_millis(self.config().idle_timeout_ms);
419
420 loop {
421 if cx.is_cancel_requested() {
423 return Outcome::Cancelled(CancelReason::user("pool acquire cancelled"));
424 }
425
426 if Instant::now() >= deadline {
428 self.shared.timeouts.fetch_add(1, Ordering::Relaxed);
429 return Outcome::Err(Error::Pool(PoolError {
430 kind: PoolErrorKind::Timeout,
431 message: "acquire timeout: no connections available".to_string(),
432 source: None,
433 }));
434 }
435
436 let (action, retired) = {
438 let mut inner = match self.shared.lock_or_error("acquire") {
439 Ok(guard) => guard,
440 Err(e) => return Outcome::Err(e),
441 };
442 let mut retired = Vec::new();
443
444 let action = if inner.closed {
445 AcquireAction::PoolClosed
446 } else {
447 let mut found_conn = None;
449 while let Some(mut meta) = inner.idle.pop_front() {
450 if meta.age() > max_lifetime {
452 inner.total_count -= 1;
453 self.shared
454 .connections_closed
455 .fetch_add(1, Ordering::Relaxed);
456 retired.push(meta);
457 continue;
458 }
459
460 if meta.idle_time() > idle_timeout {
462 inner.total_count -= 1;
463 self.shared
464 .connections_closed
465 .fetch_add(1, Ordering::Relaxed);
466 retired.push(meta);
467 continue;
468 }
469
470 meta.touch();
472 inner.active_count += 1;
473 found_conn = Some(meta);
474 break;
475 }
476
477 if let Some(meta) = found_conn {
478 AcquireAction::ValidateExisting(meta)
479 } else if inner.can_create_new() {
480 inner.total_count += 1;
482 inner.active_count += 1;
483 AcquireAction::CreateNew
484 } else {
485 inner.waiter_count += 1;
487 AcquireAction::Wait
488 }
489 };
490 (action, retired)
491 };
492
493 for meta in retired {
496 if let Err(error) = meta.conn.close_for_pool(cx).await {
497 tracing::warn!(
498 error = %error,
499 "failed to close expired pooled connection"
500 );
501 }
502 }
503
504 match action {
505 AcquireAction::PoolClosed => {
506 return Outcome::Err(Error::Pool(PoolError {
507 kind: PoolErrorKind::Closed,
508 message: "pool has been closed".to_string(),
509 source: None,
510 }));
511 }
512 AcquireAction::ValidateExisting(meta) => {
513 return self.validate_and_wrap(cx, meta, test_on_checkout).await;
515 }
516 AcquireAction::CreateNew => {
517 match factory().await {
519 Outcome::Ok(conn) => {
520 self.shared
521 .connections_created
522 .fetch_add(1, Ordering::Relaxed);
523 self.shared.acquires.fetch_add(1, Ordering::Relaxed);
524 let meta = ConnectionMeta::new(conn);
525 return Outcome::Ok(PooledConnection::new(
526 meta,
527 Arc::downgrade(&self.shared),
528 ));
529 }
530 Outcome::Err(e) => {
531 if let Ok(mut inner) = self.shared.lock_or_error("acquire_cleanup") {
533 inner.total_count -= 1;
534 inner.active_count -= 1;
535 }
536 return Outcome::Err(e);
538 }
539 Outcome::Cancelled(reason) => {
540 if let Ok(mut inner) = self.shared.lock_or_error("acquire_cleanup") {
541 inner.total_count -= 1;
542 inner.active_count -= 1;
543 }
544 return Outcome::Cancelled(reason);
545 }
546 Outcome::Panicked(info) => {
547 if let Ok(mut inner) = self.shared.lock_or_error("acquire_cleanup") {
548 inner.total_count -= 1;
549 inner.active_count -= 1;
550 }
551 return Outcome::Panicked(info);
552 }
553 }
554 }
555 AcquireAction::Wait => {
556 let remaining = deadline.saturating_duration_since(Instant::now());
558 if remaining.is_zero() {
559 if let Ok(mut inner) = self.shared.lock_or_error("acquire_timeout") {
560 inner.waiter_count -= 1;
561 }
562 self.shared.timeouts.fetch_add(1, Ordering::Relaxed);
563 return Outcome::Err(Error::Pool(PoolError {
564 kind: PoolErrorKind::Timeout,
565 message: "acquire timeout: no connections available".to_string(),
566 source: None,
567 }));
568 }
569
570 let wait_time = remaining.min(Duration::from_millis(100));
572 {
573 let inner = match self.shared.lock_or_error("acquire_wait") {
574 Ok(guard) => guard,
575 Err(e) => return Outcome::Err(e),
576 };
577 let _ = self
579 .shared
580 .conn_available
581 .wait_timeout(inner, wait_time)
582 .map_err(|_| {
583 tracing::error!("Pool mutex poisoned during wait_timeout");
584 });
585 }
586
587 {
589 if let Ok(mut inner) = self.shared.lock_or_error("acquire_wake") {
590 inner.waiter_count = inner.waiter_count.saturating_sub(1);
591 }
592 }
593
594 }
596 }
597 }
598 }
599
600 async fn validate_and_wrap(
602 &self,
603 cx: &Cx,
604 meta: ConnectionMeta<C>,
605 test_on_checkout: bool,
606 ) -> Outcome<PooledConnection<C>, Error> {
607 if test_on_checkout {
608 match meta.conn.ping(cx).await {
610 Outcome::Ok(()) => {
611 self.shared.acquires.fetch_add(1, Ordering::Relaxed);
612 Outcome::Ok(PooledConnection::new(meta, Arc::downgrade(&self.shared)))
613 }
614 Outcome::Err(_) | Outcome::Cancelled(_) | Outcome::Panicked(_) => {
615 {
617 if let Ok(mut inner) = self.shared.lock_or_error("validate_cleanup") {
618 inner.total_count -= 1;
619 inner.active_count -= 1;
620 }
621 }
622 self.shared
623 .connections_closed
624 .fetch_add(1, Ordering::Relaxed);
625 if let Err(error) = meta.conn.close_for_pool(cx).await {
626 tracing::warn!(
627 error = %error,
628 "failed to close pooled connection after checkout validation"
629 );
630 }
631 Outcome::Err(Error::Connection(ConnectionError {
633 kind: ConnectionErrorKind::Disconnected,
634 message: "connection validation failed".to_string(),
635 source: None,
636 }))
637 }
638 }
639 } else {
640 self.shared.acquires.fetch_add(1, Ordering::Relaxed);
641 Outcome::Ok(PooledConnection::new(meta, Arc::downgrade(&self.shared)))
642 }
643 }
644
645 pub fn clear_idle(&self) {
649 if let Ok(mut inner) = self.shared.inner.lock() {
650 let idle = inner.idle.drain(..).collect::<Vec<_>>();
651 let idle_count = idle.len();
652 inner.total_count -= idle_count;
653 self.shared
654 .connections_closed
655 .fetch_add(idle_count as u64, Ordering::Relaxed);
656 drop(inner);
657 close_connection_metas(idle, "pool clear_idle");
658 }
659 }
660
661 pub fn close(&self) {
662 match self.shared.inner.lock() {
663 Ok(mut inner) => {
664 inner.closed = true;
665
666 let idle = inner.idle.drain(..).collect::<Vec<_>>();
668 let idle_count = idle.len();
669 inner.total_count -= idle_count;
670 self.shared
671 .connections_closed
672 .fetch_add(idle_count as u64, Ordering::Relaxed);
673 drop(inner);
674 close_connection_metas(idle, "pool close");
675 }
676 Err(poisoned) => {
677 tracing::error!(
680 "Pool mutex poisoned during close; attempting recovery. \
681 Pool state may be inconsistent."
682 );
683 let mut inner = poisoned.into_inner();
684 inner.closed = true;
685 let idle = inner.idle.drain(..).collect::<Vec<_>>();
686 let idle_count = idle.len();
687 inner.total_count -= idle_count;
688 self.shared
689 .connections_closed
690 .fetch_add(idle_count as u64, Ordering::Relaxed);
691 drop(inner);
692 close_connection_metas(idle, "pool close poisoned");
693 }
694 }
695
696 self.shared.conn_available.notify_all();
698 }
699
700 #[must_use]
702 pub fn idle_count(&self) -> usize {
703 let inner = self.shared.lock_or_recover();
704 inner.idle.len()
705 }
706
707 #[must_use]
709 pub fn active_count(&self) -> usize {
710 let inner = self.shared.lock_or_recover();
711 inner.active_count
712 }
713
714 #[must_use]
716 pub fn total_count(&self) -> usize {
717 let inner = self.shared.lock_or_recover();
718 inner.total_count
719 }
720}
721
722impl<C: Connection> Drop for Pool<C> {
723 fn drop(&mut self) {
724 self.close();
725 }
726}
727
728enum AcquireAction<C> {
730 PoolClosed,
732 ValidateExisting(ConnectionMeta<C>),
734 CreateNew,
736 Wait,
738}
739
740pub struct PooledConnection<C: Connection> {
745 meta: Option<ConnectionMeta<C>>,
747 pool: Weak<PoolShared<C>>,
749}
750
751impl<C: Connection> PooledConnection<C> {
752 fn new(meta: ConnectionMeta<C>, pool: Weak<PoolShared<C>>) -> Self {
753 Self {
754 meta: Some(meta),
755 pool,
756 }
757 }
758
759 pub fn detach(mut self) -> C {
764 if let Some(pool) = self.pool.upgrade() {
765 match pool.inner.lock() {
768 Ok(mut inner) => {
769 inner.total_count -= 1;
770 inner.active_count -= 1;
771 pool.connections_closed.fetch_add(1, Ordering::Relaxed);
772 }
773 Err(_poisoned) => {
774 tracing::error!(
775 "Pool mutex poisoned during detach; pool counters will be inconsistent"
776 );
777 pool.connections_closed.fetch_add(1, Ordering::Relaxed);
779 }
780 }
781 }
782 self.meta.take().expect("connection already detached").conn
783 }
784
785 #[must_use]
787 pub fn age(&self) -> Duration {
788 self.meta.as_ref().map_or(Duration::ZERO, |m| m.age())
789 }
790
791 #[must_use]
793 pub fn idle_time(&self) -> Duration {
794 self.meta.as_ref().map_or(Duration::ZERO, |m| m.idle_time())
795 }
796}
797
798impl<C: Connection> std::ops::Deref for PooledConnection<C> {
799 type Target = C;
800
801 fn deref(&self) -> &Self::Target {
802 &self
803 .meta
804 .as_ref()
805 .expect("connection already returned to pool")
806 .conn
807 }
808}
809
810impl<C: Connection> std::ops::DerefMut for PooledConnection<C> {
811 fn deref_mut(&mut self) -> &mut Self::Target {
812 &mut self
813 .meta
814 .as_mut()
815 .expect("connection already returned to pool")
816 .conn
817 }
818}
819
820impl<C: Connection> Drop for PooledConnection<C> {
821 fn drop(&mut self) {
822 if let Some(mut meta) = self.meta.take() {
823 meta.touch(); if let Some(pool) = self.pool.upgrade() {
825 let mut inner = match pool.inner.lock() {
828 Ok(guard) => guard,
829 Err(_poisoned) => {
830 tracing::error!(
831 "Pool mutex poisoned during connection return; \
832 connection will be closed instead of returned. A thread panicked while holding the lock."
833 );
834 close_connection_blocking(meta.conn, "pooled connection drop poisoned");
835 return;
836 }
837 };
838
839 if inner.closed {
840 inner.total_count -= 1;
841 inner.active_count -= 1;
842 pool.connections_closed.fetch_add(1, Ordering::Relaxed);
843 drop(inner);
844 close_connection_blocking(meta.conn, "pooled connection drop closed pool");
845 return;
846 }
847
848 let max_lifetime = Duration::from_millis(inner.config.max_lifetime_ms);
850 if meta.age() > max_lifetime {
851 inner.total_count -= 1;
852 inner.active_count -= 1;
853 pool.connections_closed.fetch_add(1, Ordering::Relaxed);
854 drop(inner);
855 close_connection_blocking(meta.conn, "pooled connection drop max lifetime");
856 return;
857 }
858
859 inner.active_count -= 1;
860 inner.idle.push_back(meta);
861
862 drop(inner);
863 pool.conn_available.notify_one();
864 } else {
865 close_connection_blocking(meta.conn, "pooled connection drop missing pool");
866 }
867 }
868 }
869}
870
871impl<C: Connection + std::fmt::Debug> std::fmt::Debug for PooledConnection<C> {
872 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
873 f.debug_struct("PooledConnection")
874 .field("conn", &self.meta.as_ref().map(|m| &m.conn))
875 .field("age", &self.age())
876 .field("idle_time", &self.idle_time())
877 .finish_non_exhaustive()
878 }
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use sqlmodel_core::connection::{IsolationLevel, PreparedStatement, TransactionOps};
885 use sqlmodel_core::{Row, Value};
886 use std::sync::atomic::{AtomicBool, AtomicUsize};
887
888 #[derive(Debug)]
890 struct MockConnection {
891 id: u32,
892 ping_should_fail: Arc<AtomicBool>,
893 pool_close_calls: Arc<AtomicUsize>,
896 pool_shared: Option<Weak<PoolShared<MockConnection>>>,
899 pool_lock_was_free: Option<Arc<AtomicBool>>,
900 }
901
902 impl MockConnection {
903 fn new(id: u32) -> Self {
904 Self {
905 id,
906 ping_should_fail: Arc::new(AtomicBool::new(false)),
907 pool_close_calls: Arc::new(AtomicUsize::new(0)),
908 pool_shared: None,
909 pool_lock_was_free: None,
910 }
911 }
912
913 #[allow(dead_code)]
914 fn with_ping_behavior(id: u32, should_fail: Arc<AtomicBool>) -> Self {
915 Self {
916 id,
917 ping_should_fail: should_fail,
918 pool_close_calls: Arc::new(AtomicUsize::new(0)),
919 pool_shared: None,
920 pool_lock_was_free: None,
921 }
922 }
923
924 fn with_pool_close_counter(id: u32, pool_close_calls: Arc<AtomicUsize>) -> Self {
925 Self {
926 id,
927 ping_should_fail: Arc::new(AtomicBool::new(false)),
928 pool_close_calls,
929 pool_shared: None,
930 pool_lock_was_free: None,
931 }
932 }
933
934 fn with_pool_close_probe(
935 id: u32,
936 pool_close_calls: Arc<AtomicUsize>,
937 pool_shared: Weak<PoolShared<MockConnection>>,
938 pool_lock_was_free: Arc<AtomicBool>,
939 ) -> Self {
940 Self {
941 id,
942 ping_should_fail: Arc::new(AtomicBool::new(false)),
943 pool_close_calls,
944 pool_shared: Some(pool_shared),
945 pool_lock_was_free: Some(pool_lock_was_free),
946 }
947 }
948 }
949
950 struct MockTx;
952
953 #[allow(clippy::unused_async_trait_impl)]
956 impl TransactionOps for MockTx {
957 async fn query(&self, _cx: &Cx, _sql: &str, _params: &[Value]) -> Outcome<Vec<Row>, Error> {
958 Outcome::Ok(vec![])
959 }
960
961 async fn query_one(
962 &self,
963 _cx: &Cx,
964 _sql: &str,
965 _params: &[Value],
966 ) -> Outcome<Option<Row>, Error> {
967 Outcome::Ok(None)
968 }
969
970 async fn execute(&self, _cx: &Cx, _sql: &str, _params: &[Value]) -> Outcome<u64, Error> {
971 Outcome::Ok(0)
972 }
973
974 async fn savepoint(&self, _cx: &Cx, _name: &str) -> Outcome<(), Error> {
975 Outcome::Ok(())
976 }
977
978 async fn rollback_to(&self, _cx: &Cx, _name: &str) -> Outcome<(), Error> {
979 Outcome::Ok(())
980 }
981
982 async fn release(&self, _cx: &Cx, _name: &str) -> Outcome<(), Error> {
983 Outcome::Ok(())
984 }
985
986 async fn commit(self, _cx: &Cx) -> Outcome<(), Error> {
987 Outcome::Ok(())
988 }
989
990 async fn rollback(self, _cx: &Cx) -> Outcome<(), Error> {
991 Outcome::Ok(())
992 }
993 }
994
995 #[allow(clippy::unused_async_trait_impl)]
996 impl Connection for MockConnection {
997 type Tx<'conn> = MockTx;
998
999 async fn query(&self, _cx: &Cx, _sql: &str, _params: &[Value]) -> Outcome<Vec<Row>, Error> {
1000 Outcome::Ok(vec![])
1001 }
1002
1003 async fn query_one(
1004 &self,
1005 _cx: &Cx,
1006 _sql: &str,
1007 _params: &[Value],
1008 ) -> Outcome<Option<Row>, Error> {
1009 Outcome::Ok(None)
1010 }
1011
1012 async fn execute(&self, _cx: &Cx, _sql: &str, _params: &[Value]) -> Outcome<u64, Error> {
1013 Outcome::Ok(0)
1014 }
1015
1016 async fn insert(&self, _cx: &Cx, _sql: &str, _params: &[Value]) -> Outcome<i64, Error> {
1017 Outcome::Ok(0)
1018 }
1019
1020 async fn batch(
1021 &self,
1022 _cx: &Cx,
1023 _statements: &[(String, Vec<Value>)],
1024 ) -> Outcome<Vec<u64>, Error> {
1025 Outcome::Ok(vec![])
1026 }
1027
1028 async fn begin(&self, _cx: &Cx) -> Outcome<Self::Tx<'_>, Error> {
1029 Outcome::Ok(MockTx)
1030 }
1031
1032 async fn begin_with(
1033 &self,
1034 _cx: &Cx,
1035 _isolation: IsolationLevel,
1036 ) -> Outcome<Self::Tx<'_>, Error> {
1037 Outcome::Ok(MockTx)
1038 }
1039
1040 async fn prepare(&self, _cx: &Cx, _sql: &str) -> Outcome<PreparedStatement, Error> {
1041 Outcome::Ok(PreparedStatement::new(1, String::new(), 0))
1042 }
1043
1044 async fn query_prepared(
1045 &self,
1046 _cx: &Cx,
1047 _stmt: &PreparedStatement,
1048 _params: &[Value],
1049 ) -> Outcome<Vec<Row>, Error> {
1050 Outcome::Ok(vec![])
1051 }
1052
1053 async fn execute_prepared(
1054 &self,
1055 _cx: &Cx,
1056 _stmt: &PreparedStatement,
1057 _params: &[Value],
1058 ) -> Outcome<u64, Error> {
1059 Outcome::Ok(0)
1060 }
1061
1062 async fn ping(&self, _cx: &Cx) -> Outcome<(), Error> {
1063 if self.ping_should_fail.load(Ordering::Relaxed) {
1064 Outcome::Err(Error::Connection(ConnectionError {
1065 kind: ConnectionErrorKind::Disconnected,
1066 message: "mock ping failed".to_string(),
1067 source: None,
1068 }))
1069 } else {
1070 Outcome::Ok(())
1071 }
1072 }
1073
1074 async fn close(self, _cx: &Cx) -> Result<(), Error> {
1075 Ok(())
1076 }
1077
1078 async fn close_for_pool(self, _cx: &Cx) -> Result<(), Error> {
1079 if let (Some(pool_shared), Some(pool_lock_was_free)) =
1080 (self.pool_shared.as_ref(), self.pool_lock_was_free.as_ref())
1081 {
1082 let mutex_is_available = pool_shared
1083 .upgrade()
1084 .is_none_or(|shared| shared.inner.try_lock().is_ok());
1085 pool_lock_was_free.store(mutex_is_available, Ordering::Relaxed);
1086 }
1087 self.pool_close_calls.fetch_add(1, Ordering::Relaxed);
1088 Ok(())
1089 }
1090 }
1091
1092 #[test]
1093 fn test_config_default() {
1094 let config = PoolConfig::default();
1095 assert_eq!(config.min_connections, 1);
1096 assert_eq!(config.max_connections, 10);
1097 assert_eq!(config.idle_timeout_ms, 600_000);
1098 assert_eq!(config.acquire_timeout_ms, 30_000);
1099 assert_eq!(config.max_lifetime_ms, 1_800_000);
1100 assert!(config.test_on_checkout);
1101 assert!(!config.test_on_return);
1102 }
1103
1104 #[test]
1105 fn test_config_builder() {
1106 let config = PoolConfig::new(20)
1107 .min_connections(5)
1108 .idle_timeout(60_000)
1109 .acquire_timeout(5_000)
1110 .max_lifetime(300_000)
1111 .test_on_checkout(false)
1112 .test_on_return(true);
1113
1114 assert_eq!(config.min_connections, 5);
1115 assert_eq!(config.max_connections, 20);
1116 assert_eq!(config.idle_timeout_ms, 60_000);
1117 assert_eq!(config.acquire_timeout_ms, 5_000);
1118 assert_eq!(config.max_lifetime_ms, 300_000);
1119 assert!(!config.test_on_checkout);
1120 assert!(config.test_on_return);
1121 }
1122
1123 #[test]
1124 fn test_config_clone() {
1125 let config = PoolConfig::new(15).min_connections(3);
1126 let cloned = config.clone();
1127 assert_eq!(config.max_connections, cloned.max_connections);
1128 assert_eq!(config.min_connections, cloned.min_connections);
1129 }
1130
1131 #[test]
1132 fn test_stats_default() {
1133 let stats = PoolStats::default();
1134 assert_eq!(stats.total_connections, 0);
1135 assert_eq!(stats.idle_connections, 0);
1136 assert_eq!(stats.active_connections, 0);
1137 assert_eq!(stats.pending_requests, 0);
1138 assert_eq!(stats.connections_created, 0);
1139 assert_eq!(stats.connections_closed, 0);
1140 assert_eq!(stats.acquires, 0);
1141 assert_eq!(stats.timeouts, 0);
1142 }
1143
1144 #[test]
1145 fn test_stats_clone() {
1146 let stats = PoolStats {
1147 total_connections: 5,
1148 acquires: 100,
1149 ..Default::default()
1150 };
1151 let cloned = stats.clone();
1152 assert_eq!(stats.total_connections, cloned.total_connections);
1153 assert_eq!(stats.acquires, cloned.acquires);
1154 }
1155
1156 #[test]
1157 fn test_connection_meta_timing() {
1158 use std::thread;
1159
1160 struct DummyConn;
1162
1163 let meta = ConnectionMeta::new(DummyConn);
1164 let initial_age = meta.age();
1165
1166 thread::sleep(Duration::from_millis(10));
1168
1169 assert!(meta.age() > initial_age);
1171 assert!(meta.idle_time() > Duration::ZERO);
1172 }
1173
1174 #[test]
1175 fn test_connection_meta_touch() {
1176 use std::thread;
1177
1178 struct DummyConn;
1179
1180 let mut meta = ConnectionMeta::new(DummyConn);
1181
1182 thread::sleep(Duration::from_millis(10));
1184 let idle_before_touch = meta.idle_time();
1185 assert!(idle_before_touch > Duration::ZERO);
1186
1187 meta.touch();
1189 let idle_after_touch = meta.idle_time();
1190
1191 assert!(idle_after_touch < idle_before_touch);
1193 }
1194
1195 #[test]
1196 fn test_pool_new() {
1197 let config = PoolConfig::new(5);
1198 let pool: Pool<MockConnection> = Pool::new(config);
1199
1200 assert_eq!(pool.idle_count(), 0);
1202 assert_eq!(pool.active_count(), 0);
1203 assert_eq!(pool.total_count(), 0);
1204 assert!(!pool.is_closed());
1205 assert!(!pool.at_capacity());
1206 }
1207
1208 #[test]
1209 fn test_pool_config() {
1210 let config = PoolConfig::new(7).min_connections(2);
1211 let pool: Pool<MockConnection> = Pool::new(config);
1212
1213 let retrieved_config = pool.config();
1214 assert_eq!(retrieved_config.max_connections, 7);
1215 assert_eq!(retrieved_config.min_connections, 2);
1216 }
1217
1218 #[test]
1219 fn test_pool_stats_initial() {
1220 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1221
1222 let stats = pool.stats();
1223 assert_eq!(stats.total_connections, 0);
1224 assert_eq!(stats.idle_connections, 0);
1225 assert_eq!(stats.active_connections, 0);
1226 assert_eq!(stats.pending_requests, 0);
1227 assert_eq!(stats.connections_created, 0);
1228 assert_eq!(stats.connections_closed, 0);
1229 assert_eq!(stats.acquires, 0);
1230 assert_eq!(stats.timeouts, 0);
1231 }
1232
1233 #[test]
1234 fn test_pool_close() {
1235 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1236
1237 assert!(!pool.is_closed());
1238 pool.close();
1239 assert!(pool.is_closed());
1240 }
1241
1242 #[test]
1243 fn test_pool_close_routes_through_close_for_pool() {
1244 let pool_close_calls = Arc::new(AtomicUsize::new(0));
1245 let pool_lock_was_free = Arc::new(AtomicBool::new(false));
1246 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(2));
1247
1248 {
1252 let mut inner = pool
1253 .shared
1254 .inner
1255 .lock()
1256 .expect("pool mutex should not be poisoned");
1257 inner.total_count = 1;
1258 inner
1259 .idle
1260 .push_back(ConnectionMeta::new(MockConnection::with_pool_close_probe(
1261 1,
1262 Arc::clone(&pool_close_calls),
1263 Arc::downgrade(&pool.shared),
1264 Arc::clone(&pool_lock_was_free),
1265 )));
1266 }
1267
1268 pool.close();
1269
1270 assert!(pool.is_closed());
1271 assert_eq!(pool_close_calls.load(Ordering::Relaxed), 1);
1272 assert!(pool_lock_was_free.load(Ordering::Relaxed));
1273 }
1274
1275 #[test]
1276 fn test_expired_idle_connection_routes_through_close_for_pool() {
1277 let pool_close_calls = Arc::new(AtomicUsize::new(0));
1278 let pool: Pool<MockConnection> =
1279 Pool::new(PoolConfig::new(2).max_lifetime(1).test_on_checkout(false));
1280 let mut expired = ConnectionMeta::new(MockConnection::with_pool_close_counter(
1281 1,
1282 Arc::clone(&pool_close_calls),
1283 ));
1284 expired.created_at = Instant::now()
1285 .checked_sub(Duration::from_secs(1))
1286 .expect("one second must fit before the current instant");
1287 {
1288 let mut inner = pool
1289 .shared
1290 .inner
1291 .lock()
1292 .expect("pool mutex should not be poisoned");
1293 inner.total_count = 1;
1294 inner.idle.push_back(expired);
1295 }
1296
1297 let runtime = RuntimeBuilder::current_thread()
1298 .build()
1299 .expect("build test runtime");
1300 let cx = Cx::for_testing();
1301 let acquired =
1302 runtime.block_on(pool.acquire(&cx, || async { Outcome::Ok(MockConnection::new(2)) }));
1303
1304 assert!(matches!(acquired, Outcome::Ok(_)));
1305 assert_eq!(pool_close_calls.load(Ordering::Relaxed), 1);
1306 }
1307
1308 #[test]
1309 fn test_failed_validation_routes_through_close_for_pool() {
1310 let pool_close_calls = Arc::new(AtomicUsize::new(0));
1311 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(2).test_on_checkout(true));
1312 let failed = MockConnection::with_pool_close_counter(1, Arc::clone(&pool_close_calls));
1313 failed.ping_should_fail.store(true, Ordering::Relaxed);
1314 {
1315 let mut inner = pool
1316 .shared
1317 .inner
1318 .lock()
1319 .expect("pool mutex should not be poisoned");
1320 inner.total_count = 1;
1321 inner.idle.push_back(ConnectionMeta::new(failed));
1322 }
1323
1324 let runtime = RuntimeBuilder::current_thread()
1325 .build()
1326 .expect("build test runtime");
1327 let cx = Cx::for_testing();
1328 let acquired =
1329 runtime.block_on(pool.acquire(&cx, || async { Outcome::Ok(MockConnection::new(2)) }));
1330
1331 assert!(matches!(acquired, Outcome::Err(_)));
1332 assert_eq!(pool_close_calls.load(Ordering::Relaxed), 1);
1333 }
1334
1335 #[test]
1336 fn test_pool_inner_can_create_new() {
1337 let mut inner = PoolInner::<MockConnection>::new(PoolConfig::new(3));
1338
1339 assert!(inner.can_create_new());
1341
1342 inner.total_count = 3;
1344 assert!(!inner.can_create_new());
1345
1346 inner.total_count = 2;
1348 assert!(inner.can_create_new());
1349
1350 inner.closed = true;
1352 assert!(!inner.can_create_new());
1353 }
1354
1355 #[test]
1356 fn test_pool_inner_stats() {
1357 let mut inner = PoolInner::<MockConnection>::new(PoolConfig::new(10));
1358
1359 inner.total_count = 5;
1360 inner.active_count = 3;
1361 inner.waiter_count = 2;
1362 inner
1363 .idle
1364 .push_back(ConnectionMeta::new(MockConnection::new(1)));
1365 inner
1366 .idle
1367 .push_back(ConnectionMeta::new(MockConnection::new(2)));
1368
1369 let stats = inner.stats();
1370 assert_eq!(stats.total_connections, 5);
1371 assert_eq!(stats.idle_connections, 2);
1372 assert_eq!(stats.active_connections, 3);
1373 assert_eq!(stats.pending_requests, 2);
1374 }
1375
1376 #[test]
1377 fn test_pooled_connection_age_and_idle_time() {
1378 use std::thread;
1379
1380 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1381
1382 {
1384 let mut inner = pool.shared.inner.lock().unwrap();
1385 inner.total_count = 1;
1386 inner.active_count = 1;
1387 }
1388
1389 let meta = ConnectionMeta::new(MockConnection::new(1));
1390 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1391
1392 assert!(pooled.age() >= Duration::ZERO);
1394
1395 thread::sleep(Duration::from_millis(5));
1396 assert!(pooled.age() > Duration::ZERO);
1397 }
1398
1399 #[test]
1400 fn test_pooled_connection_detach() {
1401 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1402
1403 {
1405 let mut inner = pool.shared.inner.lock().unwrap();
1406 inner.total_count = 1;
1407 inner.active_count = 1;
1408 }
1409
1410 let meta = ConnectionMeta::new(MockConnection::new(42));
1411 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1412
1413 assert_eq!(pool.total_count(), 1);
1415 assert_eq!(pool.active_count(), 1);
1416
1417 let conn = pooled.detach();
1419 assert_eq!(conn.id, 42);
1420
1421 assert_eq!(pool.total_count(), 0);
1423 assert_eq!(pool.active_count(), 0);
1424
1425 let stats = pool.stats();
1427 assert_eq!(stats.connections_closed, 1);
1428 }
1429
1430 #[test]
1431 fn test_pooled_connection_drop_returns_to_pool() {
1432 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1433
1434 {
1436 let mut inner = pool.shared.inner.lock().unwrap();
1437 inner.total_count = 1;
1438 inner.active_count = 1;
1439 }
1440
1441 let meta = ConnectionMeta::new(MockConnection::new(1));
1442 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1443
1444 assert_eq!(pool.active_count(), 1);
1446 assert_eq!(pool.idle_count(), 0);
1447
1448 drop(pooled);
1450
1451 assert_eq!(pool.active_count(), 0);
1453 assert_eq!(pool.idle_count(), 1);
1454 assert_eq!(pool.total_count(), 1); }
1456
1457 #[test]
1458 fn test_pooled_connection_drop_when_pool_closed() {
1459 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1460
1461 {
1463 let mut inner = pool.shared.inner.lock().unwrap();
1464 inner.total_count = 1;
1465 inner.active_count = 1;
1466 }
1467
1468 let meta = ConnectionMeta::new(MockConnection::new(1));
1469 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1470
1471 pool.close();
1473
1474 drop(pooled);
1476
1477 assert_eq!(pool.idle_count(), 0);
1479 assert_eq!(pool.active_count(), 0);
1480 assert_eq!(pool.total_count(), 0);
1481
1482 assert_eq!(pool.stats().connections_closed, 1);
1484 }
1485
1486 #[test]
1487 fn test_pooled_connection_deref() {
1488 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1489
1490 {
1492 let mut inner = pool.shared.inner.lock().unwrap();
1493 inner.total_count = 1;
1494 inner.active_count = 1;
1495 }
1496
1497 let meta = ConnectionMeta::new(MockConnection::new(99));
1498 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1499
1500 assert_eq!(pooled.id, 99);
1502 }
1503
1504 #[test]
1505 fn test_pooled_connection_deref_mut() {
1506 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1507
1508 {
1510 let mut inner = pool.shared.inner.lock().unwrap();
1511 inner.total_count = 1;
1512 inner.active_count = 1;
1513 }
1514
1515 let meta = ConnectionMeta::new(MockConnection::new(1));
1516 let mut pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1517
1518 pooled.id = 50;
1520 assert_eq!(pooled.id, 50);
1521 }
1522
1523 #[test]
1524 fn test_pooled_connection_debug() {
1525 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1526
1527 {
1529 let mut inner = pool.shared.inner.lock().unwrap();
1530 inner.total_count = 1;
1531 inner.active_count = 1;
1532 }
1533
1534 let meta = ConnectionMeta::new(MockConnection::new(1));
1535 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1536
1537 let debug_str = format!("{:?}", pooled);
1538 assert!(debug_str.contains("PooledConnection"));
1539 assert!(debug_str.contains("age"));
1540 }
1541
1542 #[test]
1543 fn test_pool_at_capacity() {
1544 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(2));
1545
1546 assert!(!pool.at_capacity());
1547
1548 {
1550 let mut inner = pool.shared.inner.lock().unwrap();
1551 inner.total_count = 1;
1552 }
1553 assert!(!pool.at_capacity());
1554
1555 {
1556 let mut inner = pool.shared.inner.lock().unwrap();
1557 inner.total_count = 2;
1558 }
1559 assert!(pool.at_capacity());
1560 }
1561
1562 #[test]
1563 fn test_acquire_action_enum() {
1564 let closed: AcquireAction<MockConnection> = AcquireAction::PoolClosed;
1566 assert!(matches!(closed, AcquireAction::PoolClosed));
1567
1568 let create: AcquireAction<MockConnection> = AcquireAction::CreateNew;
1569 assert!(matches!(create, AcquireAction::CreateNew));
1570
1571 let wait: AcquireAction<MockConnection> = AcquireAction::Wait;
1572 assert!(matches!(wait, AcquireAction::Wait));
1573
1574 let meta = ConnectionMeta::new(MockConnection::new(1));
1575 let validate: AcquireAction<MockConnection> = AcquireAction::ValidateExisting(meta);
1576 assert!(matches!(validate, AcquireAction::ValidateExisting(_)));
1577 }
1578
1579 #[test]
1580 fn test_pool_shared_atomic_counters() {
1581 let shared = PoolShared::<MockConnection>::new(PoolConfig::new(5));
1582
1583 assert_eq!(shared.connections_created.load(Ordering::Relaxed), 0);
1585 assert_eq!(shared.connections_closed.load(Ordering::Relaxed), 0);
1586 assert_eq!(shared.acquires.load(Ordering::Relaxed), 0);
1587 assert_eq!(shared.timeouts.load(Ordering::Relaxed), 0);
1588
1589 shared.connections_created.fetch_add(1, Ordering::Relaxed);
1591 shared.connections_closed.fetch_add(2, Ordering::Relaxed);
1592 shared.acquires.fetch_add(10, Ordering::Relaxed);
1593 shared.timeouts.fetch_add(3, Ordering::Relaxed);
1594
1595 assert_eq!(shared.connections_created.load(Ordering::Relaxed), 1);
1596 assert_eq!(shared.connections_closed.load(Ordering::Relaxed), 2);
1597 assert_eq!(shared.acquires.load(Ordering::Relaxed), 10);
1598 assert_eq!(shared.timeouts.load(Ordering::Relaxed), 3);
1599 }
1600
1601 #[test]
1602 fn test_pool_close_clears_idle() {
1603 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1604
1605 {
1607 let mut inner = pool.shared.inner.lock().unwrap();
1608 inner.total_count = 3;
1609 inner
1610 .idle
1611 .push_back(ConnectionMeta::new(MockConnection::new(1)));
1612 inner
1613 .idle
1614 .push_back(ConnectionMeta::new(MockConnection::new(2)));
1615 inner
1616 .idle
1617 .push_back(ConnectionMeta::new(MockConnection::new(3)));
1618 }
1619
1620 assert_eq!(pool.idle_count(), 3);
1621 assert_eq!(pool.total_count(), 3);
1622
1623 pool.close();
1624
1625 assert_eq!(pool.idle_count(), 0);
1627 assert_eq!(pool.total_count(), 0);
1628 assert!(pool.is_closed());
1629
1630 assert_eq!(pool.stats().connections_closed, 3);
1632 }
1633
1634 fn poison_pool_mutex() -> Pool<MockConnection> {
1647 use std::panic;
1648 use std::thread;
1649
1650 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1651
1652 {
1654 let mut inner = pool.shared.inner.lock().unwrap();
1655 inner.total_count = 2;
1656 inner.active_count = 1;
1657 inner
1658 .idle
1659 .push_back(ConnectionMeta::new(MockConnection::new(1)));
1660 }
1661
1662 let shared_clone = Arc::clone(&pool.shared);
1664 let handle = thread::spawn(move || {
1665 let _guard = shared_clone.inner.lock().unwrap();
1666 panic!("intentional panic to poison mutex");
1668 });
1669
1670 let _ = handle.join();
1672
1673 assert!(pool.shared.inner.lock().is_err());
1675
1676 pool
1677 }
1678
1679 #[test]
1682 fn test_config_after_poisoning_returns_valid_data() {
1683 let pool = poison_pool_mutex();
1684
1685 let config = pool.config();
1687 assert_eq!(config.max_connections, 5);
1688 }
1689
1690 #[test]
1691 fn test_stats_after_poisoning_returns_valid_data() {
1692 let pool = poison_pool_mutex();
1693
1694 let stats = pool.stats();
1696 assert_eq!(stats.total_connections, 2);
1698 assert_eq!(stats.active_connections, 1);
1699 assert_eq!(stats.idle_connections, 1);
1700 }
1701
1702 #[test]
1703 fn test_at_capacity_after_poisoning() {
1704 let pool = poison_pool_mutex();
1705
1706 assert!(!pool.at_capacity());
1709 }
1710
1711 #[test]
1712 fn test_is_closed_after_poisoning() {
1713 let pool = poison_pool_mutex();
1714
1715 assert!(!pool.is_closed());
1717 }
1718
1719 #[test]
1720 fn test_idle_count_after_poisoning() {
1721 let pool = poison_pool_mutex();
1722
1723 assert_eq!(pool.idle_count(), 1);
1725 }
1726
1727 #[test]
1728 fn test_active_count_after_poisoning() {
1729 let pool = poison_pool_mutex();
1730
1731 assert_eq!(pool.active_count(), 1);
1733 }
1734
1735 #[test]
1736 fn test_total_count_after_poisoning() {
1737 let pool = poison_pool_mutex();
1738
1739 assert_eq!(pool.total_count(), 2);
1741 }
1742
1743 #[test]
1746 fn test_lock_or_error_returns_error_when_poisoned() {
1747 use std::thread;
1748
1749 let shared = Arc::new(PoolShared::<MockConnection>::new(PoolConfig::new(5)));
1750
1751 let shared_clone = Arc::clone(&shared);
1753 let handle = thread::spawn(move || {
1754 let _guard = shared_clone.inner.lock().unwrap();
1755 panic!("intentional panic to poison mutex");
1756 });
1757 let _ = handle.join();
1758
1759 let result = shared.lock_or_error("test_operation");
1761
1762 match result {
1764 Err(Error::Pool(pool_err)) => {
1765 assert!(matches!(pool_err.kind, PoolErrorKind::Poisoned));
1766 assert!(pool_err.message.contains("poisoned"));
1767 }
1768 Err(other) => panic!("Expected Pool error, got: {:?}", other),
1769 Ok(_) => panic!("Expected error, got Ok"),
1770 }
1771 }
1772
1773 #[test]
1774 fn test_lock_or_recover_succeeds_when_poisoned() {
1775 use std::thread;
1776
1777 let shared = Arc::new(PoolShared::<MockConnection>::new(PoolConfig::new(5)));
1778
1779 {
1781 let mut inner = shared.inner.lock().unwrap();
1782 inner.total_count = 42;
1783 }
1784
1785 let shared_clone = Arc::clone(&shared);
1787 let handle = thread::spawn(move || {
1788 let _guard = shared_clone.inner.lock().unwrap();
1789 panic!("intentional panic to poison mutex");
1790 });
1791 let _ = handle.join();
1792
1793 assert!(shared.inner.lock().is_err());
1795
1796 let inner = shared.lock_or_recover();
1798 assert_eq!(inner.total_count, 42);
1799 }
1800
1801 #[test]
1802 fn test_close_after_poisoning_recovers_and_closes() {
1803 let pool = poison_pool_mutex();
1804
1805 pool.close();
1807
1808 assert!(pool.is_closed());
1810
1811 assert_eq!(pool.idle_count(), 0);
1813 }
1814
1815 #[test]
1818 fn test_drop_pooled_connection_after_poisoning_does_not_panic() {
1819 use std::panic;
1820 use std::thread;
1821
1822 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1823
1824 {
1826 let mut inner = pool.shared.inner.lock().unwrap();
1827 inner.total_count = 1;
1828 inner.active_count = 1;
1829 }
1830
1831 let meta = ConnectionMeta::new(MockConnection::new(1));
1833 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1834
1835 let shared_clone = Arc::clone(&pool.shared);
1837 let handle = thread::spawn(move || {
1838 let _guard = shared_clone.inner.lock().unwrap();
1839 panic!("intentional panic to poison mutex");
1840 });
1841 let _ = handle.join();
1842
1843 assert!(pool.shared.inner.lock().is_err());
1845
1846 let drop_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
1849 drop(pooled);
1850 }));
1851
1852 assert!(
1854 drop_result.is_ok(),
1855 "Dropping PooledConnection after mutex poisoning should not panic"
1856 );
1857 }
1858
1859 #[test]
1860 fn test_detach_after_poisoning_does_not_panic() {
1861 use std::panic;
1862 use std::thread;
1863
1864 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1865
1866 {
1868 let mut inner = pool.shared.inner.lock().unwrap();
1869 inner.total_count = 1;
1870 inner.active_count = 1;
1871 }
1872
1873 let meta = ConnectionMeta::new(MockConnection::new(42));
1875 let pooled = PooledConnection::new(meta, Arc::downgrade(&pool.shared));
1876
1877 let shared_clone = Arc::clone(&pool.shared);
1879 let handle = thread::spawn(move || {
1880 let _guard = shared_clone.inner.lock().unwrap();
1881 panic!("intentional panic to poison mutex");
1882 });
1883 let _ = handle.join();
1884
1885 assert!(pool.shared.inner.lock().is_err());
1887
1888 let detach_result = panic::catch_unwind(panic::AssertUnwindSafe(|| pooled.detach()));
1890
1891 assert!(
1892 detach_result.is_ok(),
1893 "detach() after mutex poisoning should not panic"
1894 );
1895
1896 let conn = detach_result.unwrap();
1898 assert_eq!(conn.id, 42);
1899 }
1900
1901 #[test]
1904 fn test_pool_survives_thread_panic_during_acquire() {
1905 use std::thread;
1906
1907 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1908 let pool_arc = Arc::new(pool);
1909
1910 let pool_clone = Arc::clone(&pool_arc);
1913 let handle = thread::spawn(move || {
1914 {
1916 let mut inner = pool_clone.shared.inner.lock().unwrap();
1917 inner.total_count = 1;
1918 inner.active_count = 1;
1919 }
1920
1921 let _guard = pool_clone.shared.inner.lock().unwrap();
1924 panic!("simulated panic during database operation");
1925 });
1926
1927 let _ = handle.join();
1929
1930 assert_eq!(pool_arc.total_count(), 1);
1932 assert_eq!(pool_arc.config().max_connections, 5);
1933
1934 let stats = pool_arc.stats();
1936 assert_eq!(stats.total_connections, 1);
1937 }
1938
1939 #[test]
1940 fn test_pool_close_after_thread_panic() {
1941 use std::thread;
1942
1943 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1944
1945 {
1947 let mut inner = pool.shared.inner.lock().unwrap();
1948 inner.total_count = 2;
1949 inner
1950 .idle
1951 .push_back(ConnectionMeta::new(MockConnection::new(1)));
1952 inner
1953 .idle
1954 .push_back(ConnectionMeta::new(MockConnection::new(2)));
1955 }
1956
1957 let shared_clone = Arc::clone(&pool.shared);
1959 let handle = thread::spawn(move || {
1960 let _guard = shared_clone.inner.lock().unwrap();
1961 panic!("intentional panic");
1962 });
1963 let _ = handle.join();
1964
1965 pool.close();
1967
1968 assert!(pool.is_closed());
1970 assert_eq!(pool.idle_count(), 0);
1971 }
1972
1973 #[test]
1974 fn test_multiple_reads_after_poisoning() {
1975 let pool = poison_pool_mutex();
1976
1977 for _ in 0..10 {
1979 let _ = pool.config();
1980 let _ = pool.stats();
1981 let _ = pool.at_capacity();
1982 let _ = pool.is_closed();
1983 let _ = pool.idle_count();
1984 let _ = pool.active_count();
1985 let _ = pool.total_count();
1986 }
1987
1988 assert_eq!(pool.total_count(), 2);
1990 }
1991
1992 #[test]
1993 fn test_waiters_count_after_poisoning() {
1994 use std::thread;
1995
1996 let pool: Pool<MockConnection> = Pool::new(PoolConfig::new(5));
1997
1998 {
2000 let mut inner = pool.shared.inner.lock().unwrap();
2001 inner.waiter_count = 3;
2002 }
2003
2004 let shared_clone = Arc::clone(&pool.shared);
2006 let handle = thread::spawn(move || {
2007 let _guard = shared_clone.inner.lock().unwrap();
2008 panic!("intentional panic");
2009 });
2010 let _ = handle.join();
2011
2012 let stats = pool.stats();
2014 assert_eq!(stats.pending_requests, 3);
2015 }
2016}