bb8-0.9.1/.cargo_vcs_info.json0000644000000001410000000000100115120ustar { "git": { "sha1": "70c96d69bfdff4fbf1cf5a770bf5a02b89426ecb" }, "path_in_vcs": "bb8" }bb8-0.9.1/Cargo.lock0000644000000117210000000000100074730ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "bb8" version = "0.9.1" dependencies = [ "futures-util", "parking_lot", "portable-atomic", "tokio", ] [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", "pin-project-lite", "pin-utils", ] [[package]] name = "libc" version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "syn" version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tokio" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "parking_lot", "pin-project-lite", "tokio-macros", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" bb8-0.9.1/Cargo.toml0000644000000025640000000000100075230ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.75" name = "bb8" version = "0.9.1" build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Full-featured async (tokio-based) connection pool (like r2d2)" readme = "README.md" license = "MIT" repository = "https://github.com/djc/bb8" [features] default = ["parking_lot"] parking_lot = [ "dep:parking_lot", "tokio/parking_lot", ] [lib] name = "bb8" path = "src/lib.rs" [[test]] name = "test" path = "tests/test.rs" [dependencies.futures-util] version = "0.3.2" features = ["alloc"] default-features = false [dependencies.parking_lot] version = "0.12" optional = true [dependencies.tokio] version = "1.0" features = [ "rt", "sync", "time", ] [dev-dependencies.tokio] version = "1.0" features = ["macros"] [target.'cfg(not(target_has_atomic = "64"))'.dependencies.portable-atomic] version = "1" bb8-0.9.1/Cargo.toml.orig000064400000000000000000000013171046102023000131770ustar 00000000000000[package] name = "bb8" version = "0.9.1" edition = "2021" rust-version = "1.75" description = "Full-featured async (tokio-based) connection pool (like r2d2)" license = "MIT" repository = "https://github.com/djc/bb8" workspace = ".." readme = "../README.md" [dependencies] futures-util = { version = "0.3.2", default-features = false, features = ["alloc"] } parking_lot = { version = "0.12", optional = true } tokio = { version = "1.0", features = ["rt", "sync", "time"] } [target.'cfg(not(target_has_atomic = "64"))'.dependencies] portable-atomic = "1" [dev-dependencies] tokio = { version = "1.0", features = ["macros"] } [features] parking_lot = ["dep:parking_lot", "tokio/parking_lot"] default = ["parking_lot"] bb8-0.9.1/LICENSE000064400000000000000000000020641046102023000113150ustar 00000000000000The MIT License (MIT) Copyright (c) 2018 Kyle Huey Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. bb8-0.9.1/README.md000064400000000000000000000064561046102023000116000ustar 00000000000000# bb8 [![Documentation](https://docs.rs/bb8/badge.svg)](https://docs.rs/bb8/) [![Crates.io](https://img.shields.io/crates/v/bb8.svg)](https://crates.io/crates/bb8) [![Build status](https://github.com/djc/bb8/workflows/CI/badge.svg)](https://github.com/djc/bb8/actions?query=workflow%3ACI) [![codecov](https://codecov.io/gh/djc/bb8/branch/main/graph/badge.svg)](https://codecov.io/gh/djc/bb8) [![Chat](https://img.shields.io/discord/976380008299917365?logo=discord)](https://discord.gg/E4DuUP83V2) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) A full-featured connection pool, designed for asynchronous connections (using tokio). Originally based on [r2d2](https://github.com/sfackler/r2d2). Opening a new database connection every time one is needed is both inefficient and can lead to resource exhaustion under high traffic conditions. A connection pool maintains a set of open connections to a database, handing them out for repeated use. bb8 is agnostic to the connection type it is managing. Implementors of the `ManageConnection` trait provide the database-specific logic to create and check the health of connections. A (possibly not exhaustive) list of adapters for different backends: Backend | Adapter Crate ------- | ------------- [tokio-postgres](https://github.com/sfackler/rust-postgres) | [bb8-postgres](https://crates.io/crates/bb8-postgres) (in-tree) [redis](https://github.com/mitsuhiko/redis-rs) | [bb8-redis](https://crates.io/crates/bb8-redis) (in-tree) [redis_cluster_async](https://crates.io/crates/redis_cluster_async) | [bb8-redis-cluster](https://crates.io/crates/bb8-redis-cluster) [rsmq](https://github.com/smrchy/rsmq) | [rsmq_async](https://crates.io/crates/rsmq_async) [bolt-client](https://crates.io/crates/bolt-client) | [bb8-bolt](https://crates.io/crates/bb8-bolt) [diesel](https://crates.io/crates/diesel) | [diesel_async](https://github.com/weiznich/diesel_async) [tiberius](https://crates.io/crates/tiberius) | [bb8-tiberius](https://crates.io/crates/bb8-tiberius) [nebula-client](https://crates.io/crates/nebula-client) | [bb8-nebula](https://crates.io/crates/bb8-nebula) [memcache-async](https://github.com/vavrusa/memcache-async) | [bb8-memcached](https://crates.io/crates/bb8-memcached) [lapin](https://crates.io/crates/lapin) | [bb8-lapin](https://crates.io/crates/bb8-lapin) [arangors](https://crates.io/crates/arangors) | [bb8-arangodb](https://crates.io/crates/bb8-arangodb) [tonic](https://crates.io/crates/tonic) | [bb8-tonic](https://crates.io/crates/bb8-tonic) ## Example Using an imaginary "foodb" database. ```rust #[tokio::main] async fn main() { let manager = bb8_foodb::FooConnectionManager::new("localhost:1234"); let pool = bb8::Pool::builder() .max_size(15) .build(manager) .await .unwrap(); for _ in 0..20 { let pool = pool.clone(); tokio::spawn(async move { let conn = pool.get().await.unwrap(); // use the connection // it will be returned to the pool when it falls out of scope. }); } } ``` ## License Licensed under the MIT license ([LICENSE](LICENSE)). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be licensed as above, without any additional terms or conditions. bb8-0.9.1/src/api.rs000064400000000000000000000505151046102023000122220ustar 00000000000000use std::borrow::Cow; use std::error; use std::fmt; use std::future::Future; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::time::Duration; use crate::inner::PoolInner; use crate::internals::Conn; /// A generic connection pool. pub struct Pool { pub(crate) inner: PoolInner, } impl Pool { /// Returns a `Builder` instance to configure a new pool. pub fn builder() -> Builder { Builder::new() } /// Retrieves a connection from the pool. pub async fn get(&self) -> Result, RunError> { self.inner.get().await } /// Retrieves an owned connection from the pool /// /// Using an owning `PooledConnection` makes it easier to leak the connection pool. Therefore, [`Pool::get`] /// (which stores a lifetime-bound reference to the pool) should be preferred whenever possible. pub async fn get_owned(&self) -> Result, RunError> { Ok(PooledConnection { conn: self.get().await?.take(), pool: Cow::Owned(self.inner.clone()), state: ConnectionState::Present, }) } /// Get a new dedicated connection that will not be managed by the pool. /// An application may want a persistent connection (e.g. to do a /// postgres LISTEN) that will not be closed or repurposed by the pool. /// /// This method allows reusing the manager's configuration but otherwise /// bypassing the pool pub async fn dedicated_connection(&self) -> Result { self.inner.connect().await } /// Adds a connection to the pool. /// /// If the connection is broken, or the pool is at capacity, the /// connection is not added and instead returned to the caller in Err. pub fn add(&self, conn: M::Connection) -> Result<(), AddError> { self.inner.try_put(conn) } /// Returns information about the current state of the pool. pub fn state(&self) -> State { self.inner.state() } /// Expose a representation of the original pool configuration pub fn config(&self) -> Config { Config::from(self.inner.builder()) } } impl Clone for Pool { fn clone(&self) -> Self { Pool { inner: self.inner.clone(), } } } impl fmt::Debug for Pool { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_fmt(format_args!("Pool({:?})", self.inner)) } } /// Information about the state of a `Pool`. #[derive(Debug)] #[non_exhaustive] pub struct State { /// The number of connections currently being managed by the pool. pub connections: u32, /// The number of idle connections. pub idle_connections: u32, /// Statistics about the historical usage of the pool. pub statistics: Statistics, } /// Statistics about the historical usage of the `Pool`. #[derive(Debug, Default)] #[non_exhaustive] pub struct Statistics { /// Total gets started. /// /// This counter is incremented before the `get` operation starts waiting, /// so that it's possible to monitor the size of the queue by computing the /// difference with the other `get_*` statistics. pub get_started: u64, /// Total gets completed that did not have to wait for a connection. pub get_direct: u64, /// Total gets completed that had to wait for a connection available. pub get_waited: u64, /// Total gets completed that timed out while waiting for a connection. pub get_timed_out: u64, /// Total time accumulated waiting for a connection. pub get_wait_time: Duration, /// Total connections created. pub connections_created: u64, /// Total connections that were closed due to be in broken state. pub connections_closed_broken: u64, /// Total connections that were closed due to be considered invalid. pub connections_closed_invalid: u64, /// Total connections that were closed because they reached the max /// lifetime. pub connections_closed_max_lifetime: u64, /// Total connections that were closed because they reached the max /// idle timeout. pub connections_closed_idle_timeout: u64, } impl Statistics { /// Total pending gets waiting for a connection. pub fn pending_gets(&self) -> u64 { self.get_started - self.completed_gets() } /// Total gets completed. pub fn completed_gets(&self) -> u64 { self.get_direct + self.get_waited + self.get_timed_out } } #[non_exhaustive] #[derive(Debug)] pub struct Config { /// The maximum number of connections allowed. pub max_size: u32, /// The minimum idle connection count the pool will attempt to maintain. pub min_idle: Option, /// Whether or not to test the connection on checkout. pub test_on_check_out: bool, /// The maximum lifetime, if any, that a connection is allowed. pub max_lifetime: Option, /// The duration, if any, after which idle_connections in excess of `min_idle` are closed. pub idle_timeout: Option, /// The duration to wait to start a connection before giving up. pub connection_timeout: Duration, /// Enable/disable automatic retries on connection creation. pub retry_connection: bool, /// The time interval used to wake up and reap connections. pub reaper_rate: Duration, /// Queue strategy (FIFO or LIFO) pub queue_strategy: QueueStrategy, } impl From<&Builder> for Config { fn from(builder: &Builder) -> Self { let Builder { max_size, min_idle, test_on_check_out, max_lifetime, idle_timeout, connection_timeout, retry_connection, error_sink: _, reaper_rate, queue_strategy, connection_customizer: _, _p: _, } = builder; Self { max_size: *max_size, min_idle: *min_idle, test_on_check_out: *test_on_check_out, max_lifetime: *max_lifetime, idle_timeout: *idle_timeout, connection_timeout: *connection_timeout, retry_connection: *retry_connection, reaper_rate: *reaper_rate, queue_strategy: *queue_strategy, } } } /// A builder for a connection pool. #[derive(Debug)] pub struct Builder { /// The maximum number of connections allowed. pub(crate) max_size: u32, /// The minimum idle connection count the pool will attempt to maintain. pub(crate) min_idle: Option, /// Whether or not to test the connection on checkout. pub(crate) test_on_check_out: bool, /// The maximum lifetime, if any, that a connection is allowed. pub(crate) max_lifetime: Option, /// The duration, if any, after which idle_connections in excess of `min_idle` are closed. pub(crate) idle_timeout: Option, /// The duration to wait to start a connection before giving up. pub(crate) connection_timeout: Duration, /// Enable/disable automatic retries on connection creation. pub(crate) retry_connection: bool, /// The error sink. pub(crate) error_sink: Box>, /// The time interval used to wake up and reap connections. pub(crate) reaper_rate: Duration, /// Queue strategy (FIFO or LIFO) pub(crate) queue_strategy: QueueStrategy, /// User-supplied trait object responsible for initializing connections pub(crate) connection_customizer: Option>>, _p: PhantomData, } /// bb8's queue strategy when getting pool resources #[derive(Debug, Default, Clone, Copy)] pub enum QueueStrategy { /// First in first out /// This strategy behaves like a queue /// It will evenly spread load on all existing connections, resetting their idle timeouts, maintaining the pool size #[default] Fifo, /// Last in first out /// This behaves like a stack /// It will use the most recently used connection and help to keep the total pool size small by evicting idle connections Lifo, } impl Default for Builder { fn default() -> Self { Builder { max_size: 10, min_idle: None, test_on_check_out: true, max_lifetime: Some(Duration::from_secs(30 * 60)), idle_timeout: Some(Duration::from_secs(10 * 60)), connection_timeout: Duration::from_secs(30), retry_connection: true, error_sink: Box::new(NopErrorSink), reaper_rate: Duration::from_secs(30), queue_strategy: QueueStrategy::default(), connection_customizer: None, _p: PhantomData, } } } impl Builder { /// Constructs a new `Builder`. /// /// Parameters are initialized with their default values. #[must_use] pub fn new() -> Self { Builder::default() } /// Sets the maximum number of connections managed by the pool. /// /// Defaults to 10. /// /// # Panics /// /// Will panic if `max_size` is 0. #[must_use] pub fn max_size(mut self, max_size: u32) -> Self { assert!(max_size > 0, "max_size must be greater than zero!"); self.max_size = max_size; self } /// Sets the minimum idle connection count maintained by the pool. /// /// If set, the pool will try to maintain at least this many idle /// connections at all times, while respecting the value of `max_size`. /// /// Defaults to None. #[must_use] pub fn min_idle(mut self, min_idle: impl Into>) -> Self { self.min_idle = min_idle.into(); self } /// If true, the health of a connection will be verified through a call to /// `ManageConnection::is_valid` before it is provided to a pool user. /// /// Defaults to true. #[must_use] pub fn test_on_check_out(mut self, test_on_check_out: bool) -> Self { self.test_on_check_out = test_on_check_out; self } /// Sets the maximum lifetime of connections in the pool. /// /// If set, connections will be closed at the next reaping after surviving /// past this duration. /// /// If a connection reaches its maximum lifetime while checked out it will be /// closed when it is returned to the pool. /// /// Defaults to 30 minutes. /// /// # Panics /// /// Will panic if `max_lifetime` is 0. #[must_use] pub fn max_lifetime(mut self, max_lifetime: impl Into>) -> Self { let max_lifetime = max_lifetime.into(); assert_ne!( max_lifetime, Some(Duration::from_secs(0)), "max_lifetime must be greater than zero!" ); self.max_lifetime = max_lifetime; self } /// Sets the idle timeout used by the pool. /// /// If set, idle connections in excess of `min_idle` will be closed at the /// next reaping after remaining idle past this duration. /// /// Defaults to 10 minutes. /// /// # Panics /// /// Will panic if `idle_timeout` is 0. #[must_use] pub fn idle_timeout(mut self, idle_timeout: impl Into>) -> Self { let idle_timeout = idle_timeout.into(); assert_ne!( idle_timeout, Some(Duration::from_secs(0)), "idle_timeout must be greater than zero!" ); self.idle_timeout = idle_timeout; self } /// Sets the connection timeout used by the pool. /// /// Futures returned by `Pool::get` will wait this long before giving up and /// resolving with an error. /// /// Defaults to 30 seconds. /// /// # Panics /// /// Will panic if `connection_timeout` is 0. #[must_use] pub fn connection_timeout(mut self, connection_timeout: Duration) -> Self { assert!( connection_timeout > Duration::from_secs(0), "connection_timeout must be non-zero" ); self.connection_timeout = connection_timeout; self } /// Instructs the pool to automatically retry connection creation if it fails, until the `connection_timeout` has expired. /// /// Useful for transient connectivity errors like temporary DNS resolution failure /// or intermittent network failures. Some applications however are smart enough to /// know that the server is down and retries won't help (and could actually hurt recovery). /// In that case, it's better to disable retries here and let the pool error out. /// /// Defaults to enabled. #[must_use] pub fn retry_connection(mut self, retry: bool) -> Self { self.retry_connection = retry; self } /// Set the sink for errors that are not associated with any particular operation /// on the pool. This can be used to log and monitor failures. /// /// Defaults to `NopErrorSink`. #[must_use] pub fn error_sink(mut self, error_sink: Box>) -> Self { self.error_sink = error_sink; self } /// Used by tests #[allow(dead_code)] #[must_use] pub fn reaper_rate(mut self, reaper_rate: Duration) -> Self { self.reaper_rate = reaper_rate; self } /// Sets the queue strategy to be used by the pool /// /// Defaults to `Fifo`. #[must_use] pub fn queue_strategy(mut self, queue_strategy: QueueStrategy) -> Self { self.queue_strategy = queue_strategy; self } /// Set the connection customizer to customize newly checked out connections #[must_use] pub fn connection_customizer( mut self, connection_customizer: Box>, ) -> Self { self.connection_customizer = Some(connection_customizer); self } fn build_inner(self, manager: M) -> Pool { if let Some(min_idle) = self.min_idle { assert!( self.max_size >= min_idle, "min_idle must be no larger than max_size" ); } Pool { inner: PoolInner::new(self, manager), } } /// Consumes the builder, returning a new, initialized `Pool`. /// /// The `Pool` will not be returned until it has established its configured /// minimum number of connections, or it times out. pub async fn build(self, manager: M) -> Result, M::Error> { let pool = self.build_inner(manager); pool.inner.start_connections().await.map(|()| pool) } /// Consumes the builder, returning a new, initialized `Pool`. /// /// Unlike `build`, this does not wait for any connections to be established /// before returning. pub fn build_unchecked(self, manager: M) -> Pool { let p = self.build_inner(manager); p.inner.spawn_start_connections(); p } } /// A trait which provides connection-specific functionality. pub trait ManageConnection: Sized + Send + Sync + 'static { /// The connection type this manager deals with. type Connection: Send + 'static; /// The error type returned by `Connection`s. type Error: fmt::Debug + Send + 'static; /// Attempts to create a new connection. fn connect(&self) -> impl Future> + Send; /// Determines if the connection is still connected to the database. fn is_valid( &self, conn: &mut Self::Connection, ) -> impl Future> + Send; /// Synchronously determine if the connection is no longer usable, if possible. fn has_broken(&self, conn: &mut Self::Connection) -> bool; } /// A trait which provides functionality to initialize a connection pub trait CustomizeConnection: fmt::Debug + Send + Sync + 'static { /// Called with connections immediately after they are returned from /// `ManageConnection::connect`. /// /// The default implementation simply returns `Ok(())`. If this method returns an /// error, it will be forwarded to the configured error sink. fn on_acquire<'a>( &'a self, _connection: &'a mut C, ) -> Pin> + Send + 'a>> { Box::pin(async { Ok(()) }) } } /// A smart pointer wrapping a connection. pub struct PooledConnection<'a, M: ManageConnection> { pool: Cow<'a, PoolInner>, conn: Option>, pub(crate) state: ConnectionState, } impl<'a, M: ManageConnection> PooledConnection<'a, M> { pub(crate) fn new(pool: &'a PoolInner, conn: Conn) -> Self { Self { pool: Cow::Borrowed(pool), conn: Some(conn), state: ConnectionState::Present, } } pub(crate) fn take(mut self) -> Option> { self.state = ConnectionState::Extracted; self.conn.take() } } impl Deref for PooledConnection<'_, M> { type Target = M::Connection; fn deref(&self) -> &Self::Target { &self.conn.as_ref().unwrap().conn } } impl DerefMut for PooledConnection<'_, M> { fn deref_mut(&mut self) -> &mut M::Connection { &mut self.conn.as_mut().unwrap().conn } } impl fmt::Debug for PooledConnection<'_, M> where M: ManageConnection, M::Connection: fmt::Debug, { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.conn.as_ref().unwrap().conn, fmt) } } impl Drop for PooledConnection<'_, M> { fn drop(&mut self) { if let ConnectionState::Extracted = self.state { return; } debug_assert!(self.conn.is_some(), "incorrect state {:?}", self.state); if let Some(conn) = self.conn.take() { self.pool.as_ref().put_back(conn, self.state); } } } #[derive(Debug, Clone, Copy)] pub(crate) enum ConnectionState { Present, Extracted, Invalid, } /// bb8's error type. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RunError { /// An error returned from user code. User(E), /// bb8 attempted to get a connection but the provided timeout was exceeded. TimedOut, } impl fmt::Display for RunError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { RunError::User(ref err) => write!(f, "{err}"), RunError::TimedOut => write!(f, "Timed out in bb8"), } } } impl error::Error for RunError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match *self { RunError::User(ref err) => Some(err), RunError::TimedOut => None, } } } impl From for RunError { fn from(error: E) -> Self { Self::User(error) } } /// Error type returned by `Pool::add(conn)` #[derive(Debug, Clone, PartialEq, Eq)] pub enum AddError { /// The connection was broken before it could be added. Broken(C), /// Unable to add the connection to the pool due to insufficient capacity. NoCapacity(C), } impl fmt::Display for AddError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { AddError::Broken(_) => write!(f, "The connection was broken before it could be added"), AddError::NoCapacity(_) => write!( f, "Unable to add the connection to the pool due to insufficient capacity" ), } } } impl error::Error for AddError { fn source(&self) -> Option<&(dyn error::Error + 'static)> { None } } /// A trait to receive errors generated by connection management that aren't /// tied to any particular caller. pub trait ErrorSink: fmt::Debug + Send + Sync + 'static { /// Receive an error fn sink(&self, error: E); /// Clone this sink. fn boxed_clone(&self) -> Box>; } /// An `ErrorSink` implementation that does nothing. #[derive(Debug, Clone, Copy)] pub struct NopErrorSink; impl ErrorSink for NopErrorSink { fn sink(&self, _: E) {} fn boxed_clone(&self) -> Box> { Box::new(*self) } } bb8-0.9.1/src/inner.rs000064400000000000000000000223321046102023000125600ustar 00000000000000use std::cmp::{max, min}; use std::fmt; use std::future::Future; use std::sync::{Arc, Weak}; use std::time::{Duration, Instant}; use futures_util::stream::{FuturesUnordered, StreamExt}; use futures_util::TryFutureExt; use tokio::spawn; use tokio::time::{interval_at, sleep, timeout, Interval}; use crate::api::{ AddError, Builder, ConnectionState, ManageConnection, PooledConnection, RunError, State, }; use crate::internals::{Approval, ApprovalIter, Conn, SharedPool, StatsGetKind, StatsKind}; pub(crate) struct PoolInner { inner: Arc>, } impl PoolInner { pub(crate) fn new(builder: Builder, manager: M) -> Self { let inner = Arc::new(SharedPool::new(builder, manager)); if inner.statics.max_lifetime.is_some() || inner.statics.idle_timeout.is_some() { let start = Instant::now() + inner.statics.reaper_rate; let interval = interval_at(start.into(), inner.statics.reaper_rate); tokio::spawn( Reaper { interval, pool: Arc::downgrade(&inner), } .run(), ); } Self { inner } } pub(crate) async fn start_connections(&self) -> Result<(), M::Error> { let wanted = self.inner.internals.lock().wanted(&self.inner.statics); let mut stream = self.replenish_idle_connections(wanted); while let Some(result) = stream.next().await { result?; } Ok(()) } pub(crate) fn spawn_start_connections(&self) { let mut locked = self.inner.internals.lock(); self.spawn_replenishing_approvals(locked.wanted(&self.inner.statics)); } fn spawn_replenishing_approvals(&self, approvals: ApprovalIter) { if approvals.len() == 0 { return; } let this = self.clone(); spawn(async move { let mut stream = this.replenish_idle_connections(approvals); while let Some(result) = stream.next().await { match result { Ok(()) => {} Err(e) => this.inner.forward_error(e), } } }); } fn replenish_idle_connections( &self, approvals: ApprovalIter, ) -> FuturesUnordered>> { let stream = FuturesUnordered::new(); for approval in approvals { let this = self.clone(); stream.push(async move { this.add_connection(approval).await }); } stream } pub(crate) async fn get(&self) -> Result, RunError> { let mut kind = StatsGetKind::Direct; let mut wait_time_start = None; let future = async { let getting = self.inner.start_get(); loop { let (conn, approvals) = getting.get(); self.spawn_replenishing_approvals(approvals); // Cancellation safety: make sure to wrap the connection in a `PooledConnection` // before allowing the code to hit an `await`, so we don't lose the connection. let mut conn = match conn { Some(conn) => PooledConnection::new(self, conn), None => { wait_time_start = Some(Instant::now()); kind = StatsGetKind::Waited; self.inner.notify.notified().await; continue; } }; if !self.inner.statics.test_on_check_out { return Ok(conn); } match self.inner.manager.is_valid(&mut conn).await { Ok(()) => return Ok(conn), Err(e) => { self.inner.statistics.record(StatsKind::ClosedInvalid); self.inner.forward_error(e); conn.state = ConnectionState::Invalid; continue; } } } }; self.inner.statistics.record_get_started(); let result = match timeout(self.inner.statics.connection_timeout, future).await { Ok(result) => result, _ => { kind = StatsGetKind::TimedOut; Err(RunError::TimedOut) } }; self.inner.statistics.record_get(kind, wait_time_start); result } pub(crate) async fn connect(&self) -> Result { let mut conn = self.inner.manager.connect().await?; self.on_acquire_connection(&mut conn).await?; Ok(conn) } /// Return connection back in to the pool pub(crate) fn put_back(&self, mut conn: Conn, state: ConnectionState) { debug_assert!( !matches!(state, ConnectionState::Extracted), "handled in caller" ); let is_broken = self.inner.manager.has_broken(&mut conn.conn); let is_expired = match self.inner.statics.max_lifetime { Some(lt) => conn.is_expired(Instant::now(), lt), None => false, }; let mut locked = self.inner.internals.lock(); if let (ConnectionState::Present, false) = (state, is_broken || is_expired) { locked.put(conn, None, self.inner.clone()); return; } else if is_broken { self.inner.statistics.record(StatsKind::ClosedBroken); } else if is_expired { self.inner.statistics.record_connections_reaped(0, 1); } let approvals = locked.dropped(1, &self.inner.statics); self.spawn_replenishing_approvals(approvals); self.inner.notify.notify_one(); } /// Adds an external connection to the pool if there is capacity for it. pub(crate) fn try_put(&self, mut conn: M::Connection) -> Result<(), AddError> { if self.inner.manager.has_broken(&mut conn) { Err(AddError::Broken(conn)) } else { self.inner.try_put(conn).map_err(AddError::NoCapacity) } } /// Returns information about the current state of the pool. pub(crate) fn state(&self) -> State { self.inner .internals .lock() .state((&self.inner.statistics).into()) } // Outside of Pool to avoid borrow splitting issues on self async fn add_connection(&self, approval: Approval) -> Result<(), M::Error> { let new_shared = Arc::downgrade(&self.inner); let shared = match new_shared.upgrade() { None => return Ok(()), Some(shared) => shared, }; let start = Instant::now(); let mut delay = Duration::from_secs(0); loop { let conn = shared .manager .connect() .and_then(|mut c| async { self.on_acquire_connection(&mut c).await.map(|_| c) }) .await; match conn { Ok(conn) => { let conn = Conn::new(conn); shared .internals .lock() .put(conn, Some(approval), self.inner.clone()); self.inner.statistics.record(StatsKind::Created); return Ok(()); } Err(e) => { if !self.inner.statics.retry_connection || Instant::now() - start > self.inner.statics.connection_timeout { let mut locked = shared.internals.lock(); locked.connect_failed(approval); return Err(e); } else { self.inner.forward_error(e); delay = max(Duration::from_millis(200), delay); delay = min(self.inner.statics.connection_timeout / 2, delay * 2); sleep(delay).await; } } } } } async fn on_acquire_connection(&self, conn: &mut M::Connection) -> Result<(), M::Error> { match self.inner.statics.connection_customizer.as_ref() { Some(customizer) => customizer.on_acquire(conn).await, None => Ok(()), } } pub(crate) fn builder(&self) -> &Builder { &self.inner.statics } } impl Clone for PoolInner { fn clone(&self) -> Self { PoolInner { inner: self.inner.clone(), } } } impl fmt::Debug for PoolInner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_fmt(format_args!("PoolInner({:p})", self.inner)) } } struct Reaper { interval: Interval, pool: Weak>, } impl Reaper { async fn run(mut self) { loop { let _ = self.interval.tick().await; let pool = match self.pool.upgrade() { Some(inner) => PoolInner { inner }, None => break, }; let approvals = pool.inner.reap(); pool.spawn_replenishing_approvals(approvals); } } } bb8-0.9.1/src/internals.rs000064400000000000000000000275711046102023000134560ustar 00000000000000use std::cmp::min; use std::collections::VecDeque; #[cfg(target_has_atomic = "64")] use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::sync::Arc; use std::time::{Duration, Instant}; #[cfg(not(target_has_atomic = "64"))] use portable_atomic::AtomicU64; use tokio::sync::Notify; use crate::api::{Builder, ManageConnection, QueueStrategy, State, Statistics}; use crate::lock::Mutex; /// The guts of a `Pool`. #[allow(missing_debug_implementations)] pub(crate) struct SharedPool { pub(crate) statics: Builder, pub(crate) manager: M, pub(crate) internals: Mutex>, pub(crate) notify: Arc, pub(crate) statistics: AtomicStatistics, } impl SharedPool { pub(crate) fn new(statics: Builder, manager: M) -> Self { Self { statics, manager, internals: Mutex::new(PoolInternals::default()), notify: Arc::new(Notify::new()), statistics: AtomicStatistics::default(), } } pub(crate) fn try_put(self: &Arc, conn: M::Connection) -> Result<(), M::Connection> { let mut locked = self.internals.lock(); let mut approvals = locked.approvals(&self.statics, 1); let Some(approval) = approvals.next() else { return Err(conn); }; let conn = Conn::new(conn); locked.put(conn, Some(approval), self.clone()); Ok(()) } pub(crate) fn reap(&self) -> ApprovalIter { let mut locked = self.internals.lock(); let (iter, closed_idle_timeout, closed_max_lifetime) = locked.reap(&self.statics); drop(locked); self.statistics .record_connections_reaped(closed_idle_timeout, closed_max_lifetime); iter } pub(crate) fn start_get(self: &Arc) -> Getting { Getting::new(self.clone()) } pub(crate) fn forward_error(&self, err: M::Error) { self.statics.error_sink.sink(err); } } /// The pool data that must be protected by a lock. #[allow(missing_debug_implementations)] pub(crate) struct PoolInternals { conns: VecDeque>, num_conns: u32, pending_conns: u32, in_flight: u32, } impl PoolInternals { pub(crate) fn put( &mut self, conn: Conn, approval: Option, pool: Arc>, ) { if approval.is_some() { #[cfg(debug_assertions)] { self.pending_conns -= 1; self.num_conns += 1; } #[cfg(not(debug_assertions))] { self.pending_conns = self.pending_conns.saturating_sub(1); self.num_conns = self.num_conns.saturating_add(1); } } // Queue it in the idle queue let conn = IdleConn::from(conn); match pool.statics.queue_strategy { QueueStrategy::Fifo => self.conns.push_back(conn), QueueStrategy::Lifo => self.conns.push_front(conn), } pool.notify.notify_one(); } pub(crate) fn connect_failed(&mut self, _: Approval) { #[cfg(debug_assertions)] { self.pending_conns -= 1; } #[cfg(not(debug_assertions))] { self.pending_conns = self.pending_conns.saturating_sub(1); } } pub(crate) fn dropped(&mut self, num: u32, config: &Builder) -> ApprovalIter { #[cfg(debug_assertions)] { self.num_conns -= num; } #[cfg(not(debug_assertions))] { self.num_conns = self.num_conns.saturating_sub(num); } self.wanted(config) } pub(crate) fn wanted(&mut self, config: &Builder) -> ApprovalIter { let available = self.conns.len() as u32 + self.pending_conns; let min_idle = config.min_idle.unwrap_or(0); let wanted = min_idle.saturating_sub(available); self.approvals(config, wanted) } fn approvals(&mut self, config: &Builder, num: u32) -> ApprovalIter { let current = self.num_conns + self.pending_conns; let num = min(num, config.max_size.saturating_sub(current)); self.pending_conns += num; ApprovalIter { num: num as usize } } pub(crate) fn reap(&mut self, config: &Builder) -> (ApprovalIter, u64, u64) { let mut closed_max_lifetime = 0; let mut closed_idle_timeout = 0; let now = Instant::now(); let before = self.conns.len(); self.conns.retain(|conn| { let mut keep = true; if let Some(timeout) = config.idle_timeout { if now - conn.idle_start >= timeout { closed_idle_timeout += 1; keep &= false; } } if let Some(lifetime) = config.max_lifetime { if conn.conn.is_expired(now, lifetime) { closed_max_lifetime += 1; keep &= false; } } keep }); ( self.dropped((before - self.conns.len()) as u32, config), closed_idle_timeout, closed_max_lifetime, ) } pub(crate) fn state(&self, statistics: Statistics) -> State { State { connections: self.num_conns, idle_connections: self.conns.len() as u32, statistics, } } } impl Default for PoolInternals { fn default() -> Self { Self { conns: VecDeque::new(), num_conns: 0, pending_conns: 0, in_flight: 0, } } } #[must_use] pub(crate) struct ApprovalIter { num: usize, } impl Iterator for ApprovalIter { type Item = Approval; fn next(&mut self) -> Option { match self.num { 0 => None, _ => { self.num -= 1; Some(Approval { _priv: () }) } } } } impl ExactSizeIterator for ApprovalIter { fn len(&self) -> usize { self.num } } #[must_use] pub(crate) struct Approval { _priv: (), } pub(crate) struct Getting { inner: Arc>, } impl Getting { pub(crate) fn get(&self) -> (Option>, ApprovalIter) { let mut locked = self.inner.internals.lock(); if let Some(IdleConn { conn, .. }) = locked.conns.pop_front() { return (Some(conn), locked.wanted(&self.inner.statics)); } let approvals = match locked.in_flight > locked.pending_conns { true => 1, false => 0, }; (None, locked.approvals(&self.inner.statics, approvals)) } } impl Getting { fn new(inner: Arc>) -> Self { { let mut locked = inner.internals.lock(); locked.in_flight += 1; } Getting { inner } } } impl Drop for Getting { fn drop(&mut self) { let mut locked = self.inner.internals.lock(); locked.in_flight -= 1; } } #[derive(Debug)] pub(crate) struct Conn { pub(crate) conn: C, birth: Instant, } impl Conn { pub(crate) fn new(conn: C) -> Self { Self { conn, birth: Instant::now(), } } pub(crate) fn is_expired(&self, now: Instant, max: Duration) -> bool { // The current age of the connection is longer than the maximum allowed lifetime now - self.birth >= max } } impl From> for Conn { fn from(conn: IdleConn) -> Self { conn.conn } } struct IdleConn { conn: Conn, idle_start: Instant, } impl From> for IdleConn { fn from(conn: Conn) -> Self { IdleConn { conn, idle_start: Instant::now(), } } } #[derive(Default)] pub(crate) struct AtomicStatistics { pub(crate) get_started: AtomicU64, pub(crate) get_direct: AtomicU64, pub(crate) get_waited: AtomicU64, pub(crate) get_timed_out: AtomicU64, pub(crate) get_wait_time_micros: AtomicU64, pub(crate) connections_created: AtomicU64, pub(crate) connections_closed_broken: AtomicU64, pub(crate) connections_closed_invalid: AtomicU64, pub(crate) connections_closed_max_lifetime: AtomicU64, pub(crate) connections_closed_idle_timeout: AtomicU64, } impl AtomicStatistics { pub(crate) fn record_get_started(&self) { self.get_started.fetch_add(1, Ordering::Relaxed); } pub(crate) fn record_get(&self, kind: StatsGetKind, wait_time_start: Option) { match kind { StatsGetKind::Direct => self.get_direct.fetch_add(1, Ordering::Relaxed), StatsGetKind::Waited => self.get_waited.fetch_add(1, Ordering::Relaxed), StatsGetKind::TimedOut => self.get_timed_out.fetch_add(1, Ordering::Relaxed), }; if let Some(wait_time_start) = wait_time_start { let wait_time = Instant::now() - wait_time_start; self.get_wait_time_micros .fetch_add(wait_time.as_micros() as u64, Ordering::Relaxed); } } pub(crate) fn record(&self, kind: StatsKind) { match kind { StatsKind::Created => &self.connections_created, StatsKind::ClosedBroken => &self.connections_closed_broken, StatsKind::ClosedInvalid => &self.connections_closed_invalid, } .fetch_add(1, Ordering::Relaxed); } pub(crate) fn record_connections_reaped( &self, closed_idle_timeout: u64, closed_max_lifetime: u64, ) { self.connections_closed_idle_timeout .fetch_add(closed_idle_timeout, Ordering::Relaxed); self.connections_closed_max_lifetime .fetch_add(closed_max_lifetime, Ordering::Relaxed); } } impl From<&AtomicStatistics> for Statistics { fn from(item: &AtomicStatistics) -> Self { Self { get_started: item.get_started.load(Ordering::Relaxed), get_direct: item.get_direct.load(Ordering::Relaxed), get_waited: item.get_waited.load(Ordering::Relaxed), get_timed_out: item.get_timed_out.load(Ordering::Relaxed), get_wait_time: Duration::from_micros(item.get_wait_time_micros.load(Ordering::Relaxed)), connections_created: item.connections_created.load(Ordering::Relaxed), connections_closed_broken: item.connections_closed_broken.load(Ordering::Relaxed), connections_closed_invalid: item.connections_closed_invalid.load(Ordering::Relaxed), connections_closed_max_lifetime: item .connections_closed_max_lifetime .load(Ordering::Relaxed), connections_closed_idle_timeout: item .connections_closed_idle_timeout .load(Ordering::Relaxed), } } } pub(crate) enum StatsGetKind { Direct, Waited, TimedOut, } pub(crate) enum StatsKind { Created, ClosedBroken, ClosedInvalid, } #[cfg(test)] mod tests { use std::time::{Duration, Instant}; use crate::internals::Conn; #[test] fn test_conn_is_expired() { let conn = Conn { conn: (), birth: Instant::now(), }; assert!( !conn.is_expired(conn.birth, Duration::from_nanos(1)), "at birth, the connection has not expired" ); assert!( !conn.is_expired(Instant::now(), Duration::from_secs(5)), "from setup to now is shorter than 5s, so the connection has not expired" ); assert!( conn.is_expired(Instant::now(), Duration::from_nanos(1)), "from setup to now is longer than 1ns, so the connection has expired" ); } } bb8-0.9.1/src/lib.rs000064400000000000000000000043571046102023000122220ustar 00000000000000//! A full-featured connection pool, designed for asynchronous connections //! (using tokio). Originally based on [r2d2](https://github.com/sfackler/r2d2). //! //! Opening a new database connection every time one is needed is both //! inefficient and can lead to resource exhaustion under high traffic //! conditions. A connection pool maintains a set of open connections to a //! database, handing them out for repeated use. //! //! bb8 is agnostic to the connection type it is managing. Implementors of the //! `ManageConnection` trait provide the database-specific logic to create and //! check the health of connections. //! //! # Example //! //! Using an imaginary "foodb" database. //! //! ```ignore //! #[tokio::main] //! async fn main() { //! let manager = bb8_foodb::FooConnectionManager::new("localhost:1234"); //! let pool = bb8::Pool::builder().build(manager).await.unwrap(); //! //! for _ in 0..20 { //! let pool = pool.clone(); //! tokio::spawn(async move { //! let conn = pool.get().await.unwrap(); //! // use the connection //! // it will be returned to the pool when it falls out of scope. //! }); //! } //! } //! ``` #![allow(clippy::needless_doctest_main)] #![deny(missing_docs, missing_debug_implementations)] mod api; pub use api::{ AddError, Builder, CustomizeConnection, ErrorSink, ManageConnection, NopErrorSink, Pool, PooledConnection, QueueStrategy, RunError, State, Statistics, }; mod inner; mod internals; mod lock { #[cfg(feature = "parking_lot")] use parking_lot::Mutex as MutexImpl; #[cfg(feature = "parking_lot")] use parking_lot::MutexGuard; #[cfg(not(feature = "parking_lot"))] use std::sync::Mutex as MutexImpl; #[cfg(not(feature = "parking_lot"))] use std::sync::MutexGuard; pub(crate) struct Mutex(MutexImpl); impl Mutex { pub(crate) fn new(val: T) -> Self { Self(MutexImpl::new(val)) } pub(crate) fn lock(&self) -> MutexGuard<'_, T> { #[cfg(feature = "parking_lot")] { self.0.lock() } #[cfg(not(feature = "parking_lot"))] { self.0.lock().unwrap() } } } } bb8-0.9.1/tests/test.rs000064400000000000000000000724621046102023000130100ustar 00000000000000use bb8::*; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::Mutex; use std::task::Poll; use std::time::Duration; use std::{error, fmt}; use futures_util::future::{err, lazy, ok, pending, ready, try_join_all, FutureExt}; use futures_util::stream::{FuturesUnordered, TryStreamExt}; use tokio::sync::oneshot; use tokio::time::{sleep, timeout}; #[derive(Debug, PartialEq, Eq)] pub struct Error; impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fmt.write_str("blammo") } } impl error::Error for Error { fn description(&self) -> &str { "Error" } } #[derive(Debug, Default)] struct FakeConnection; struct OkManager { _c: PhantomData, } impl OkManager { fn new() -> Self { OkManager { _c: PhantomData } } } impl ManageConnection for OkManager { type Connection = C; type Error = Error; async fn connect(&self) -> Result { Ok(Default::default()) } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _: &mut Self::Connection) -> bool { false } } struct NthConnectionFailManager { n: Mutex, _c: PhantomData, } impl NthConnectionFailManager { fn new(n: u32) -> Self { NthConnectionFailManager { n: Mutex::new(n), _c: PhantomData, } } } impl ManageConnection for NthConnectionFailManager { type Connection = C; type Error = Error; async fn connect(&self) -> Result { let mut n = self.n.lock().unwrap(); if *n > 0 { *n -= 1; Ok(Default::default()) } else { Err(Error) } } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _conn: &mut Self::Connection) -> bool { false } } #[tokio::test] async fn test_max_size_ok() { let manager = NthConnectionFailManager::::new(5); let pool = Pool::builder().max_size(5).build(manager).await.unwrap(); let mut channels = Vec::with_capacity(5); let mut ignored = Vec::with_capacity(5); for _ in 0..5 { let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel::<()>(); let pool = pool.clone(); tokio::spawn(async move { let conn = pool.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); channels.push(rx1); ignored.push(tx2); } assert_eq!(channels.len(), 5); assert_eq!(try_join_all(channels).await.unwrap().len(), 5); } #[tokio::test] async fn test_acquire_release() { let pool = Pool::builder() .max_size(2) .build(OkManager::::new()) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .then(|r| match r { Ok(v) => ok((v, conn)), Err(_) => err((Error, conn)), }) .await; }); let (tx3, rx3) = oneshot::channel(); let (tx4, rx4) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get(); tx3.send(()).unwrap(); let _ = rx4 .then(|r| match r { Ok(v) => ok((v, conn)), Err(_) => err((Error, conn)), }) .await; }); // Get the first connection. rx1.await.unwrap(); // Get the second connection. rx3.await.unwrap(); let (tx5, mut rx5) = oneshot::channel(); let (tx6, rx6) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx5.send(()).unwrap(); let _ = rx6 .then(|r| match r { Ok(v) => ok((v, conn)), Err(_) => err((Error, conn)), }) .await; }); { let rx5_ref = &mut rx5; // NB: The channel needs to run on a Task, so shove it onto // the tokio event loop with a lazy. assert_eq!(lazy(|cx| Pin::new(rx5_ref).poll(cx)).await, Poll::Pending); } // Release the first connection. tx2.send(()).unwrap(); rx5.await.unwrap(); tx4.send(()).unwrap(); tx6.send(()).unwrap(); } #[test] fn test_is_send_sync() { fn is_send_sync() {} is_send_sync::>>(); } // A connection manager that always returns `true` for `has_broken()` #[derive(Default)] struct BrokenConnectionManager { _c: PhantomData, } impl ManageConnection for BrokenConnectionManager { type Connection = C; type Error = Error; async fn connect(&self) -> Result { Ok(C::default()) } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _: &mut Self::Connection) -> bool { true } } #[tokio::test] async fn test_drop_on_broken() { static DROPPED: AtomicBool = AtomicBool::new(false); #[derive(Default)] struct Connection; impl Drop for Connection { fn drop(&mut self) { DROPPED.store(true, Ordering::SeqCst); } } let pool = Pool::builder() .build(BrokenConnectionManager::::default()) .await .unwrap(); { let _ = pool.get().await.unwrap(); } assert!(DROPPED.load(Ordering::SeqCst)); assert_eq!(pool.state().statistics.connections_closed_broken, 1); } #[tokio::test] async fn test_initialization_failure() { let manager = NthConnectionFailManager::::new(0); let res = Pool::builder() .connection_timeout(Duration::from_secs(5)) .max_size(1) .min_idle(Some(1)) .build(manager) .await; assert_eq!(res.unwrap_err(), Error); } #[tokio::test] async fn test_lazy_initialization_failure() { let manager = NthConnectionFailManager::::new(0); let pool = Pool::builder() .connection_timeout(Duration::from_secs(1)) .build_unchecked(manager); let res = pool.get().await; assert_eq!(res.unwrap_err(), RunError::TimedOut); } #[tokio::test] async fn test_lazy_initialization_failure_no_retry() { let manager = NthConnectionFailManager::::new(0); let pool = Pool::builder() .connection_timeout(Duration::from_secs(1)) .retry_connection(false) .build_unchecked(manager); let res = pool.get().await; assert_eq!(res.unwrap_err(), RunError::TimedOut); } #[tokio::test] async fn test_get_timeout() { let pool = Pool::builder() .max_size(1) .connection_timeout(Duration::from_millis(100)) .build(OkManager::::new()) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); // Get the first connection. assert!(rx1.await.is_ok()); pool.get().await.unwrap_err(); tx2.send(()).unwrap(); let r: Result<(), ()> = Ok(()); ready(r).await.unwrap(); } #[tokio::test] async fn test_lots_of_waiters() { let pool = Pool::builder() .max_size(3) .connection_timeout(Duration::from_millis(5_000)) .build(OkManager::::new()) .await .unwrap(); let mut waiters: Vec> = Vec::new(); for _ in 0..25000 { let pool = pool.clone(); let (tx, rx) = oneshot::channel(); waiters.push(rx); tokio::spawn(async move { let _conn = pool.get().await.unwrap(); tx.send(()).unwrap(); }); } let results = futures_util::future::join_all(&mut waiters).await; for result in results { assert!(result.is_ok()); } } #[tokio::test] async fn test_timeout_caller() { let pool = Pool::builder() .max_size(1) .connection_timeout(Duration::from_millis(5_000)) .build(OkManager::::new()) .await .unwrap(); let one = pool.get().await; assert!(one.is_ok()); let res = tokio::time::timeout(Duration::from_millis(100), pool.get()).await; assert!(res.is_err()); drop(one); let two = pool.get().await; assert!(two.is_ok()); } #[tokio::test] async fn test_now_invalid() { static INVALID: AtomicBool = AtomicBool::new(false); struct Handler; impl ManageConnection for Handler { type Connection = FakeConnection; type Error = Error; async fn connect(&self) -> Result { if INVALID.load(Ordering::SeqCst) { Err(Error) } else { Ok(FakeConnection) } } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { println!("Called is_valid"); if INVALID.load(Ordering::SeqCst) { println!("Not"); Err(Error) } else { println!("Is"); Ok(()) } } fn has_broken(&self, _: &mut Self::Connection) -> bool { false } } let pool = Pool::builder() .max_size(2) .min_idle(Some(2)) .connection_timeout(Duration::from_secs(1)) .build(Handler) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); let (tx3, rx3) = oneshot::channel(); let (tx4, rx4) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx3.send(()).unwrap(); let _ = rx4 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); // Get the first connection. rx1.await.unwrap(); // Get the second connection. rx3.await.unwrap(); INVALID.store(true, Ordering::SeqCst); tx2.send(()).unwrap(); tx4.send(()).unwrap(); // Go idle for a bit assert!(timeout(Duration::from_secs(3), pending::<()>()) .await .is_err()); // Now try to get a new connection. let r = pool.get().await; assert!(r.is_err()); // both connections in the pool were considered invalid assert_eq!(pool.state().statistics.connections_closed_invalid, 2); } #[tokio::test] async fn test_idle_timeout() { static DROPPED: AtomicUsize = AtomicUsize::new(0); #[derive(Default)] struct Connection; impl Drop for Connection { fn drop(&mut self) { DROPPED.fetch_add(1, Ordering::SeqCst); } } let manager = NthConnectionFailManager::::new(5); let pool = Pool::builder() .idle_timeout(Some(Duration::from_secs(1))) .connection_timeout(Duration::from_secs(1)) .reaper_rate(Duration::from_secs(1)) .max_size(5) .min_idle(Some(5)) .build(manager) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); // NB: If we sleep here we'll block this thread's event loop, and the // reaper can't run. let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); rx1.await.unwrap(); // And wait. assert!(timeout(Duration::from_secs(2), pending::<()>()) .await .is_err()); assert_eq!(DROPPED.load(Ordering::SeqCst), 4); tx2.send(()).unwrap(); // And wait some more. assert!(timeout(Duration::from_secs(3), pending::<()>()) .await .is_err()); assert_eq!(DROPPED.load(Ordering::SeqCst), 5); // all 5 idle connections were closed due to max idle time assert_eq!(pool.state().statistics.connections_closed_idle_timeout, 5); } #[tokio::test] async fn test_max_lifetime() { static DROPPED: AtomicUsize = AtomicUsize::new(0); #[derive(Default)] struct Connection; impl Drop for Connection { fn drop(&mut self) { DROPPED.fetch_add(1, Ordering::SeqCst); } } let manager = NthConnectionFailManager::::new(5); let pool = Pool::builder() .max_lifetime(Some(Duration::from_secs(1))) .connection_timeout(Duration::from_secs(1)) .reaper_rate(Duration::from_secs(1)) .max_size(5) .min_idle(Some(5)) .build(manager) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); // NB: If we sleep here we'll block this thread's event loop, and the // reaper can't run. let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); rx1.await.unwrap(); // And wait. assert!(timeout(Duration::from_secs(2), pending::<()>()) .await .is_err()); assert_eq!(DROPPED.load(Ordering::SeqCst), 4); tx2.send(()).unwrap(); // And wait some more. assert!(timeout(Duration::from_secs(2), pending::<()>()) .await .is_err()); assert_eq!(DROPPED.load(Ordering::SeqCst), 5); // all 5 connections were closed due to max lifetime assert_eq!(pool.state().statistics.connections_closed_max_lifetime, 5); } #[tokio::test] async fn test_max_lifetime_reap_on_drop() { static DROPPED: AtomicUsize = AtomicUsize::new(0); #[derive(Default)] struct Connection; impl Drop for Connection { fn drop(&mut self) { DROPPED.fetch_add(1, Ordering::SeqCst); } } let manager = OkManager::::new(); let pool = Pool::builder() .max_lifetime(Some(Duration::from_secs(1))) .connection_timeout(Duration::from_secs(1)) .reaper_rate(Duration::from_secs(999)) .build(manager) .await .unwrap(); let conn = pool.get().await; // And wait. sleep(Duration::from_secs(2)).await; assert_eq!(DROPPED.load(Ordering::SeqCst), 0); // Connection is reaped on drop. drop(conn); assert_eq!(DROPPED.load(Ordering::SeqCst), 1); assert_eq!(pool.state().statistics.connections_closed_max_lifetime, 1); } #[tokio::test] async fn test_min_idle() { let pool = Pool::builder() .max_size(5) .min_idle(Some(2)) .build(OkManager::::new()) .await .unwrap(); let state = pool.state(); assert_eq!(2, state.idle_connections); assert_eq!(2, state.connections); let mut rx = Vec::with_capacity(3); let mut tx = Vec::with_capacity(3); for _ in 0..3 { let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .map(|r| match r { Ok(v) => Ok((v, conn)), Err(_) => Err((Error, conn)), }) .await; }); rx.push(rx1); tx.push(tx2); } FuturesUnordered::from_iter(rx) .try_collect::>() .await .unwrap(); let state = pool.state(); assert_eq!(2, state.idle_connections); assert_eq!(5, state.connections); for tx in tx.into_iter() { tx.send(()).unwrap(); } // And wait for the connections to return. assert!(timeout(Duration::from_secs(1), pending::<()>()) .await .is_err()); let state = pool.state(); assert_eq!(5, state.idle_connections); assert_eq!(5, state.connections); } #[tokio::test] async fn test_conns_drop_on_pool_drop() { static DROPPED: AtomicUsize = AtomicUsize::new(0); struct Connection; impl Drop for Connection { fn drop(&mut self) { DROPPED.fetch_add(1, Ordering::SeqCst); } } struct Handler; impl ManageConnection for Handler { type Connection = Connection; type Error = Error; async fn connect(&self) -> Result { Ok(Connection) } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _: &mut Self::Connection) -> bool { false } } let pool = Pool::builder() .max_lifetime(Some(Duration::from_secs(10))) .max_size(10) .min_idle(Some(10)) .build(Handler) .await .unwrap(); drop(pool); for _ in 0u8..10 { if DROPPED.load(Ordering::SeqCst) == 10 { return; } assert!(timeout(Duration::from_secs(1), pending::<()>()) .await .is_err()); } panic!( "Timed out waiting for connections to drop, {} dropped", DROPPED.load(Ordering::SeqCst) ); } // make sure that bb8 retries after is_valid fails once #[tokio::test] async fn test_retry() { static FAILED_ONCE: AtomicBool = AtomicBool::new(false); struct Connection; struct Handler; impl ManageConnection for Handler { type Connection = Connection; type Error = Error; async fn connect(&self) -> Result { Ok(Connection) } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { // only fail once so the retry should work if FAILED_ONCE.load(Ordering::SeqCst) { Ok(()) } else { FAILED_ONCE.store(true, Ordering::SeqCst); Err(Error) } } fn has_broken(&self, _: &mut Self::Connection) -> bool { false } } let pool = Pool::builder().max_size(1).build(Handler).await.unwrap(); // is_valid() will be called between the 2 iterations for _ in 0..2 { let _ = pool.get().await.unwrap(); } } #[tokio::test] async fn test_conn_fail_once() { static FAILED_ONCE: AtomicBool = AtomicBool::new(false); static NB_CALL: AtomicUsize = AtomicUsize::new(0); struct Connection; struct Handler; impl Connection { fn inc(&mut self) { NB_CALL.fetch_add(1, Ordering::SeqCst); } } impl ManageConnection for Handler { type Connection = Connection; type Error = Error; async fn connect(&self) -> Result { // only fail once so the retry should work if FAILED_ONCE.load(Ordering::SeqCst) { Ok(Connection) } else { FAILED_ONCE.store(true, Ordering::SeqCst); Err(Error) } } async fn is_valid(&self, _conn: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _: &mut Self::Connection) -> bool { false } } let pool = Pool::builder().max_size(1).build(Handler).await.unwrap(); for _ in 0..2 { pool.get().await.unwrap().inc(); } assert_eq!(NB_CALL.load(Ordering::SeqCst), 2); } // This mimics the test_acquire_release test, but using the `get()` API. #[tokio::test] async fn test_guard() { let pool = Pool::builder() .max_size(2) .build(OkManager::::new()) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await; tx1.send(()).unwrap(); let res = rx2 .then(|r| match r { Ok(()) => ok(()), Err(_) => err(Error), }) .await .map(|_| ()); drop(conn); res }); let (tx3, rx3) = oneshot::channel(); let (tx4, rx4) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await; tx3.send(()).unwrap(); let res = rx4 .then(|r| match r { Ok(()) => ok(()), Err(_) => err(Error), }) .await .map(|_| ()); drop(conn); res }); // Get the first connection. rx1.await.unwrap(); // Get the second connection. rx3.await.unwrap(); let (tx5, mut rx5) = oneshot::channel(); let (tx6, rx6) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await; tx5.send(()).unwrap(); let res = rx6 .then(|r| match r { Ok(()) => ok(()), Err(_) => err(Error), }) .await .map(|_| ()); drop(conn); res }); { let rx5_ref = &mut rx5; // NB: The channel needs to run on a Task, so shove it onto // the tokio event loop with a lazy. assert_eq!(lazy(|cx| Pin::new(rx5_ref).poll(cx)).await, Poll::Pending); } // Release the first connection. tx2.send(()).unwrap(); rx5.await.unwrap(); tx4.send(()).unwrap(); tx6.send(()).unwrap(); } #[tokio::test] async fn test_customize_connection_acquire() { #[derive(Debug, Default)] struct Connection { custom_field: usize, } #[derive(Debug, Default)] struct CountingCustomizer { count: AtomicUsize, } impl CustomizeConnection for CountingCustomizer { fn on_acquire<'a>( &'a self, connection: &'a mut Connection, ) -> Pin> + Send + 'a>> { Box::pin(async move { connection.custom_field = 1 + self.count.fetch_add(1, Ordering::SeqCst); Ok(()) }) } } let pool = Pool::builder() .max_size(2) .connection_customizer(Box::::default()) .build(OkManager::::new()) .await .unwrap(); // Each connection gets customized { let connection_1 = pool.get().await.unwrap(); assert_eq!(connection_1.custom_field, 1); let connection_2 = pool.get().await.unwrap(); assert_eq!(connection_2.custom_field, 2); } // Connections don't get customized again on re-use let connection_1_or_2 = pool.get().await.unwrap(); assert!(connection_1_or_2.custom_field == 1 || connection_1_or_2.custom_field == 2); } #[tokio::test] async fn test_broken_connections_dont_starve_pool() { use std::sync::RwLock; use std::{convert::Infallible, time::Duration}; #[derive(Default)] struct ConnectionManager { counter: RwLock, } #[derive(Debug)] struct Connection; impl bb8::ManageConnection for ConnectionManager { type Connection = Connection; type Error = Infallible; async fn connect(&self) -> Result { Ok(Connection) } async fn is_valid(&self, _: &mut Self::Connection) -> Result<(), Self::Error> { Ok(()) } fn has_broken(&self, _: &mut Self::Connection) -> bool { let mut counter = self.counter.write().unwrap(); let res = *counter < 5; *counter += 1; res } } let pool = bb8::Pool::builder() .max_size(5) .connection_timeout(Duration::from_secs(10)) .build(ConnectionManager::default()) .await .unwrap(); let mut futures = Vec::new(); for _ in 0..10 { let pool = pool.clone(); futures.push(tokio::spawn(async move { let conn = pool.get().await.unwrap(); drop(conn); })); } for future in futures { future.await.unwrap(); } } #[tokio::test] async fn test_state_get_contention() { let pool = Pool::builder() .max_size(1) .min_idle(1) .build(OkManager::::new()) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let (tx2, rx2) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let conn = clone.get().await.unwrap(); tx1.send(()).unwrap(); let _ = rx2 .then(|r| match r { Ok(v) => ok((v, conn)), Err(_) => err((Error, conn)), }) .await; }); // Get the first connection. rx1.await.unwrap(); // Now try to get a new connection without waiting. let f = pool.get(); // Release the first connection. tx2.send(()).unwrap(); // Wait for the second attempt to get a connection. f.await.unwrap(); let statistics = pool.state().statistics; assert_eq!(statistics.get_direct, 1); assert_eq!(statistics.get_waited, 1); assert!(statistics.get_wait_time > Duration::from_micros(0)); } #[tokio::test] async fn test_statistics_connections_created() { let pool = Pool::builder() .max_size(1) .min_idle(1) .build(OkManager::::new()) .await .unwrap(); let (tx1, rx1) = oneshot::channel(); let clone = pool.clone(); tokio::spawn(async move { let _ = clone.get().await.unwrap(); tx1.send(()).unwrap(); }); // wait until finished. rx1.await.unwrap(); assert_eq!(pool.state().statistics.connections_created, 1); } #[tokio::test] async fn test_can_use_added_connections() { let pool = Pool::builder() .connection_timeout(Duration::from_millis(1)) .build_unchecked(NthConnectionFailManager::::new(0)); // Assert pool can't replenish connections on its own let res = pool.get().await; assert_eq!(res.unwrap_err(), RunError::TimedOut); pool.add(FakeConnection).unwrap(); let res = pool.get().await; assert!(res.is_ok()); } #[tokio::test] async fn test_add_ok_until_max_size() { let pool = Pool::builder() .min_idle(1) .max_size(3) .build(OkManager::::new()) .await .unwrap(); for _ in 0..2 { let conn = pool.dedicated_connection().await.unwrap(); pool.add(conn).unwrap(); } let conn = pool.dedicated_connection().await.unwrap(); let res = pool.add(conn); assert!(matches!(res, Err(AddError::NoCapacity(_)))); } #[tokio::test] async fn test_add_checks_broken_connections() { let pool = Pool::builder() .min_idle(1) .max_size(3) .build(BrokenConnectionManager::::default()) .await .unwrap(); let conn = pool.dedicated_connection().await.unwrap(); let res = pool.add(conn); assert!(matches!(res, Err(AddError::Broken(_)))); } #[tokio::test] async fn test_reuse_on_drop() { let pool = Pool::builder() .min_idle(0) .max_size(100) .queue_strategy(QueueStrategy::Lifo) .build(OkManager::::new()) .await .unwrap(); // The first get should // 1) see nothing in the pool, // 2) spawn a single replenishing approval, // 3) get notified of the new connection and grab it from the pool let conn_0 = pool.get().await.expect("should connect"); // Dropping the connection queues up a notify drop(conn_0); // The second get should // 1) see the first connection in the pool and grab it let _conn_1 = pool.get().await.expect("should connect"); // The third get will // 1) see nothing in the pool, // 2) spawn a single replenishing approval, // 3) get notified of the new connection, // 4) see nothing in the pool, // 5) _not_ spawn a single replenishing approval, // 6) get notified of the new connection and grab it from the pool let _conn_2 = pool.get().await.expect("should connect"); assert_eq!(pool.state().connections, 2); }