├── .circleci └── config.yml ├── .github └── dependabot.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── config.rs ├── event.rs ├── extensions.rs ├── lib.rs └── test.rs /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | jobs: 4 | build: 5 | docker: 6 | - image: rust:1.49.0 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: registry 11 | - run: cargo generate-lockfile 12 | - save_cache: 13 | key: registry-{{ epoch }} 14 | paths: 15 | - /usr/local/cargo/registry/index 16 | - restore_cache: 17 | key: dependencies-1.49-{{ checksum "Cargo.lock" }} 18 | - run: cargo test 19 | - save_cache: 20 | key: dependencies-1.49-{{ checksum "Cargo.lock" }} 21 | paths: 22 | - target 23 | - /usr/local/cargo/registry/cache 24 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .cargo/ 4 | .idea/ 5 | *.iml 6 | .vscode/ 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.8.10] - 2022-06-21 6 | 7 | ## Changed 8 | 9 | * Upgraded `parking_lot`. 10 | 11 | ## [0.8.9] - 2020-06-30 12 | 13 | ## Changed 14 | 15 | * Upgraded `parking_lot`. 16 | 17 | ## [0.8.7] - 2019-11-25 18 | 19 | ## Changed 20 | 21 | * Upgraded `parking_lot`. 22 | 23 | ## [0.8.6] - 2019-10-19 24 | 25 | ## Added 26 | 27 | * Added the ability to associate arbitrary data with pooled connections. 28 | 29 | ## [0.8.5] - 2019-06-06 30 | 31 | ## Changed 32 | 33 | * Upgraded `parking_lot`. 34 | 35 | ## [0.8.4] - 2019-04-01 36 | 37 | ### Added 38 | 39 | * Added a `HandleEvent` trait used to listen for various events from the pool for monitoring 40 | purposes. 41 | 42 | ### Changed 43 | 44 | * Switched from standard library synchronization primitives to `parking_lot`. 45 | 46 | ## [0.8.3] - 2018-11-03 47 | 48 | ### Fixed 49 | 50 | * The set of idle connections is now treated as a stack rather than a queue. The old behavior 51 | interacted poorly with configurations that allowed the pool size to shrink when mostly idle. 52 | 53 | ## [0.8.2] - 2017-12-24 54 | 55 | ### Changed 56 | 57 | * Upgraded from log 0.3 to 0.4. 58 | 59 | ## [0.8.1] - 2017-10-28 60 | 61 | ### Fixed 62 | 63 | * Fixed the example in the README. 64 | 65 | ## [0.8.0] - 2017-10-26 66 | 67 | ### Changed 68 | 69 | * Pool configuration has changed. Rather than constructing a `Config` and passing it to the `Pool` 70 | constructor, you now configure a `Builder` which then directly constructs the pool: 71 | 72 | ```rust 73 | // In 0.7.x 74 | let config = Config::builder() 75 | .min_idle(3) 76 | .build(); 77 | let pool = Pool::new(config, manager)?; 78 | 79 | // In 0.8.x 80 | let pool = Pool::builder() 81 | .min_idle(3) 82 | .build(manager)?; 83 | ``` 84 | 85 | * The `Pool::new` method can be used to construct a `Pool` with default settings: 86 | 87 | ```rust 88 | // In 0.7.x 89 | let config = Config::default(); 90 | let pool = Pool::new(config, manager)?; 91 | 92 | // In 0.8.x 93 | let pool = Pool::new(manager)?; 94 | ``` 95 | 96 | * The `initialization_fail_fast` configuration option has been replaced with separate 97 | `Builder::build` and `Builder::build_unchecked` methods. The second returns a `Pool` directly 98 | without wrapping it in a `Result`, and does not check that connections are being successfully 99 | opened: 100 | 101 | ```rust 102 | // In 0.7.x 103 | let config = Config::builder() 104 | .initialization_fail_fast(false) 105 | .build(); 106 | let pool = Pool::new(config, manager).unwrap(); 107 | 108 | // In 0.8.x 109 | let pool = Pool::builder().build_unchecked(manager); 110 | ``` 111 | 112 | * The `InitializationError` and `GetTimeout` error types have been merged into a unified `Error` 113 | type. 114 | 115 | * The `Pool::config` method has been replaced with accessor methods on `Pool` to directly access 116 | configuration, such as `Pool::min_idle`. 117 | 118 | * The `scheduled_thread_pool` crate has been upgraded from 0.1 to 0.2. 119 | 120 | ### Removed 121 | 122 | * The deprecated `Builder::num_threads` method has been removed. Construct a `ScheduledThreadPool` 123 | and set it via `Builder::thread_pool` instead. 124 | 125 | ## Older 126 | 127 | Look at the [release tags] for information about older releases. 128 | 129 | [Unreleased]: https://github.com/sfackler/r2d2/compare/v0.8.10...HEAD 130 | [0.8.10]: https://github.com/sfackler/r2d2/compare/v0.8.9...v0.8.10 131 | [0.8.9]: https://github.com/sfackler/r2d2/compare/v0.8.8...v0.8.9 132 | [0.8.7]: https://github.com/sfackler/r2d2/compare/v0.8.6...v0.8.7 133 | [0.8.6]: https://github.com/sfackler/r2d2/compare/v0.8.5...v0.8.6 134 | [0.8.5]: https://github.com/sfackler/r2d2/compare/v0.8.4...v0.8.5 135 | [0.8.4]: https://github.com/sfackler/r2d2/compare/v0.8.3...v0.8.4 136 | [0.8.3]: https://github.com/sfackler/r2d2/compare/v0.8.2...v0.8.3 137 | [0.8.2]: https://github.com/sfackler/r2d2/compare/v0.8.1...v0.8.2 138 | [0.8.1]: https://github.com/sfackler/r2d2/compare/v0.8.0...v0.8.1 139 | [0.8.0]: https://github.com/sfackler/r2d2/compare/v0.7.4...v0.8.0 140 | [release tags]: https://github.com/sfackler/r2d2/releases 141 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "r2d2" 3 | version = "0.8.10" 4 | authors = ["Steven Fackler "] 5 | license = "MIT OR Apache-2.0" 6 | description = "A generic connection pool" 7 | repository = "https://github.com/sfackler/r2d2" 8 | readme = "README.md" 9 | keywords = ["database", "pool"] 10 | edition = "2018" 11 | 12 | [dependencies] 13 | log = "0.4" 14 | parking_lot = "0.12" 15 | scheduled-thread-pool = "0.2" 16 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 Steven Fackler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # r2d2 2 | [![CircleCI](https://circleci.com/gh/sfackler/r2d2.svg?style=shield)](https://circleci.com/gh/sfackler/r2d2) 3 | 4 | A generic connection pool for Rust. 5 | 6 | [Documentation](https://docs.rs/r2d2) 7 | 8 | Opening a new database connection every time one is needed is both inefficient 9 | and can lead to resource exhaustion under high traffic conditions. A connection 10 | pool maintains a set of open connections to a database, handing them out for 11 | repeated use. 12 | 13 | r2d2 is agnostic to the connection type it is managing. Implementors of the 14 | `ManageConnection` trait provide the database-specific logic to create and 15 | check the health of connections. 16 | 17 | A (possibly not exhaustive) list of adaptors for different backends: 18 | 19 | Backend | Adaptor Crate 20 | ---------------------------------------------------------------------- | ------------- 21 | [rust-postgres](https://github.com/sfackler/rust-postgres) | [r2d2-postgres](https://github.com/sfackler/r2d2-postgres) 22 | [redis-rs](https://github.com/mitsuhiko/redis-rs) | use `r2d2` feature of [redis-rs](https://github.com/mitsuhiko/redis-rs) 23 | [rust-memcache](https://github.com/aisk/rust-memcache) | [r2d2-memcache](https://github.com/megumish/r2d2-memcache) 24 | [rust-mysql-simple](https://github.com/blackbeam/rust-mysql-simple) | [r2d2-mysql](https://github.com/outersky/r2d2-mysql) 25 | [rusqlite](https://github.com/jgallagher/rusqlite) | [r2d2-sqlite](https://github.com/ivanceras/r2d2-sqlite) 26 | [rsfbclient](https://github.com/fernandobatels/rsfbclient) | [r2d2-firebird](https://crates.io/crates/r2d2_firebird/) 27 | [rusted-cypher](https://github.com/livioribeiro/rusted-cypher) | [r2d2-cypher](https://github.com/flosse/r2d2-cypher) 28 | [diesel](https://github.com/sgrif/diesel) | [diesel::r2d2](https://github.com/diesel-rs/diesel/blob/master/diesel/src/r2d2.rs) ([docs](https://docs.rs/diesel/latest/diesel/r2d2/index.html)) 29 | [couchdb](https://github.com/chill-rs/chill) | [r2d2-couchdb](https://github.com/scorphus/r2d2-couchdb) 30 | [mongodb (archived)](https://github.com/mongodb-labs/mongo-rust-driver-prototype)
use official [mongodb](https://github.com/mongodb/mongo-rust-driver) driver instead | [r2d2-mongodb](https://gitlab.com/petoknm/r2d2-mongodb)
(deprecated: official driver handles pooling internally) 31 | [odbc](https://github.com/Koka/odbc-rs) | [r2d2-odbc](https://github.com/Koka/r2d2-odbc) 32 | [jfs](https://github.com/flosse/rust-json-file-store) | [r2d2-jfs](https://github.com/flosse/r2d2-jfs) 33 | [oracle](https://github.com/kubo/rust-oracle) | [r2d2-oracle](https://github.com/rursprung/r2d2-oracle) 34 | [ldap3](https://github.com/inejge/ldap3) | [r2d2-ldap](https://github.com/c0dearm/r2d2-ldap) 35 | [duckdb-rs](https://github.com/wangfenjin/duckdb-rs) | use `r2d2` feature of [duckdb-rs](https://github.com/wangfenjin/duckdb-rs) 36 | 37 | # Example 38 | 39 | Using an imaginary "foodb" database. 40 | 41 | ```rust 42 | use std::thread; 43 | 44 | extern crate r2d2; 45 | extern crate r2d2_foodb; 46 | 47 | fn main() { 48 | let manager = r2d2_foodb::FooConnectionManager::new("localhost:1234"); 49 | let pool = r2d2::Pool::builder() 50 | .max_size(15) 51 | .build(manager) 52 | .unwrap(); 53 | 54 | for _ in 0..20 { 55 | let pool = pool.clone(); 56 | thread::spawn(move || { 57 | let conn = pool.get().unwrap(); 58 | // use the connection 59 | // it will be returned to the pool when it falls out of scope. 60 | }) 61 | } 62 | } 63 | ``` 64 | 65 | ## License 66 | 67 | Licensed under either of 68 | * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 69 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 70 | 71 | at your option. 72 | 73 | ### Contribution 74 | 75 | Unless you explicitly state otherwise, any contribution intentionally submitted 76 | for inclusion in the work by you shall be dual licensed as above, without any 77 | additional terms or conditions. 78 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use scheduled_thread_pool::ScheduledThreadPool; 2 | use std::fmt; 3 | use std::marker::PhantomData; 4 | use std::sync::Arc; 5 | use std::time::Duration; 6 | 7 | use crate::{ 8 | CustomizeConnection, Error, HandleError, HandleEvent, LoggingErrorHandler, ManageConnection, 9 | NopConnectionCustomizer, NopEventHandler, Pool, 10 | }; 11 | 12 | /// A builder for a connection pool. 13 | pub struct Builder 14 | where 15 | M: ManageConnection, 16 | { 17 | max_size: u32, 18 | min_idle: Option, 19 | test_on_check_out: bool, 20 | max_lifetime: Option, 21 | idle_timeout: Option, 22 | connection_timeout: Duration, 23 | error_handler: Box>, 24 | connection_customizer: Box>, 25 | event_handler: Box, 26 | thread_pool: Option>, 27 | reaper_rate: Duration, 28 | _p: PhantomData, 29 | } 30 | 31 | impl fmt::Debug for Builder 32 | where 33 | M: ManageConnection, 34 | { 35 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 36 | fmt.debug_struct("Builder") 37 | .field("max_size", &self.max_size) 38 | .field("min_idle", &self.min_idle) 39 | .field("test_on_check_out", &self.test_on_check_out) 40 | .field("max_lifetime", &self.max_lifetime) 41 | .field("idle_timeout", &self.idle_timeout) 42 | .field("connection_timeout", &self.connection_timeout) 43 | .field("error_handler", &self.error_handler) 44 | .field("event_handler", &self.event_handler) 45 | .field("connection_customizer", &self.connection_customizer) 46 | .finish() 47 | } 48 | } 49 | 50 | impl Default for Builder 51 | where 52 | M: ManageConnection, 53 | { 54 | fn default() -> Builder { 55 | Builder { 56 | max_size: 10, 57 | min_idle: None, 58 | test_on_check_out: true, 59 | idle_timeout: Some(Duration::from_secs(10 * 60)), 60 | max_lifetime: Some(Duration::from_secs(30 * 60)), 61 | connection_timeout: Duration::from_secs(30), 62 | error_handler: Box::new(LoggingErrorHandler), 63 | event_handler: Box::new(NopEventHandler), 64 | connection_customizer: Box::new(NopConnectionCustomizer), 65 | thread_pool: None, 66 | reaper_rate: Duration::from_secs(30), 67 | _p: PhantomData, 68 | } 69 | } 70 | } 71 | 72 | impl Builder 73 | where 74 | M: ManageConnection, 75 | { 76 | /// Constructs a new `Builder`. 77 | /// 78 | /// Parameters are initialized with their default values. 79 | pub fn new() -> Builder { 80 | Builder::default() 81 | } 82 | 83 | /// Sets the maximum number of connections managed by the pool. 84 | /// 85 | /// Defaults to 10. 86 | /// 87 | /// # Panics 88 | /// 89 | /// Panics if `max_size` is 0. 90 | pub fn max_size(mut self, max_size: u32) -> Builder { 91 | assert!(max_size > 0, "max_size must be positive"); 92 | self.max_size = max_size; 93 | self 94 | } 95 | 96 | /// Sets the minimum idle connection count maintained by the pool. 97 | /// 98 | /// If set, the pool will try to maintain at least this many idle 99 | /// connections at all times, while respecting the value of `max_size`. 100 | /// 101 | /// Defaults to `None` (equivalent to the value of `max_size`). 102 | pub fn min_idle(mut self, min_idle: Option) -> Builder { 103 | self.min_idle = min_idle; 104 | self 105 | } 106 | 107 | /// Sets the thread pool used for asynchronous operations such as connection 108 | /// creation. 109 | /// 110 | /// Defaults to a new pool with 3 threads. 111 | pub fn thread_pool(mut self, thread_pool: Arc) -> Builder { 112 | self.thread_pool = Some(thread_pool); 113 | self 114 | } 115 | 116 | /// If true, the health of a connection will be verified via a call to 117 | /// `ConnectionManager::is_valid` before it is checked out of the pool. 118 | /// 119 | /// Defaults to true. 120 | pub fn test_on_check_out(mut self, test_on_check_out: bool) -> Builder { 121 | self.test_on_check_out = test_on_check_out; 122 | self 123 | } 124 | 125 | /// Sets the maximum lifetime of connections in the pool. 126 | /// 127 | /// If set, connections will be closed after existing for at most 30 seconds 128 | /// beyond this duration. 129 | /// 130 | /// If a connection reaches its maximum lifetime while checked out it will 131 | /// be closed when it is returned to the pool. 132 | /// 133 | /// Defaults to 30 minutes. 134 | /// 135 | /// # Panics 136 | /// 137 | /// Panics if `max_lifetime` is the zero `Duration`. 138 | pub fn max_lifetime(mut self, max_lifetime: Option) -> Builder { 139 | assert_ne!( 140 | max_lifetime, 141 | Some(Duration::from_secs(0)), 142 | "max_lifetime must be positive" 143 | ); 144 | self.max_lifetime = max_lifetime; 145 | self 146 | } 147 | 148 | /// Sets the idle timeout used by the pool. 149 | /// 150 | /// If set, connections will be closed after sitting idle for at most 30 151 | /// seconds beyond this duration. 152 | /// 153 | /// Defaults to 10 minutes. 154 | /// 155 | /// # Panics 156 | /// 157 | /// Panics if `idle_timeout` is the zero `Duration`. 158 | pub fn idle_timeout(mut self, idle_timeout: Option) -> Builder { 159 | assert_ne!( 160 | idle_timeout, 161 | Some(Duration::from_secs(0)), 162 | "idle_timeout must be positive" 163 | ); 164 | self.idle_timeout = idle_timeout; 165 | self 166 | } 167 | 168 | /// Sets the connection timeout used by the pool. 169 | /// 170 | /// Calls to `Pool::get` will wait this long for a connection to become 171 | /// available before returning an error. 172 | /// 173 | /// Defaults to 30 seconds. 174 | /// 175 | /// # Panics 176 | /// 177 | /// Panics if `connection_timeout` is the zero duration 178 | pub fn connection_timeout(mut self, connection_timeout: Duration) -> Builder { 179 | assert!( 180 | connection_timeout > Duration::from_secs(0), 181 | "connection_timeout must be positive" 182 | ); 183 | self.connection_timeout = connection_timeout; 184 | self 185 | } 186 | 187 | /// Sets the handler for errors reported in the pool. 188 | /// 189 | /// Defaults to the `LoggingErrorHandler`. 190 | pub fn error_handler(mut self, error_handler: Box>) -> Builder { 191 | self.error_handler = error_handler; 192 | self 193 | } 194 | 195 | /// Sets the handler for events reported by the pool. 196 | /// 197 | /// Defaults to the `NopEventHandler`. 198 | pub fn event_handler(mut self, event_handler: Box) -> Builder { 199 | self.event_handler = event_handler; 200 | self 201 | } 202 | 203 | /// Sets the connection customizer used by the pool. 204 | /// 205 | /// Defaults to the `NopConnectionCustomizer`. 206 | pub fn connection_customizer( 207 | mut self, 208 | connection_customizer: Box>, 209 | ) -> Builder { 210 | self.connection_customizer = connection_customizer; 211 | self 212 | } 213 | 214 | // used by tests 215 | #[allow(dead_code)] 216 | pub(crate) fn reaper_rate(mut self, reaper_rate: Duration) -> Builder { 217 | self.reaper_rate = reaper_rate; 218 | self 219 | } 220 | 221 | /// Consumes the builder, returning a new, initialized pool. 222 | /// 223 | /// It will block until the pool has established its configured minimum 224 | /// number of connections, or it times out. 225 | /// 226 | /// # Errors 227 | /// 228 | /// Returns an error if the pool is unable to open its minimum number of 229 | /// connections. 230 | /// 231 | /// # Panics 232 | /// 233 | /// Panics if `min_idle` is greater than `max_size`. 234 | pub fn build(self, manager: M) -> Result, Error> { 235 | let pool = self.build_unchecked(manager); 236 | pool.wait_for_initialization()?; 237 | Ok(pool) 238 | } 239 | 240 | /// Consumes the builder, returning a new pool. 241 | /// 242 | /// Unlike `build`, this method does not wait for any connections to be 243 | /// established before returning. 244 | /// 245 | /// # Panics 246 | /// 247 | /// Panics if `min_idle` is greater than `max_size`. 248 | pub fn build_unchecked(self, manager: M) -> Pool { 249 | if let Some(min_idle) = self.min_idle { 250 | assert!( 251 | self.max_size >= min_idle, 252 | "min_idle must be no larger than max_size" 253 | ); 254 | } 255 | 256 | let thread_pool = match self.thread_pool { 257 | Some(thread_pool) => thread_pool, 258 | None => Arc::new( 259 | ScheduledThreadPool::builder() 260 | .num_threads(3) 261 | .thread_name_pattern("r2d2-worker-{}") 262 | .build(), 263 | ), 264 | }; 265 | 266 | let config = Config { 267 | max_size: self.max_size, 268 | min_idle: self.min_idle, 269 | test_on_check_out: self.test_on_check_out, 270 | max_lifetime: self.max_lifetime, 271 | idle_timeout: self.idle_timeout, 272 | connection_timeout: self.connection_timeout, 273 | error_handler: self.error_handler, 274 | event_handler: self.event_handler, 275 | connection_customizer: self.connection_customizer, 276 | thread_pool, 277 | }; 278 | 279 | Pool::new_inner(config, manager, self.reaper_rate) 280 | } 281 | } 282 | 283 | pub struct Config { 284 | pub max_size: u32, 285 | pub min_idle: Option, 286 | pub test_on_check_out: bool, 287 | pub max_lifetime: Option, 288 | pub idle_timeout: Option, 289 | pub connection_timeout: Duration, 290 | pub error_handler: Box>, 291 | pub event_handler: Box, 292 | pub connection_customizer: Box>, 293 | pub thread_pool: Arc, 294 | } 295 | 296 | // manual to avoid bounds on C and E 297 | impl fmt::Debug for Config { 298 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 299 | fmt.debug_struct("Config") 300 | .field("max_size", &self.max_size) 301 | .field("min_idle", &self.min_idle) 302 | .field("test_on_check_out", &self.test_on_check_out) 303 | .field("max_lifetime", &self.max_lifetime) 304 | .field("idle_timeout", &self.idle_timeout) 305 | .field("connection_timeout", &self.connection_timeout) 306 | .field("error_handler", &self.error_handler) 307 | .field("event_handler", &self.event_handler) 308 | .field("connection_customizer", &self.connection_customizer) 309 | .finish() 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | //! Event subscriptions. 2 | 3 | use std::fmt; 4 | use std::time::Duration; 5 | 6 | /// A trait which is provided with information about events in a connection pool. 7 | pub trait HandleEvent: fmt::Debug + Sync + Send { 8 | /// Called when a new connection is acquired. 9 | /// 10 | /// The default implementation does nothing. 11 | #[allow(unused_variables)] 12 | fn handle_acquire(&self, event: AcquireEvent) {} 13 | 14 | /// Called when a connection is released. 15 | /// 16 | /// The default implementation does nothing. 17 | #[allow(unused_variables)] 18 | fn handle_release(&self, event: ReleaseEvent) {} 19 | 20 | /// Called when a connection is checked out from the pool. 21 | /// 22 | /// The default implementation does nothing. 23 | #[allow(unused_variables)] 24 | fn handle_checkout(&self, event: CheckoutEvent) {} 25 | 26 | /// Called when a checkout attempt times out. 27 | /// 28 | /// The default implementation does nothing. 29 | #[allow(unused_variables)] 30 | fn handle_timeout(&self, event: TimeoutEvent) {} 31 | 32 | /// Called when a connection is checked back into the pool. 33 | #[allow(unused_variables)] 34 | fn handle_checkin(&self, event: CheckinEvent) {} 35 | } 36 | 37 | /// A `HandleEvent` implementation which does nothing. 38 | #[derive(Copy, Clone, Debug)] 39 | pub struct NopEventHandler; 40 | 41 | impl HandleEvent for NopEventHandler {} 42 | 43 | /// Information about an acquire event. 44 | #[derive(Debug)] 45 | pub struct AcquireEvent { 46 | pub(crate) id: u64, 47 | } 48 | 49 | impl AcquireEvent { 50 | /// Returns the ID of the connection. 51 | #[inline] 52 | pub fn connection_id(&self) -> u64 { 53 | self.id 54 | } 55 | } 56 | 57 | /// Information about a release event. 58 | #[derive(Debug)] 59 | pub struct ReleaseEvent { 60 | pub(crate) id: u64, 61 | pub(crate) age: Duration, 62 | } 63 | 64 | impl ReleaseEvent { 65 | /// Returns the ID of the connection. 66 | #[inline] 67 | pub fn connection_id(&self) -> u64 { 68 | self.id 69 | } 70 | 71 | /// Returns the age of the connection. 72 | #[inline] 73 | pub fn age(&self) -> Duration { 74 | self.age 75 | } 76 | } 77 | 78 | /// Information about a checkout event. 79 | #[derive(Debug)] 80 | pub struct CheckoutEvent { 81 | pub(crate) id: u64, 82 | pub(crate) duration: Duration, 83 | } 84 | 85 | impl CheckoutEvent { 86 | /// Returns the ID of the connection. 87 | #[inline] 88 | pub fn connection_id(&self) -> u64 { 89 | self.id 90 | } 91 | 92 | /// Returns the time taken to check out the connection. 93 | #[inline] 94 | pub fn duration(&self) -> Duration { 95 | self.duration 96 | } 97 | } 98 | 99 | /// Information about a timeout event. 100 | #[derive(Debug)] 101 | pub struct TimeoutEvent { 102 | pub(crate) timeout: Duration, 103 | } 104 | 105 | impl TimeoutEvent { 106 | /// Returns the timeout of the failed checkout attempt. 107 | #[inline] 108 | pub fn timeout(&self) -> Duration { 109 | self.timeout 110 | } 111 | } 112 | 113 | /// Information about a checkin event. 114 | #[derive(Debug)] 115 | pub struct CheckinEvent { 116 | pub(crate) id: u64, 117 | pub(crate) duration: Duration, 118 | } 119 | 120 | impl CheckinEvent { 121 | /// Returns the ID of the connection. 122 | #[inline] 123 | pub fn connection_id(&self) -> u64 { 124 | self.id 125 | } 126 | 127 | /// Returns the amount of time the connection was checked out. 128 | #[inline] 129 | pub fn duration(&self) -> Duration { 130 | self.duration 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/extensions.rs: -------------------------------------------------------------------------------- 1 | use std::any::{Any, TypeId}; 2 | use std::collections::HashMap; 3 | 4 | /// A "type map" used to associate data with pooled connections. 5 | /// 6 | /// `Extensions` is a data structure mapping types to a value of that type. This 7 | /// can be used to, for example, cache prepared statements along side their 8 | /// connection. 9 | #[derive(Default)] 10 | pub struct Extensions(HashMap>); 11 | 12 | impl Extensions { 13 | /// Returns a new, empty `Extensions`. 14 | #[inline] 15 | pub fn new() -> Extensions { 16 | Extensions::default() 17 | } 18 | 19 | /// Inserts a new value into the map. 20 | /// 21 | /// Returns the previously stored value of that type, if present. 22 | pub fn insert(&mut self, value: T) -> Option 23 | where 24 | T: 'static + Sync + Send, 25 | { 26 | self.0 27 | .insert(TypeId::of::(), Box::new(value)) 28 | .and_then(|v| Box::::downcast(v).ok()) 29 | .map(|v| *v) 30 | } 31 | 32 | /// Returns a shared reference to the stored value of the specified type. 33 | pub fn get(&self) -> Option<&T> 34 | where 35 | T: 'static + Sync + Send, 36 | { 37 | self.0 38 | .get(&TypeId::of::()) 39 | .and_then(|v| v.downcast_ref()) 40 | } 41 | 42 | /// Returns a mutable reference to the stored value of the specified type. 43 | pub fn get_mut(&mut self) -> Option<&mut T> 44 | where 45 | T: 'static + Sync + Send, 46 | { 47 | self.0 48 | .get_mut(&TypeId::of::()) 49 | .and_then(|v| v.downcast_mut()) 50 | } 51 | 52 | /// Removes the value of the specified type from the map, returning it. 53 | pub fn remove(&mut self) -> Option 54 | where 55 | T: 'static + Sync + Send, 56 | { 57 | self.0 58 | .remove(&TypeId::of::()) 59 | .and_then(|v| Box::::downcast(v).ok()) 60 | .map(|v| *v) 61 | } 62 | 63 | /// Removes all values from the map. 64 | #[inline] 65 | pub fn clear(&mut self) { 66 | self.0.clear(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A generic connection pool. 2 | //! 3 | //! Opening a new database connection every time one is needed is both 4 | //! inefficient and can lead to resource exhaustion under high traffic 5 | //! conditions. A connection pool maintains a set of open connections to a 6 | //! database, handing them out for repeated use. 7 | //! 8 | //! r2d2 is agnostic to the connection type it is managing. Implementors of the 9 | //! `ManageConnection` trait provide the database-specific logic to create and 10 | //! check the health of connections. 11 | //! 12 | //! # Example 13 | //! 14 | //! Using an imaginary "foodb" database. 15 | //! 16 | //! ```rust,ignore 17 | //! use std::thread; 18 | //! 19 | //! extern crate r2d2; 20 | //! extern crate r2d2_foodb; 21 | //! 22 | //! fn main() { 23 | //! let manager = r2d2_foodb::FooConnectionManager::new("localhost:1234"); 24 | //! let pool = r2d2::Pool::builder() 25 | //! .max_size(15) 26 | //! .build(manager) 27 | //! .unwrap(); 28 | //! 29 | //! for _ in 0..20 { 30 | //! let pool = pool.clone(); 31 | //! thread::spawn(move || { 32 | //! let conn = pool.get().unwrap(); 33 | //! // use the connection 34 | //! // it will be returned to the pool when it falls out of scope. 35 | //! }) 36 | //! } 37 | //! } 38 | //! ``` 39 | #![warn(missing_docs)] 40 | #![doc(html_root_url = "https://docs.rs/r2d2/0.8")] 41 | 42 | use log::error; 43 | 44 | use parking_lot::{Condvar, Mutex, MutexGuard}; 45 | use std::cmp; 46 | use std::error; 47 | use std::fmt; 48 | use std::mem; 49 | use std::ops::{Deref, DerefMut}; 50 | use std::sync::atomic::{AtomicUsize, Ordering}; 51 | use std::sync::{Arc, Weak}; 52 | use std::time::{Duration, Instant}; 53 | 54 | pub use crate::config::Builder; 55 | use crate::config::Config; 56 | use crate::event::{AcquireEvent, CheckinEvent, CheckoutEvent, ReleaseEvent, TimeoutEvent}; 57 | pub use crate::event::{HandleEvent, NopEventHandler}; 58 | pub use crate::extensions::Extensions; 59 | 60 | mod config; 61 | pub mod event; 62 | mod extensions; 63 | 64 | #[cfg(test)] 65 | mod test; 66 | 67 | static CONNECTION_ID: AtomicUsize = AtomicUsize::new(0); 68 | 69 | /// A trait which provides connection-specific functionality. 70 | pub trait ManageConnection: Send + Sync + 'static { 71 | /// The connection type this manager deals with. 72 | type Connection: Send + 'static; 73 | 74 | /// The error type returned by `Connection`s. 75 | type Error: error::Error + 'static; 76 | 77 | /// Attempts to create a new connection. 78 | fn connect(&self) -> Result; 79 | 80 | /// Determines if the connection is still connected to the database. 81 | /// 82 | /// A standard implementation would check if a simple query like `SELECT 1` 83 | /// succeeds. 84 | fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error>; 85 | 86 | /// *Quickly* determines if the connection is no longer usable. 87 | /// 88 | /// This will be called synchronously every time a connection is returned 89 | /// to the pool, so it should *not* block. If it returns `true`, the 90 | /// connection will be discarded. 91 | /// 92 | /// For example, an implementation might check if the underlying TCP socket 93 | /// has disconnected. Implementations that do not support this kind of 94 | /// fast health check may simply return `false`. 95 | fn has_broken(&self, conn: &mut Self::Connection) -> bool; 96 | } 97 | 98 | /// A trait which handles errors reported by the `ManageConnection`. 99 | pub trait HandleError: fmt::Debug + Send + Sync + 'static { 100 | /// Handles an error. 101 | fn handle_error(&self, error: E); 102 | } 103 | 104 | /// A `HandleError` implementation which does nothing. 105 | #[derive(Copy, Clone, Debug)] 106 | pub struct NopErrorHandler; 107 | 108 | impl HandleError for NopErrorHandler { 109 | fn handle_error(&self, _: E) {} 110 | } 111 | 112 | /// A `HandleError` implementation which logs at the error level. 113 | #[derive(Copy, Clone, Debug)] 114 | pub struct LoggingErrorHandler; 115 | 116 | impl HandleError for LoggingErrorHandler 117 | where 118 | E: error::Error, 119 | { 120 | fn handle_error(&self, error: E) { 121 | error!("{}", error); 122 | } 123 | } 124 | 125 | /// A trait which allows for customization of connections. 126 | pub trait CustomizeConnection: fmt::Debug + Send + Sync + 'static { 127 | /// Called with connections immediately after they are returned from 128 | /// `ManageConnection::connect`. 129 | /// 130 | /// The default implementation simply returns `Ok(())`. 131 | /// 132 | /// # Errors 133 | /// 134 | /// If this method returns an error, the connection will be discarded. 135 | #[allow(unused_variables)] 136 | fn on_acquire(&self, conn: &mut C) -> Result<(), E> { 137 | Ok(()) 138 | } 139 | 140 | /// Called with connections when they are removed from the pool. 141 | /// 142 | /// The connections may be broken (as reported by `is_valid` or 143 | /// `has_broken`), or have simply timed out. 144 | /// 145 | /// The default implementation does nothing. 146 | #[allow(unused_variables)] 147 | fn on_release(&self, conn: C) {} 148 | } 149 | 150 | /// A `CustomizeConnection` which does nothing. 151 | #[derive(Copy, Clone, Debug)] 152 | pub struct NopConnectionCustomizer; 153 | 154 | impl CustomizeConnection for NopConnectionCustomizer {} 155 | 156 | struct Conn { 157 | conn: C, 158 | extensions: Extensions, 159 | birth: Instant, 160 | id: u64, 161 | } 162 | 163 | struct IdleConn { 164 | conn: Conn, 165 | idle_start: Instant, 166 | } 167 | 168 | struct PoolInternals { 169 | conns: Vec>, 170 | num_conns: u32, 171 | pending_conns: u32, 172 | last_error: Option, 173 | } 174 | 175 | struct SharedPool 176 | where 177 | M: ManageConnection, 178 | { 179 | config: Config, 180 | manager: M, 181 | internals: Mutex>, 182 | cond: Condvar, 183 | } 184 | 185 | fn drop_conns( 186 | shared: &Arc>, 187 | mut internals: MutexGuard>, 188 | conns: Vec>, 189 | ) where 190 | M: ManageConnection, 191 | { 192 | internals.num_conns -= conns.len() as u32; 193 | establish_idle_connections(shared, &mut internals); 194 | drop(internals); // make sure we run connection destructors without this locked 195 | 196 | for conn in conns { 197 | let event = ReleaseEvent { 198 | id: conn.id, 199 | age: conn.birth.elapsed(), 200 | }; 201 | shared.config.event_handler.handle_release(event); 202 | shared.config.connection_customizer.on_release(conn.conn); 203 | } 204 | } 205 | 206 | fn establish_idle_connections( 207 | shared: &Arc>, 208 | internals: &mut PoolInternals, 209 | ) where 210 | M: ManageConnection, 211 | { 212 | let min = shared.config.min_idle.unwrap_or(shared.config.max_size); 213 | let idle = internals.conns.len() as u32; 214 | for _ in idle..min { 215 | add_connection(shared, internals); 216 | } 217 | } 218 | 219 | fn add_connection(shared: &Arc>, internals: &mut PoolInternals) 220 | where 221 | M: ManageConnection, 222 | { 223 | if internals.num_conns + internals.pending_conns >= shared.config.max_size { 224 | return; 225 | } 226 | 227 | internals.pending_conns += 1; 228 | inner(Duration::from_secs(0), shared); 229 | 230 | fn inner(delay: Duration, shared: &Arc>) 231 | where 232 | M: ManageConnection, 233 | { 234 | let new_shared = Arc::downgrade(shared); 235 | shared.config.thread_pool.execute_after(delay, move || { 236 | let shared = match new_shared.upgrade() { 237 | Some(shared) => shared, 238 | None => return, 239 | }; 240 | 241 | let conn = shared.manager.connect().and_then(|mut conn| { 242 | shared 243 | .config 244 | .connection_customizer 245 | .on_acquire(&mut conn) 246 | .map(|_| conn) 247 | }); 248 | match conn { 249 | Ok(conn) => { 250 | let id = CONNECTION_ID.fetch_add(1, Ordering::Relaxed) as u64; 251 | 252 | let event = AcquireEvent { id }; 253 | shared.config.event_handler.handle_acquire(event); 254 | 255 | let mut internals = shared.internals.lock(); 256 | internals.last_error = None; 257 | let now = Instant::now(); 258 | let conn = IdleConn { 259 | conn: Conn { 260 | conn, 261 | extensions: Extensions::new(), 262 | birth: now, 263 | id, 264 | }, 265 | idle_start: now, 266 | }; 267 | internals.conns.push(conn); 268 | internals.pending_conns -= 1; 269 | internals.num_conns += 1; 270 | shared.cond.notify_one(); 271 | } 272 | Err(err) => { 273 | shared.internals.lock().last_error = Some(err.to_string()); 274 | shared.config.error_handler.handle_error(err); 275 | let delay = cmp::max(Duration::from_millis(200), delay); 276 | let delay = cmp::min(shared.config.connection_timeout / 2, delay * 2); 277 | inner(delay, &shared); 278 | } 279 | } 280 | }); 281 | } 282 | } 283 | 284 | fn reap_connections(shared: &Weak>) 285 | where 286 | M: ManageConnection, 287 | { 288 | let shared = match shared.upgrade() { 289 | Some(shared) => shared, 290 | None => return, 291 | }; 292 | 293 | let mut old = Vec::with_capacity(shared.config.max_size as usize); 294 | let mut to_drop = vec![]; 295 | 296 | let mut internals = shared.internals.lock(); 297 | mem::swap(&mut old, &mut internals.conns); 298 | let now = Instant::now(); 299 | for conn in old { 300 | let mut reap = false; 301 | if let Some(timeout) = shared.config.idle_timeout { 302 | reap |= now - conn.idle_start >= timeout; 303 | } 304 | if let Some(lifetime) = shared.config.max_lifetime { 305 | reap |= now - conn.conn.birth >= lifetime; 306 | } 307 | if reap { 308 | to_drop.push(conn.conn); 309 | } else { 310 | internals.conns.push(conn); 311 | } 312 | } 313 | drop_conns(&shared, internals, to_drop); 314 | } 315 | 316 | /// A generic connection pool. 317 | pub struct Pool(Arc>) 318 | where 319 | M: ManageConnection; 320 | 321 | /// Returns a new `Pool` referencing the same state as `self`. 322 | impl Clone for Pool 323 | where 324 | M: ManageConnection, 325 | { 326 | fn clone(&self) -> Pool { 327 | Pool(self.0.clone()) 328 | } 329 | } 330 | 331 | impl fmt::Debug for Pool 332 | where 333 | M: ManageConnection + fmt::Debug, 334 | { 335 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 336 | fmt.debug_struct("Pool") 337 | .field("state", &self.state()) 338 | .field("config", &self.0.config) 339 | .field("manager", &self.0.manager) 340 | .finish() 341 | } 342 | } 343 | 344 | impl Pool 345 | where 346 | M: ManageConnection, 347 | { 348 | /// Creates a new connection pool with a default configuration. 349 | pub fn new(manager: M) -> Result, Error> { 350 | Pool::builder().build(manager) 351 | } 352 | 353 | /// Returns a builder type to configure a new pool. 354 | pub fn builder() -> Builder { 355 | Builder::new() 356 | } 357 | 358 | // for testing 359 | fn new_inner( 360 | config: Config, 361 | manager: M, 362 | reaper_rate: Duration, 363 | ) -> Pool { 364 | let internals = PoolInternals { 365 | conns: Vec::with_capacity(config.max_size as usize), 366 | num_conns: 0, 367 | pending_conns: 0, 368 | last_error: None, 369 | }; 370 | 371 | let shared = Arc::new(SharedPool { 372 | config, 373 | manager, 374 | internals: Mutex::new(internals), 375 | cond: Condvar::new(), 376 | }); 377 | 378 | establish_idle_connections(&shared, &mut shared.internals.lock()); 379 | 380 | if shared.config.max_lifetime.is_some() || shared.config.idle_timeout.is_some() { 381 | let s = Arc::downgrade(&shared); 382 | shared 383 | .config 384 | .thread_pool 385 | .execute_at_fixed_rate(reaper_rate, reaper_rate, move || reap_connections(&s)); 386 | } 387 | 388 | Pool(shared) 389 | } 390 | 391 | fn wait_for_initialization(&self) -> Result<(), Error> { 392 | let end = Instant::now() + self.0.config.connection_timeout; 393 | let mut internals = self.0.internals.lock(); 394 | 395 | let initial_size = self.0.config.min_idle.unwrap_or(self.0.config.max_size); 396 | 397 | while internals.num_conns != initial_size { 398 | if self.0.cond.wait_until(&mut internals, end).timed_out() { 399 | return Err(Error(internals.last_error.take())); 400 | } 401 | } 402 | 403 | Ok(()) 404 | } 405 | 406 | /// Retrieves a connection from the pool. 407 | /// 408 | /// Waits for at most the configured connection timeout before returning an 409 | /// error. 410 | pub fn get(&self) -> Result, Error> { 411 | self.get_timeout(self.0.config.connection_timeout) 412 | } 413 | 414 | /// Retrieves a connection from the pool, waiting for at most `timeout` 415 | /// 416 | /// The given timeout will be used instead of the configured connection 417 | /// timeout. 418 | pub fn get_timeout(&self, timeout: Duration) -> Result, Error> { 419 | let start = Instant::now(); 420 | let end = start + timeout; 421 | let mut internals = self.0.internals.lock(); 422 | 423 | loop { 424 | match self.try_get_inner(internals) { 425 | Ok(conn) => { 426 | let event = CheckoutEvent { 427 | id: conn.conn.as_ref().unwrap().id, 428 | duration: start.elapsed(), 429 | }; 430 | self.0.config.event_handler.handle_checkout(event); 431 | return Ok(conn); 432 | } 433 | Err(i) => internals = i, 434 | } 435 | 436 | add_connection(&self.0, &mut internals); 437 | 438 | if self.0.cond.wait_until(&mut internals, end).timed_out() { 439 | let event = TimeoutEvent { timeout }; 440 | self.0.config.event_handler.handle_timeout(event); 441 | 442 | return Err(Error(internals.last_error.take())); 443 | } 444 | } 445 | } 446 | 447 | /// Attempts to retrieve a connection from the pool if there is one 448 | /// available. 449 | /// 450 | /// Returns `None` if there are no idle connections available in the pool. 451 | /// This method will not block waiting to establish a new connection. 452 | pub fn try_get(&self) -> Option> { 453 | self.try_get_inner(self.0.internals.lock()).ok() 454 | } 455 | 456 | fn try_get_inner<'a>( 457 | &'a self, 458 | mut internals: MutexGuard<'a, PoolInternals>, 459 | ) -> Result, MutexGuard<'a, PoolInternals>> { 460 | loop { 461 | if let Some(mut conn) = internals.conns.pop() { 462 | establish_idle_connections(&self.0, &mut internals); 463 | drop(internals); 464 | 465 | if self.0.config.test_on_check_out { 466 | if let Err(e) = self.0.manager.is_valid(&mut conn.conn.conn) { 467 | let msg = e.to_string(); 468 | self.0.config.error_handler.handle_error(e); 469 | // FIXME we shouldn't have to lock, unlock, and relock here 470 | internals = self.0.internals.lock(); 471 | internals.last_error = Some(msg); 472 | drop_conns(&self.0, internals, vec![conn.conn]); 473 | internals = self.0.internals.lock(); 474 | continue; 475 | } 476 | } 477 | 478 | return Ok(PooledConnection { 479 | pool: self.clone(), 480 | checkout: Instant::now(), 481 | conn: Some(conn.conn), 482 | }); 483 | } else { 484 | return Err(internals); 485 | } 486 | } 487 | } 488 | 489 | fn put_back(&self, checkout: Instant, mut conn: Conn) { 490 | let event = CheckinEvent { 491 | id: conn.id, 492 | duration: checkout.elapsed(), 493 | }; 494 | self.0.config.event_handler.handle_checkin(event); 495 | 496 | // This is specified to be fast, but call it before locking anyways 497 | let broken = self.0.manager.has_broken(&mut conn.conn); 498 | 499 | let mut internals = self.0.internals.lock(); 500 | if broken { 501 | drop_conns(&self.0, internals, vec![conn]); 502 | } else { 503 | let conn = IdleConn { 504 | conn, 505 | idle_start: Instant::now(), 506 | }; 507 | internals.conns.push(conn); 508 | self.0.cond.notify_one(); 509 | } 510 | } 511 | 512 | /// Returns information about the current state of the pool. 513 | pub fn state(&self) -> State { 514 | let internals = self.0.internals.lock(); 515 | State { 516 | connections: internals.num_conns, 517 | idle_connections: internals.conns.len() as u32, 518 | } 519 | } 520 | 521 | /// Returns the configured maximum pool size. 522 | pub fn max_size(&self) -> u32 { 523 | self.0.config.max_size 524 | } 525 | 526 | /// Returns the configured minimum idle connection count. 527 | pub fn min_idle(&self) -> Option { 528 | self.0.config.min_idle 529 | } 530 | 531 | /// Returns if the pool is configured to test connections on check out. 532 | pub fn test_on_check_out(&self) -> bool { 533 | self.0.config.test_on_check_out 534 | } 535 | 536 | /// Returns the configured maximum connection lifetime. 537 | pub fn max_lifetime(&self) -> Option { 538 | self.0.config.max_lifetime 539 | } 540 | 541 | /// Returns the configured idle connection timeout. 542 | pub fn idle_timeout(&self) -> Option { 543 | self.0.config.idle_timeout 544 | } 545 | 546 | /// Returns the configured connection timeout. 547 | pub fn connection_timeout(&self) -> Duration { 548 | self.0.config.connection_timeout 549 | } 550 | } 551 | 552 | /// The error type returned by methods in this crate. 553 | #[derive(Debug)] 554 | pub struct Error(Option); 555 | 556 | impl fmt::Display for Error { 557 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 558 | fmt.write_str("timed out waiting for connection")?; 559 | if let Some(ref err) = self.0 { 560 | write!(fmt, ": {}", err)?; 561 | } 562 | Ok(()) 563 | } 564 | } 565 | 566 | impl error::Error for Error {} 567 | 568 | /// Information about the state of a `Pool`. 569 | #[non_exhaustive] 570 | #[derive(Debug)] 571 | pub struct State { 572 | /// The number of connections currently being managed by the pool. 573 | pub connections: u32, 574 | /// The number of idle connections. 575 | pub idle_connections: u32, 576 | } 577 | 578 | /// A smart pointer wrapping a connection. 579 | pub struct PooledConnection 580 | where 581 | M: ManageConnection, 582 | { 583 | pool: Pool, 584 | checkout: Instant, 585 | conn: Option>, 586 | } 587 | 588 | impl fmt::Debug for PooledConnection 589 | where 590 | M: ManageConnection, 591 | M::Connection: fmt::Debug, 592 | { 593 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 594 | fmt::Debug::fmt(&self.conn.as_ref().unwrap().conn, fmt) 595 | } 596 | } 597 | 598 | impl Drop for PooledConnection 599 | where 600 | M: ManageConnection, 601 | { 602 | fn drop(&mut self) { 603 | self.pool.put_back(self.checkout, self.conn.take().unwrap()); 604 | } 605 | } 606 | 607 | impl Deref for PooledConnection 608 | where 609 | M: ManageConnection, 610 | { 611 | type Target = M::Connection; 612 | 613 | fn deref(&self) -> &M::Connection { 614 | &self.conn.as_ref().unwrap().conn 615 | } 616 | } 617 | 618 | impl DerefMut for PooledConnection 619 | where 620 | M: ManageConnection, 621 | { 622 | fn deref_mut(&mut self) -> &mut M::Connection { 623 | &mut self.conn.as_mut().unwrap().conn 624 | } 625 | } 626 | 627 | impl PooledConnection 628 | where 629 | M: ManageConnection, 630 | { 631 | /// Returns a shared reference to the extensions associated with this connection. 632 | pub fn extensions(this: &Self) -> &Extensions { 633 | &this.conn.as_ref().unwrap().extensions 634 | } 635 | 636 | /// Returns a mutable reference to the extensions associated with this connection. 637 | pub fn extensions_mut(this: &mut Self) -> &mut Extensions { 638 | &mut this.conn.as_mut().unwrap().extensions 639 | } 640 | } 641 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::Mutex; 2 | use std::sync::atomic::{AtomicBool, AtomicIsize, AtomicUsize, Ordering}; 3 | use std::sync::mpsc::{self, Receiver, SyncSender}; 4 | use std::sync::Arc; 5 | use std::time::{Duration, Instant}; 6 | use std::{error, fmt, mem, thread}; 7 | 8 | use crate::event::{AcquireEvent, CheckinEvent, CheckoutEvent, ReleaseEvent, TimeoutEvent}; 9 | use crate::{CustomizeConnection, HandleEvent, ManageConnection, Pool, PooledConnection}; 10 | 11 | #[derive(Debug)] 12 | pub struct Error; 13 | 14 | impl fmt::Display for Error { 15 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 16 | fmt.write_str("blammo") 17 | } 18 | } 19 | 20 | impl error::Error for Error { 21 | fn description(&self) -> &str { 22 | "Error" 23 | } 24 | } 25 | 26 | #[derive(Debug, PartialEq)] 27 | struct FakeConnection(bool); 28 | 29 | struct OkManager; 30 | 31 | impl ManageConnection for OkManager { 32 | type Connection = FakeConnection; 33 | type Error = Error; 34 | 35 | fn connect(&self) -> Result { 36 | Ok(FakeConnection(true)) 37 | } 38 | 39 | fn is_valid(&self, _: &mut FakeConnection) -> Result<(), Error> { 40 | Ok(()) 41 | } 42 | 43 | fn has_broken(&self, _: &mut FakeConnection) -> bool { 44 | false 45 | } 46 | } 47 | 48 | struct NthConnectFailManager { 49 | n: Mutex, 50 | } 51 | 52 | impl ManageConnection for NthConnectFailManager { 53 | type Connection = FakeConnection; 54 | type Error = Error; 55 | 56 | fn connect(&self) -> Result { 57 | let mut n = self.n.lock(); 58 | if *n > 0 { 59 | *n -= 1; 60 | Ok(FakeConnection(true)) 61 | } else { 62 | Err(Error) 63 | } 64 | } 65 | 66 | fn is_valid(&self, _: &mut FakeConnection) -> Result<(), Error> { 67 | Ok(()) 68 | } 69 | 70 | fn has_broken(&self, _: &mut FakeConnection) -> bool { 71 | false 72 | } 73 | } 74 | 75 | #[test] 76 | fn test_max_size_ok() { 77 | let manager = NthConnectFailManager { n: Mutex::new(5) }; 78 | let pool = Pool::builder().max_size(5).build(manager).unwrap(); 79 | let mut conns = vec![]; 80 | for _ in 0..5 { 81 | conns.push(pool.get().ok().unwrap()); 82 | } 83 | } 84 | 85 | #[test] 86 | fn test_acquire_release() { 87 | let pool = Pool::builder().max_size(2).build(OkManager).unwrap(); 88 | 89 | let conn1 = pool.get().ok().unwrap(); 90 | let conn2 = pool.get().ok().unwrap(); 91 | drop(conn1); 92 | let conn3 = pool.get().ok().unwrap(); 93 | drop(conn2); 94 | drop(conn3); 95 | } 96 | 97 | #[test] 98 | fn try_get() { 99 | let pool = Pool::builder().max_size(2).build(OkManager).unwrap(); 100 | 101 | let conn1 = pool.try_get(); 102 | let conn2 = pool.try_get(); 103 | let conn3 = pool.try_get(); 104 | 105 | assert!(conn1.is_some()); 106 | assert!(conn2.is_some()); 107 | assert!(conn3.is_none()); 108 | 109 | drop(conn1); 110 | 111 | assert!(pool.try_get().is_some()); 112 | } 113 | 114 | #[test] 115 | fn get_timeout() { 116 | let pool = Pool::builder() 117 | .max_size(1) 118 | .connection_timeout(Duration::from_millis(500)) 119 | .build(OkManager) 120 | .unwrap(); 121 | 122 | let timeout = Duration::from_millis(100); 123 | let succeeds_immediately = pool.get_timeout(timeout); 124 | 125 | assert!(succeeds_immediately.is_ok()); 126 | 127 | thread::spawn(move || { 128 | thread::sleep(Duration::from_millis(50)); 129 | drop(succeeds_immediately); 130 | }); 131 | 132 | let succeeds_delayed = pool.get_timeout(timeout); 133 | assert!(succeeds_delayed.is_ok()); 134 | 135 | thread::spawn(move || { 136 | thread::sleep(Duration::from_millis(150)); 137 | drop(succeeds_delayed); 138 | }); 139 | 140 | let fails = pool.get_timeout(timeout); 141 | assert!(fails.is_err()); 142 | } 143 | 144 | #[test] 145 | fn test_is_send_sync() { 146 | fn is_send_sync() {} 147 | is_send_sync::>(); 148 | } 149 | 150 | #[test] 151 | fn test_issue_2_unlocked_during_is_valid() { 152 | struct BlockingChecker { 153 | first: AtomicBool, 154 | s: Mutex>, 155 | r: Mutex>, 156 | } 157 | 158 | impl ManageConnection for BlockingChecker { 159 | type Connection = FakeConnection; 160 | type Error = Error; 161 | 162 | fn connect(&self) -> Result { 163 | Ok(FakeConnection(true)) 164 | } 165 | 166 | fn is_valid(&self, _: &mut FakeConnection) -> Result<(), Error> { 167 | if self 168 | .first 169 | .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) 170 | .unwrap_or_else(|e| e) 171 | { 172 | self.s.lock().send(()).unwrap(); 173 | self.r.lock().recv().unwrap(); 174 | } 175 | Ok(()) 176 | } 177 | 178 | fn has_broken(&self, _: &mut FakeConnection) -> bool { 179 | false 180 | } 181 | } 182 | 183 | let (s1, r1) = mpsc::sync_channel(0); 184 | let (s2, r2) = mpsc::sync_channel(0); 185 | 186 | let manager = BlockingChecker { 187 | first: AtomicBool::new(true), 188 | s: Mutex::new(s1), 189 | r: Mutex::new(r2), 190 | }; 191 | 192 | let pool = Pool::builder() 193 | .test_on_check_out(true) 194 | .max_size(2) 195 | .build(manager) 196 | .unwrap(); 197 | 198 | let p2 = pool.clone(); 199 | let t = thread::spawn(move || { 200 | p2.get().ok().unwrap(); 201 | }); 202 | 203 | r1.recv().unwrap(); 204 | // get call by other task has triggered the health check 205 | pool.get().ok().unwrap(); 206 | s2.send(()).ok().unwrap(); 207 | 208 | t.join().ok().unwrap(); 209 | } 210 | 211 | #[test] 212 | fn test_drop_on_broken() { 213 | static DROPPED: AtomicBool = AtomicBool::new(false); 214 | DROPPED.store(false, Ordering::SeqCst); 215 | 216 | struct Connection; 217 | 218 | impl Drop for Connection { 219 | fn drop(&mut self) { 220 | DROPPED.store(true, Ordering::SeqCst); 221 | } 222 | } 223 | 224 | struct Handler; 225 | 226 | impl ManageConnection for Handler { 227 | type Connection = Connection; 228 | type Error = Error; 229 | 230 | fn connect(&self) -> Result { 231 | Ok(Connection) 232 | } 233 | 234 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 235 | Ok(()) 236 | } 237 | 238 | fn has_broken(&self, _: &mut Connection) -> bool { 239 | true 240 | } 241 | } 242 | 243 | let pool = Pool::new(Handler).unwrap(); 244 | 245 | drop(pool.get().ok().unwrap()); 246 | 247 | assert!(DROPPED.load(Ordering::SeqCst)); 248 | } 249 | 250 | #[test] 251 | fn test_initialization_failure() { 252 | let manager = NthConnectFailManager { n: Mutex::new(0) }; 253 | let err = Pool::builder() 254 | .connection_timeout(Duration::from_secs(1)) 255 | .build(manager) 256 | .err() 257 | .unwrap(); 258 | assert!(err.to_string().contains("blammo")); 259 | } 260 | 261 | #[test] 262 | fn test_lazy_initialization_failure() { 263 | let manager = NthConnectFailManager { n: Mutex::new(0) }; 264 | let pool = Pool::builder() 265 | .connection_timeout(Duration::from_secs(1)) 266 | .build_unchecked(manager); 267 | let err = pool.get().err().unwrap(); 268 | assert!(err.to_string().contains("blammo")); 269 | } 270 | 271 | #[test] 272 | fn test_get_global_timeout() { 273 | let pool = Pool::builder() 274 | .max_size(1) 275 | .connection_timeout(Duration::from_secs(1)) 276 | .build(OkManager) 277 | .unwrap(); 278 | let _c = pool.get().unwrap(); 279 | let started_waiting = Instant::now(); 280 | pool.get().err().unwrap(); 281 | // Elapsed time won't be *exactly* 1 second, but it will certainly be 282 | // less than 2 seconds 283 | assert_eq!(started_waiting.elapsed().as_secs(), 1); 284 | } 285 | 286 | #[test] 287 | fn test_connection_customizer() { 288 | static RELEASED: AtomicBool = AtomicBool::new(false); 289 | RELEASED.store(false, Ordering::SeqCst); 290 | 291 | static DROPPED: AtomicBool = AtomicBool::new(false); 292 | DROPPED.store(false, Ordering::SeqCst); 293 | 294 | struct Connection(i32); 295 | 296 | impl Drop for Connection { 297 | fn drop(&mut self) { 298 | DROPPED.store(true, Ordering::SeqCst); 299 | } 300 | } 301 | 302 | struct Handler; 303 | 304 | impl ManageConnection for Handler { 305 | type Connection = Connection; 306 | type Error = Error; 307 | 308 | fn connect(&self) -> Result { 309 | Ok(Connection(0)) 310 | } 311 | 312 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 313 | Ok(()) 314 | } 315 | 316 | fn has_broken(&self, _: &mut Connection) -> bool { 317 | true 318 | } 319 | } 320 | 321 | #[derive(Debug)] 322 | struct Customizer; 323 | 324 | impl CustomizeConnection for Customizer { 325 | fn on_acquire(&self, conn: &mut Connection) -> Result<(), Error> { 326 | if !DROPPED.load(Ordering::SeqCst) { 327 | Err(Error) 328 | } else { 329 | conn.0 = 1; 330 | Ok(()) 331 | } 332 | } 333 | 334 | fn on_release(&self, _: Connection) { 335 | RELEASED.store(true, Ordering::SeqCst); 336 | } 337 | } 338 | 339 | let pool = Pool::builder() 340 | .connection_customizer(Box::new(Customizer)) 341 | .build(Handler) 342 | .unwrap(); 343 | 344 | { 345 | let conn = pool.get().unwrap(); 346 | assert_eq!(1, conn.0); 347 | assert!(!RELEASED.load(Ordering::SeqCst)); 348 | assert!(DROPPED.load(Ordering::SeqCst)); 349 | } 350 | assert!(RELEASED.load(Ordering::SeqCst)); 351 | } 352 | 353 | #[test] 354 | fn test_idle_timeout() { 355 | static DROPPED: AtomicUsize = AtomicUsize::new(0); 356 | 357 | struct Connection; 358 | 359 | impl Drop for Connection { 360 | fn drop(&mut self) { 361 | DROPPED.fetch_add(1, Ordering::SeqCst); 362 | } 363 | } 364 | 365 | struct Handler(AtomicIsize); 366 | 367 | impl ManageConnection for Handler { 368 | type Connection = Connection; 369 | type Error = Error; 370 | 371 | fn connect(&self) -> Result { 372 | if self.0.fetch_sub(1, Ordering::SeqCst) > 0 { 373 | Ok(Connection) 374 | } else { 375 | Err(Error) 376 | } 377 | } 378 | 379 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 380 | Ok(()) 381 | } 382 | 383 | fn has_broken(&self, _: &mut Connection) -> bool { 384 | false 385 | } 386 | } 387 | 388 | let pool = Pool::builder() 389 | .max_size(5) 390 | .idle_timeout(Some(Duration::from_secs(1))) 391 | .reaper_rate(Duration::from_secs(1)) 392 | .build(Handler(AtomicIsize::new(5))) 393 | .unwrap(); 394 | let conn = pool.get().unwrap(); 395 | thread::sleep(Duration::from_secs(2)); 396 | assert_eq!(4, DROPPED.load(Ordering::SeqCst)); 397 | drop(conn); 398 | assert_eq!(4, DROPPED.load(Ordering::SeqCst)); 399 | } 400 | 401 | #[test] 402 | fn idle_timeout_partial_use() { 403 | static DROPPED: AtomicUsize = AtomicUsize::new(0); 404 | 405 | struct Connection; 406 | 407 | impl Drop for Connection { 408 | fn drop(&mut self) { 409 | DROPPED.fetch_add(1, Ordering::SeqCst); 410 | } 411 | } 412 | 413 | struct Handler(AtomicIsize); 414 | 415 | impl ManageConnection for Handler { 416 | type Connection = Connection; 417 | type Error = Error; 418 | 419 | fn connect(&self) -> Result { 420 | if self.0.fetch_sub(1, Ordering::SeqCst) > 0 { 421 | Ok(Connection) 422 | } else { 423 | Err(Error) 424 | } 425 | } 426 | 427 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 428 | Ok(()) 429 | } 430 | 431 | fn has_broken(&self, _: &mut Connection) -> bool { 432 | false 433 | } 434 | } 435 | 436 | let pool = Pool::builder() 437 | .max_size(5) 438 | .idle_timeout(Some(Duration::from_secs(1))) 439 | .reaper_rate(Duration::from_secs(1)) 440 | .build(Handler(AtomicIsize::new(5))) 441 | .unwrap(); 442 | for _ in 0..8 { 443 | thread::sleep(Duration::from_millis(250)); 444 | pool.get().unwrap(); 445 | } 446 | assert_eq!(4, DROPPED.load(Ordering::SeqCst)); 447 | assert_eq!(1, pool.state().connections); 448 | } 449 | 450 | #[test] 451 | fn test_max_lifetime() { 452 | static DROPPED: AtomicUsize = AtomicUsize::new(0); 453 | 454 | struct Connection; 455 | 456 | impl Drop for Connection { 457 | fn drop(&mut self) { 458 | DROPPED.fetch_add(1, Ordering::SeqCst); 459 | } 460 | } 461 | 462 | struct Handler(AtomicIsize); 463 | 464 | impl ManageConnection for Handler { 465 | type Connection = Connection; 466 | type Error = Error; 467 | 468 | fn connect(&self) -> Result { 469 | if self.0.fetch_sub(1, Ordering::SeqCst) > 0 { 470 | Ok(Connection) 471 | } else { 472 | Err(Error) 473 | } 474 | } 475 | 476 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 477 | Ok(()) 478 | } 479 | 480 | fn has_broken(&self, _: &mut Connection) -> bool { 481 | false 482 | } 483 | } 484 | 485 | let pool = Pool::builder() 486 | .max_size(5) 487 | .max_lifetime(Some(Duration::from_secs(1))) 488 | .connection_timeout(Duration::from_secs(1)) 489 | .reaper_rate(Duration::from_secs(1)) 490 | .build(Handler(AtomicIsize::new(5))) 491 | .unwrap(); 492 | let conn = pool.get().unwrap(); 493 | thread::sleep(Duration::from_secs(2)); 494 | assert_eq!(4, DROPPED.load(Ordering::SeqCst)); 495 | drop(conn); 496 | thread::sleep(Duration::from_secs(2)); 497 | assert_eq!(5, DROPPED.load(Ordering::SeqCst)); 498 | assert!(pool.get().is_err()); 499 | } 500 | 501 | #[test] 502 | fn min_idle() { 503 | struct Connection; 504 | 505 | struct Handler; 506 | 507 | impl ManageConnection for Handler { 508 | type Connection = Connection; 509 | type Error = Error; 510 | 511 | fn connect(&self) -> Result { 512 | Ok(Connection) 513 | } 514 | 515 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 516 | Ok(()) 517 | } 518 | 519 | fn has_broken(&self, _: &mut Connection) -> bool { 520 | false 521 | } 522 | } 523 | 524 | let pool = Pool::builder() 525 | .max_size(5) 526 | .min_idle(Some(2)) 527 | .build(Handler) 528 | .unwrap(); 529 | thread::sleep(Duration::from_secs(1)); 530 | assert_eq!(2, pool.state().idle_connections); 531 | assert_eq!(2, pool.state().connections); 532 | let conns = (0..3).map(|_| pool.get().unwrap()).collect::>(); 533 | thread::sleep(Duration::from_secs(1)); 534 | assert_eq!(2, pool.state().idle_connections); 535 | assert_eq!(5, pool.state().connections); 536 | mem::drop(conns); 537 | assert_eq!(5, pool.state().idle_connections); 538 | assert_eq!(5, pool.state().connections); 539 | } 540 | 541 | #[test] 542 | fn conns_drop_on_pool_drop() { 543 | static DROPPED: AtomicUsize = AtomicUsize::new(0); 544 | 545 | struct Connection; 546 | 547 | impl Drop for Connection { 548 | fn drop(&mut self) { 549 | DROPPED.fetch_add(1, Ordering::SeqCst); 550 | } 551 | } 552 | 553 | struct Handler; 554 | 555 | impl ManageConnection for Handler { 556 | type Connection = Connection; 557 | type Error = Error; 558 | 559 | fn connect(&self) -> Result { 560 | Ok(Connection) 561 | } 562 | 563 | fn is_valid(&self, _: &mut Connection) -> Result<(), Error> { 564 | Ok(()) 565 | } 566 | 567 | fn has_broken(&self, _: &mut Connection) -> bool { 568 | false 569 | } 570 | } 571 | 572 | let pool = Pool::builder() 573 | .max_lifetime(Some(Duration::from_secs(10))) 574 | .max_size(10) 575 | .build(Handler) 576 | .unwrap(); 577 | drop(pool); 578 | for _ in 0..10 { 579 | if DROPPED.load(Ordering::SeqCst) == 10 { 580 | return; 581 | } 582 | thread::sleep(Duration::from_secs(1)); 583 | } 584 | panic!("timed out waiting for connections to drop"); 585 | } 586 | 587 | #[test] 588 | fn events() { 589 | #[derive(Debug)] 590 | enum Event { 591 | Acquire(AcquireEvent), 592 | Release(ReleaseEvent), 593 | Checkout(CheckoutEvent), 594 | Checkin(CheckinEvent), 595 | Timeout(TimeoutEvent), 596 | } 597 | 598 | #[derive(Debug)] 599 | struct TestEventHandler(Arc>>); 600 | 601 | impl HandleEvent for TestEventHandler { 602 | fn handle_acquire(&self, event: AcquireEvent) { 603 | self.0.lock().push(Event::Acquire(event)); 604 | } 605 | 606 | fn handle_release(&self, event: ReleaseEvent) { 607 | self.0.lock().push(Event::Release(event)); 608 | } 609 | 610 | fn handle_checkout(&self, event: CheckoutEvent) { 611 | self.0.lock().push(Event::Checkout(event)); 612 | } 613 | 614 | fn handle_timeout(&self, event: TimeoutEvent) { 615 | self.0.lock().push(Event::Timeout(event)); 616 | } 617 | 618 | fn handle_checkin(&self, event: CheckinEvent) { 619 | self.0.lock().push(Event::Checkin(event)); 620 | } 621 | } 622 | 623 | struct TestConnection; 624 | 625 | struct TestConnectionManager; 626 | 627 | impl ManageConnection for TestConnectionManager { 628 | type Connection = TestConnection; 629 | type Error = Error; 630 | 631 | fn connect(&self) -> Result { 632 | Ok(TestConnection) 633 | } 634 | 635 | fn is_valid(&self, _: &mut TestConnection) -> Result<(), Error> { 636 | Ok(()) 637 | } 638 | 639 | fn has_broken(&self, _: &mut TestConnection) -> bool { 640 | true 641 | } 642 | } 643 | 644 | let events = Arc::new(Mutex::new(vec![])); 645 | 646 | let creation = Instant::now(); 647 | let pool = Pool::builder() 648 | .max_size(1) 649 | .connection_timeout(Duration::from_millis(250)) 650 | .event_handler(Box::new(TestEventHandler(events.clone()))) 651 | .build(TestConnectionManager) 652 | .unwrap(); 653 | 654 | let start = Instant::now(); 655 | let conn = pool.get().unwrap(); 656 | let checkout = start.elapsed(); 657 | 658 | pool.get_timeout(Duration::from_millis(123)).err().unwrap(); 659 | 660 | drop(conn); 661 | let checkin = start.elapsed(); 662 | let release = creation.elapsed(); 663 | 664 | let _conn2 = pool.get().unwrap(); 665 | 666 | let events = events.lock(); 667 | 668 | let id = match events[0] { 669 | Event::Acquire(ref event) => event.connection_id(), 670 | _ => unreachable!(), 671 | }; 672 | 673 | match events[1] { 674 | Event::Checkout(ref event) => { 675 | assert_eq!(event.connection_id(), id); 676 | assert!(event.duration() <= checkout); 677 | } 678 | _ => unreachable!(), 679 | } 680 | 681 | match events[2] { 682 | Event::Timeout(ref event) => assert_eq!(event.timeout(), Duration::from_millis(123)), 683 | _ => unreachable!(), 684 | } 685 | 686 | match events[3] { 687 | Event::Checkin(ref event) => { 688 | assert_eq!(event.connection_id(), id); 689 | assert!(event.duration() <= checkin); 690 | } 691 | _ => unreachable!(), 692 | } 693 | 694 | match events[4] { 695 | Event::Release(ref event) => { 696 | assert_eq!(event.connection_id(), id); 697 | assert!(event.age() <= release); 698 | } 699 | _ => unreachable!(), 700 | } 701 | 702 | let id2 = match events[5] { 703 | Event::Acquire(ref event) => event.connection_id(), 704 | _ => unreachable!(), 705 | }; 706 | assert!(id < id2); 707 | 708 | match events[6] { 709 | Event::Checkout(ref event) => assert_eq!(event.connection_id(), id2), 710 | _ => unreachable!(), 711 | } 712 | } 713 | 714 | #[test] 715 | fn extensions() { 716 | let pool = Pool::builder().max_size(2).build(OkManager).unwrap(); 717 | 718 | let mut conn1 = pool.get().unwrap(); 719 | let mut conn2 = pool.get().unwrap(); 720 | 721 | PooledConnection::extensions_mut(&mut conn1).insert(1); 722 | PooledConnection::extensions_mut(&mut conn2).insert(2); 723 | 724 | drop(conn1); 725 | 726 | let conn = pool.get().unwrap(); 727 | assert_eq!(PooledConnection::extensions(&conn).get::(), Some(&1)); 728 | } 729 | --------------------------------------------------------------------------------