deadpool-0.12.2/.cargo_vcs_info.json0000644000000001360000000000100127050ustar { "git": { "sha1": "69f10e252ff64b0ee626d9950c1347fde43a5264" }, "path_in_vcs": "" }deadpool-0.12.2/.devcontainer/Dockerfile000064400000000000000000000013731046102023000162320ustar 00000000000000FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 ARG RUST_VERSION=1.84 # Include lld linker to improve build times either by using environment variable # RUSTFLAGS="-C link-arg=-fuse-ld=lld" or with Cargo's configuration file (i.e see .cargo/config.toml). RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install clang lld libsqlite3-dev libmariadb-dev-compat libmariadb-dev libpq-dev pkg-config gh git-delta \ && apt-get autoremove -y && apt-get clean -y USER vscode RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain ${RUST_VERSION} --no-modify-path -y RUN /home/vscode/.cargo/bin/cargo install cargo-release RUN /home/vscode/.cargo/bin/cargo install --locked cargo-outdated deadpool-0.12.2/.devcontainer/devcontainer.json000064400000000000000000000030411046102023000176060ustar 00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/rust-postgres { "name": "Deadpool", "dockerComposeFile": "docker-compose.yml", "service": "app", "workspaceFolder": "/workspace", // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. "vscode": { // Set *default* container specific settings.json values on container create. "settings": { "lldb.executable": "/usr/bin/lldb", // VS Code don't watch files under ./target "files.watcherExclude": { "**/target/**": true }, "rust-analyzer.checkOnSave.command": "clippy" }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ "vadimcn.vscode-lldb", "mutantdino.resourcemonitor", "rust-lang.rust-analyzer", "tamasfe.even-better-toml", "serayuzgur.crates", "ms-azuretools.vscode-docker" ] } }, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [5432], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "rustc --version", // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", "remoteEnv": { "PATH": "${containerEnv:PATH}:/home/vscode/.cargo/bin" } }deadpool-0.12.2/.devcontainer/docker-compose.yml000064400000000000000000000034571046102023000177020ustar 00000000000000version: "3.8" volumes: postgres-data: redis-data: redis-cluster-data: rabbitmq-data: services: app: build: context: . dockerfile: Dockerfile args: # Use the VARIANT arg to pick a Debian OS version: buster, bullseye, bookworm # Use bullseye when on local on arm64/Apple Silicon. VARIANT: bookworm env_file: # Ensure that the variables in .env match the same variables in devcontainer.json - .env # Security Opt and cap_add for C++ based debuggers to work. # See `runArgs`: https://github.com/Microsoft/vscode-docs/blob/main/docs/remote/devcontainerjson-reference.md # security_opt: # - seccomp:unconfined # cap_add: # - SYS_PTRACE volumes: - ..:/workspace:cached # Overrides default command so things don't shut down after the process ends. command: sleep infinity # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. #network_mode: service:postgres # Uncomment the next line to use a non-root user for all processes. # user: vscode # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. # (Adding the "ports" property to this file will not forward from a Codespace.) postgres: image: postgres:16-alpine restart: unless-stopped volumes: - postgres-data:/var/lib/postgresql/data env_file: - postgres.env redis: image: redis:7.2-alpine restart: unless-stopped volumes: - redis-data:/data redis-cluster: image: grokzen/redis-cluster:7.0.10 restart: unless-stopped volumes: - redis-cluster-data:/redis-data rabbitmq: image: rabbitmq:3.12-alpine env_file: - rabbitmq.env volumes: - rabbitmq-data:/var/lib/rabbitmq deadpool-0.12.2/.devcontainer/postgres.env000064400000000000000000000001071046102023000166120ustar 00000000000000POSTGRES_PASSWORD=deadpool POSTGRES_USER=deadpool POSTGRES_DB=deadpool deadpool-0.12.2/.devcontainer/rabbitmq.env000064400000000000000000000001361046102023000165470ustar 00000000000000RABBITMQ_DEFAULT_USER=deadpool RABBITMQ_DEFAULT_PASS=deadpool RABBITMQ_DEFAULT_VHOST=deadpool deadpool-0.12.2/.github/workflows/ci.yml000064400000000000000000000157341046102023000162220ustar 00000000000000name: CI on: push: branches: [ "master" ] tags: [ "deadpool-*" ] pull_request: branches: [ "master" ] env: RUST_BACKTRACE: 1 jobs: ########################## # Linting and formatting # ########################## clippy: strategy: fail-fast: false matrix: crate: - deadpool-runtime - deadpool-sync - deadpool - deadpool-diesel - deadpool-lapin - deadpool-memcached - deadpool-postgres - deadpool-r2d2 - deadpool-redis - deadpool-sqlite # Examples - example-postgres-actix-web - example-postgres-benchmark - example-postgres-hyper - example-redis-actix-web runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable components: clippy - run: cargo clippy -p ${{ matrix.crate }} --no-deps --all-features -- -D warnings rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable components: rustfmt - run: cargo fmt --all --check ########### # Testing # ########### check-deadpool: name: Check deadpool strategy: fail-fast: false matrix: feature1: - managed - unmanaged feature2: - rt_tokio_1 - rt_async-std_1 - serde runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable - run: cargo check -p deadpool --no-default-features --features ${{ matrix.feature1 }},${{ matrix.feature2 }} check-integration: name: Check integration strategy: fail-fast: false matrix: crate: - diesel - lapin - postgres - redis - sqlite feature: - rt_tokio_1 - rt_async-std_1 - serde include: # additional inclusions for matrix - crate: diesel feature: mysql - crate: diesel feature: postgres - crate: diesel feature: sqlite runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable # We don't use `--no-default-features` here as integration crates don't # work with it at all. - run: cargo check -p deadpool-${{ matrix.crate }} --features ${{ matrix.feature }} check-integration-wasm: name: Check integration (WebAssembly) strategy: fail-fast: false matrix: crate: - postgres feature: - --features rt_tokio_1 - --features serde --features rt_tokio_1 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable target: wasm32-unknown-unknown - run: cargo check -p deadpool-${{ matrix.crate }} --no-default-features ${{ matrix.feature }} --target wasm32-unknown-unknown msrv: name: MSRV strategy: fail-fast: false matrix: include: - { crate: deadpool-runtime, msrv: '1.75.0' } - { crate: deadpool-sync, msrv: '1.75.0' } - { crate: deadpool, msrv: '1.75.0' } # Disable Diesel MSRV check as it keeps failing for no obvious reason. #- { crate: deadpool-diesel, msrv: '1.78.0' } - { crate: deadpool-lapin, msrv: '1.75.0' } - { crate: deadpool-postgres, msrv: '1.75.0' } - { crate: deadpool-redis, msrv: '1.75.0' } - { crate: deadpool-sqlite, msrv: '1.77.0' } runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: ${{ matrix.msrv }} override: true - run: cargo +nightly update -Z minimal-versions - run: cargo check -p ${{ matrix.crate }} --all-features test: strategy: fail-fast: false matrix: crate: - deadpool-runtime - deadpool-sync - deadpool - deadpool-diesel - deadpool-lapin - deadpool-postgres - deadpool-redis - deadpool-sqlite runs-on: ubuntu-latest services: postgres: image: postgres:15.3-alpine ports: - 5432:5432 env: POSTGRES_USER: deadpool POSTGRES_PASSWORD: deadpool POSTGRES_DB: deadpool # Health checks to wait until Postgres has started. options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 redis-sentinel: image: 'bitnami/redis-sentinel:latest' env: ALLOW_EMPTY_PASSWORD: yes ports: - 26379:26379 redis: image: redis:7.0-alpine ports: - 6379:6379 redis-cluster: image: grokzen/redis-cluster:7.0.10 ports: - 7000-7005:7000-7005 rabbitmq: image: rabbitmq:3.11-alpine ports: - 5672:5672 env: RABBITMQ_DEFAULT_USER: deadpool RABBITMQ_DEFAULT_PASS: deadpool RABBITMQ_DEFAULT_VHOST: deadpool steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable - run: cargo test -p ${{ matrix.crate }} --all-features env: PG__HOST: 127.0.0.1 PG__PORT: 5432 PG__USER: deadpool PG__PASSWORD: deadpool PG__DBNAME: deadpool REDIS__URL: redis://127.0.0.1/ REDIS_SENTINEL__URLS: redis://127.0.0.1:26379 REDIS_SENTINEL__SERVER_TYPE: "master" REDIS_SENTINEL__MASTER_NAME: "mymaster" REDIS_CLUSTER__URLS: redis://127.0.0.1:7000,redis://127.0.0.1:7001 AMQP__URL: amqp://deadpool:deadpool@127.0.0.1/deadpool ############ # Building # ############ rustdoc: name: Docs strategy: matrix: crate: - deadpool-runtime - deadpool-sync - deadpool - deadpool-diesel - deadpool-lapin - deadpool-postgres - deadpool-redis - deadpool-sqlite runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable - run: cargo doc -p ${{ matrix.crate }} --no-deps --all-features deadpool-0.12.2/.gitignore000064400000000000000000000000611046102023000134620ustar 00000000000000.vscode *.swp /target **/*.rs.bk Cargo.lock .env deadpool-0.12.2/CHANGELOG.md000064400000000000000000000212241046102023000133070ustar 00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [0.12.2] - 2025-02-02 - Update `itertools` dependency to version `0.13.0` - Change predicate parameter of `Pool::retain` method to `FnMut` - Add `RetainResult` as return value of `Pool::retain` method - Fix panic in `Pool::resize` method caused by shrinking and growing the pool in quick succession. ## [0.12.1] - 2024-05-07 - Add WASM support ## [0.12.0] - 2024-05-04 - Add `Send` to `Manager::Type` and `Manager::Error` associated types - Add `Send` to `Manager::create` and `Manager::recycle` return types ## [0.11.2] - 2024-04-10 - Make `Timeouts::new` and `Timeouts::wait_millis` functions const fns ## [0.11.1] - 2024-04-06 - Remove unused `console` dependency ## [0.11.0] - 2024-04-01 - Remove `async_trait` dependency - Bump up MSRV to `1.75` ## [0.10.0] - 2023-09-25 - Remove unreachable enum variant `BuildError::Backend` - Split `Status.available` into `available` and `waiting`. - Add `QueueMode` configuration option for choosing between a `FIFO` (default) and `LIFO` queue. - Remove `HookError::Continue` and `HookError::Abort` variants replacing it with the contents of `HookErrorCause`. Returning a `HookError` from a `post_create` hook causes the `Pool::get` operation to fail while returning it from a `pre_recycle` or `post_recycle` hook the operation continues. - Add `metrics` argument to `Manager::recycle` method. - Remove deprecated `managed::sync` module. - Remove deprecated `managed::Pool::try_get` method. - Bump up MSRV to `1.63` to match the one of `tokio`. ## [0.9.5] - 2022-05-20 - Fix bug causing the pool to exceed its `max_size` in the case of a recycling error. - Fix panic caused by an integer overflow in the case of a failing `post_create` hook. ## [0.9.4] - 2022-04-27 - Fix `HookError` and `HookErrorCause` in re-exports ## [0.9.3] - 2022-04-12 - Add `Pool::retain` method - Fix `Pool::get_timeouts` method - Deprecate `managed::Pool::try_get` - Add `Pool::timeouts` method ## [0.9.2] - 2021-11-15 - `PoolConfig` now implements `Serialize` ## [0.9.1] - 2021-10-26 - Deprecate `managed::sync` module in favor of `deadpool-sync` crate - Extract `runtime` module as separate `deadpool-runtime` crate ## [0.9.0] - 2021-10-18 - __Breaking:__ Replace `config` feature with `serde` (opted out by default) - Fix `std::error::Error::source` implementations for library errors - Add `Runtime::spawn_blocking` method - Add `Runtime::spawn_blocking_background` method - Remove `Runtime::None` in favor of `Option` - Remove `Pool::new` method - Add `Pool::builder` method and `PoolBuilder` struct - Add `Object::metrics` method and `Metrics` struct - Update `tokio` dependency to version `1.5.0` - Add `post_create`, `pre_recycle` and `post_recycle` hooks - Add `Pool::resize` method - Add `managed_reexports` macro ## [0.8.2] - 2021-07-16 - Add `deadpool-diesel` to README - Add `Sync + Send` as supertrait to `Manager` - Fix usage of `PhantomData` in `Pool` struct: `Pool is now `Sync` regardless of the wrapper. ## [0.8.1] - 2021-07-04 - Add `Object::pool` method ## [0.8.0] - 2021-05-21 - Add support for closing pools - Replace `crossbeam-queue` by `Mutex>` - Fix invalid `size` and `available` counts when recycling fails - Update `config` dependency to version `0.11` - Remove deprecated `from_env` methods - Add support for wrappers returned by the pool - Use associated types for traits ## [0.7.0] - 2020-12-26 - Update `tokio` dependency to version `1` ## [0.6.0] - 2020-11-04 - Update `tokio` dependency to version `0.3` - Update `crossbeam-queue` dependency to version `0.3` - Remove deprecated `deadpool::*` types - Add `deadpool-memcached` to README ## [0.5.2] - 2020-07-14 - Deprecate `managed::Config::from_env` - Deprecate `unmanaged::Config::from_env` ## [0.5.1] - 2020-01-18 - Add `managed::Object::take` method ## [0.5.0] - 2020-01-16 - Move current pool implementation into `managed` module - Add unmanaged version of the `Pool` which does not use a `Manager` to create and recycle objects. - Add feature flags `"managed"` and `"unmanaged"` to enable only parts of this crate. - Add `max_size` to pool `Status` - Add support for `config` crate ## [0.4.3] - 2019-12-23 - Add `std::error::Error` implementation for `PoolError` and `RecycleError`. This makes it more convenient to use the `?` operator. ## [0.4.2] - 2019-12-23 - Replace `tokio::sync::mpsc::channel` by `crossbeam_queue::ArrayQueue` which gets rid of the mutex when fetching an object from the pool. ## [0.4.1] - 2019-12-19 - Make `Pool::timeout_get` public ## [0.4.0] - 2019-12-19 - Add support for timeouts - Make fields of pool status public - Fix possible deadlock and make implementation a lot simpler by using the new tokio `Semaphore` and `Receiver::try_recv`. - Add `Pool::try_get` and `Pool::timeout_get` functions ## [0.3.0] - 2019-12-13 - Add `deadpool-lapin` to README - Add `deadpool-redis` to README - Fix possible stale state and deadlock if a future calling `Pool::get` is aborted. This is related to - Make recycling more robust by changing the `Manager::recycle` to a non consuming API. ## [0.2.3] - 2019-12-02 - Add documentation for `docs.rs` - Remove `PoolInner` and `PoolSize` struct from public interface - Improve example in `README.md` and crate root ## [0.2.2] - 2019-12-02 - Update to `tokio 0.2` ## 0.2.1 - Version skipped; only `tokio-postgres` was updated. ## [0.2.0] - 2019-11-14 - Split `deadpool` and `deadpool-postgres` in separate crates instead of one with feature flags. ## [0.1.0] - 2019-11-14 - First release [Unreleased]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.12.2...HEAD [0.12.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.12.1...deadpool-v0.12.2 [0.12.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.12.0...deadpool-v0.12.1 [0.12.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.11.2...deadpool-v0.12.0 [0.11.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.11.1...deadpool-v0.11.2 [0.11.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.11.0...deadpool-v0.11.1 [0.11.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.10.0...deadpool-v0.11.0 [0.10.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.5...deadpool-v0.10.0 [0.9.5]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.4...deadpool-v0.9.5 [0.9.4]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.3...deadpool-v0.9.4 [0.9.3]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.2...deadpool-v0.9.3 [0.9.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.1...deadpool-v0.9.2 [0.9.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.9.0...deadpool-v0.9.1 [0.9.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.8.2...deadpool-v0.9.0 [0.8.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.8.1...deadpool-v0.8.2 [0.8.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.8.0...deadpool-v0.8.1 [0.8.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.7.0...deadpool-v0.8.0 [0.7.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.6.0...deadpool-v0.7.0 [0.6.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.5.2...deadpool-v0.6.0 [0.5.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.5.1...deadpool-v0.5.2 [0.5.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.5.0...deadpool-v0.5.1 [0.5.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.4.4...deadpool-v0.5.0 [0.4.4]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.4.3...deadpool-v0.4.4 [0.4.3]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.4.2...deadpool-v0.4.3 [0.4.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.4.1...deadpool-v0.4.2 [0.4.1]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.4.0...deadpool-v0.4.1 [0.4.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.3.0...deadpool-v0.4.0 [0.3.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.2.3...deadpool-v0.3.0 [0.2.3]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.2.2...deadpool-v0.2.3 [0.2.2]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.2.1...deadpool-v0.2.2 [0.2.0]: https://github.com/bikeshedder/deadpool/compare/deadpool-v0.1.0...deadpool-v0.2.0 [0.1.0]: https://github.com/bikeshedder/deadpool/releases/tag/deadpool-v0.1.0 deadpool-0.12.2/Cargo.toml0000644000000052010000000000100107010ustar # 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 = "deadpool" version = "0.12.2" authors = ["Michael P. Jung "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Dead simple async pool" readme = "README.md" keywords = [ "async", "database", "pool", ] license = "MIT OR Apache-2.0" repository = "https://github.com/bikeshedder/deadpool" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", ] [lib] name = "deadpool" path = "src/lib.rs" [[test]] name = "managed" path = "tests/managed.rs" [[test]] name = "managed_cancellation" path = "tests/managed_cancellation.rs" [[test]] name = "managed_config" path = "tests/managed_config.rs" [[test]] name = "managed_deadlock" path = "tests/managed_deadlock.rs" [[test]] name = "managed_hooks" path = "tests/managed_hooks.rs" [[test]] name = "managed_resize" path = "tests/managed_resize.rs" [[test]] name = "managed_timeout" path = "tests/managed_timeout.rs" [[test]] name = "managed_unreliable_manager" path = "tests/managed_unreliable_manager.rs" [[test]] name = "unmanaged" path = "tests/unmanaged.rs" [[test]] name = "unmanaged_timeout" path = "tests/unmanaged_timeout.rs" [[bench]] name = "managed" path = "benches/managed.rs" harness = false [[bench]] name = "unmanaged" path = "benches/unmanaged.rs" harness = false [dependencies.deadpool-runtime] version = "0.1" [dependencies.num_cpus] version = "1.11.1" [dependencies.serde] version = "1.0.103" features = ["derive"] optional = true [dependencies.tokio] version = "1.0" features = ["sync"] [dev-dependencies.async-std] version = "1.0" features = ["attributes"] [dev-dependencies.config] version = "0.14" features = ["json"] [dev-dependencies.criterion] version = "0.5" features = [ "html_reports", "async_tokio", ] [dev-dependencies.itertools] version = "0.13" [dev-dependencies.tokio] version = "1.5.0" features = [ "macros", "rt", "rt-multi-thread", "time", ] [features] default = [ "managed", "unmanaged", ] managed = [] rt_async-std_1 = ["deadpool-runtime/async-std_1"] rt_tokio_1 = ["deadpool-runtime/tokio_1"] unmanaged = [] deadpool-0.12.2/Cargo.toml.orig000064400000000000000000000031261046102023000143660ustar 00000000000000[package] name = "deadpool" version = "0.12.2" edition = "2021" rust-version = "1.75" authors = ["Michael P. Jung "] description = "Dead simple async pool" keywords = ["async", "database", "pool"] license = "MIT OR Apache-2.0" repository = "https://github.com/bikeshedder/deadpool" readme = "README.md" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] default = ["managed", "unmanaged"] managed = [] unmanaged = [] rt_tokio_1 = ["deadpool-runtime/tokio_1"] rt_async-std_1 = ["deadpool-runtime/async-std_1"] [dependencies] num_cpus = "1.11.1" # `serde` feature serde = { version = "1.0.103", features = ["derive"], optional = true } # `rt_async-std_1` feature deadpool-runtime = { version = "0.1", path = "./runtime" } # The dependency of tokio::sync is non-optional. Deadpool depends on # `tokio::sync::Semaphore`. No other features of `tokio` are enabled or used # unless the `rt_tokio_1` feature is enabled. tokio = { version = "1.0", features = ["sync"] } [dev-dependencies] async-std = { version = "1.0", features = ["attributes"] } config = { version = "0.14", features = ["json"] } criterion = { version = "0.5", features = ["html_reports", "async_tokio"] } itertools = "0.13" tokio = { version = "1.5.0", features = [ "macros", "rt", "rt-multi-thread", "time", ] } [[bench]] name = "managed" harness = false [[bench]] name = "unmanaged" harness = false [workspace] members = [ "diesel", "lapin", "memcached", "postgres", "r2d2", "redis", "runtime", "sqlite", "sync", "examples/*", ] deadpool-0.12.2/LICENSE-APACHE000064400000000000000000000261221046102023000134240ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2019 Michael P. Jung Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. deadpool-0.12.2/LICENSE-MIT000064400000000000000000000020731046102023000131330ustar 00000000000000The MIT License (MIT) Copyright (c) 2019 Michael P. Jung 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. deadpool-0.12.2/README.md000064400000000000000000000213061046102023000127560ustar 00000000000000# Deadpool [![Latest Version](https://img.shields.io/crates/v/deadpool.svg)](https://crates.io/crates/deadpool) [![Build Status](https://img.shields.io/github/actions/workflow/status/bikeshedder/deadpool/ci.yml?branch=master)](https://github.com/bikeshedder/deadpool/actions?query=workflow%3ARust) ![Unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg "Unsafe forbidden") [![Rust 1.75+](https://img.shields.io/badge/rustc-1.75+-lightgray.svg "Rust 1.75+")](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) Deadpool is a dead simple async pool for connections and objects of any type. This crate provides two implementations: - Managed pool (`deadpool::managed::Pool`) - Creates and recycles objects as needed - Useful for [database connection pools](#database-connection-pools) - Enabled via the `managed` feature in your `Cargo.toml` - Unmanaged pool (`deadpool::unmanaged::Pool`) - All objects either need to be created by the user and added to the pool manually. It is also possible to create a pool from an existing collection of objects. - Enabled via the `unmanaged` feature in your `Cargo.toml` ## Features | Feature | Description | Extra dependencies | Default | | ------- | ----------- | ------------------ | ------- | | `managed` | Enable managed pool implementation | - | yes | | `unmanaged` | Enable unmanaged pool implementation | - | yes | | `rt_tokio_1` | Enable support for [tokio](https://crates.io/crates/tokio) crate | `tokio/time` | no | | `rt_async-std_1` | Enable support for [async-std](https://crates.io/crates/async-std) crate | `async-std` | no | | `serde` | Enable support for deserializing pool config | `serde/derive` | no | The runtime features (`rt_*`) are only needed if you need support for timeouts. If you try to use timeouts without specifying a runtime at pool creation the pool get methods will return an `PoolError::NoRuntimeSpecified` error. ## Managed pool (aka. connection pool) This is the obvious choice for connection pools of any kind. Deadpool already comes with a couple of [database connection pools](#database-connection-pools) which work out of the box. ### Example ```rust use deadpool::managed; #[derive(Debug)] enum Error { Fail } struct Computer {} impl Computer { async fn get_answer(&self) -> i32 { 42 } } struct Manager {} impl managed::Manager for Manager { type Type = Computer; type Error = Error; async fn create(&self) -> Result { Ok(Computer {}) } async fn recycle(&self, _: &mut Computer, _: &managed::Metrics) -> managed::RecycleResult { Ok(()) } } type Pool = managed::Pool; #[tokio::main] async fn main() { let mgr = Manager {}; let pool = Pool::builder(mgr).build().unwrap(); let mut conn = pool.get().await.unwrap(); let answer = conn.get_answer().await; assert_eq!(answer, 42); } ``` ### Database connection pools Deadpool supports various database backends by implementing the `deadpool::managed::Manager` trait. The following backends are currently supported: Backend | Crate | Latest Version | ------- | ----- | -------------- | [bolt-client](https://crates.io/crates/bolt-client) | [deadpool-bolt](https://crates.io/crates/deadpool-bolt) | [![Latest Version](https://img.shields.io/crates/v/deadpool-bolt.svg)](https://crates.io/crates/deadpool-bolt) | [tokio-postgres](https://crates.io/crates/tokio-postgres) | [deadpool-postgres](https://crates.io/crates/deadpool-postgres) | [![Latest Version](https://img.shields.io/crates/v/deadpool-postgres.svg)](https://crates.io/crates/deadpool-postgres) | [lapin](https://crates.io/crates/lapin) (AMQP) | [deadpool-lapin](https://crates.io/crates/deadpool-lapin) | [![Latest Version](https://img.shields.io/crates/v/deadpool-lapin.svg)](https://crates.io/crates/deadpool-lapin) | [redis](https://crates.io/crates/redis) | [deadpool-redis](https://crates.io/crates/deadpool-redis) | [![Latest Version](https://img.shields.io/crates/v/deadpool-redis.svg)](https://crates.io/crates/deadpool-redis) | [async-memcached](https://crates.io/crates/async-memcached) | [deadpool-memcached](https://crates.io/crates/deadpool-memcached) | [![Latest Version](https://img.shields.io/crates/v/deadpool-memcached.svg)](https://crates.io/crates/deadpool-memcached) | [rusqlite](https://crates.io/crates/rusqlite) | [deadpool-sqlite](https://crates.io/crates/deadpool-sqlite) | [![Latest Version](https://img.shields.io/crates/v/deadpool-sqlite.svg)](https://crates.io/crates/deadpool-sqlite) | [diesel](https://crates.io/crates/diesel) | [deadpool-diesel](https://crates.io/crates/deadpool-diesel) | [![Latest Version](https://img.shields.io/crates/v/deadpool-diesel.svg)](https://crates.io/crates/deadpool-diesel) | [tiberius](https://crates.io/crates/tiberius) | [deadpool-tiberius](https://crates.io/crates/deadpool-tiberius) | [![Latest Version](https://img.shields.io/crates/v/deadpool-tiberius.svg)](https://crates.io/crates/deadpool-tiberius) | [r2d2](https://crates.io/crates/r2d2) | [deadpool-r2d2](https://crates.io/crates/deadpool-r2d2) | [![Latest Version](https://img.shields.io/crates/v/deadpool-r2d2.svg)](https://crates.io/crates/deadpool-r2d2) | [rbatis](https://crates.io/crates/rbatis) | [rbatis](https://crates.io/crates/rbatis) | [![Latest Version](https://img.shields.io/crates/v/rbatis.svg)](https://crates.io/crates/rbatis) | ### Reasons for yet another connection pool Deadpool is by no means the only pool implementation available. It does things a little different and that is the main reason for it to exist: - **Deadpool is compatible with any executor.** Objects are returned to the pool using the `Drop` trait. The health of those objects is checked upon next retrieval and not when they are returned. Deadpool never performs any actions in the background. This is the reason why deadpool does not need to spawn futures and does not rely on a background thread or task of any type. - **Identical startup and runtime behaviour**. When writing long running application there usually should be no difference between startup and runtime if a database connection is temporarily not available. Nobody would expect an application to crash if the database becomes unavailable at runtime. So it should not crash on startup either. Creating the pool never fails and errors are only ever returned when calling `Pool::get()`. If you really want your application to crash on startup if objects can not be created on startup simply call `pool.get().await.expect("DB connection failed")` right after creating the pool. - **Deadpool is fast.** Whenever working with locking primitives they are held for the shortest duration possible. When returning an object to the pool a single mutex is locked and when retrieving objects from the pool a Semaphore is used to make this Mutex as little contested as possible. - **Deadpool is simple.** Dead simple. There is very little API surface. The actual code is barely 100 lines of code and lives in the two functions `Pool::get` and `Object::drop`. - **Deadpool is extensible.** By using `post_create`, `pre_recycle` and `post_recycle` hooks you can customize object creation and recycling to fit your needs. - **Deadpool provides insights.** All objects track `Metrics` and the pool provides a `status` method that can be used to find out details about the inner workings. - **Deadpool is resizable.** You can grow and shrink the pool at runtime without requiring an application restart. ## Unmanaged pool An unmanaged pool is useful when you can't write a manager for the objects you want to pool or simply don't want to. This pool implementation is slightly faster than the managed pool because it does not use a `Manager` trait to `create` and `recycle` objects but leaves it up to the user. ### Unmanaged pool example ```rust use deadpool::unmanaged::Pool; struct Computer {} impl Computer { async fn get_answer(&self) -> i32 { 42 } } #[tokio::main] async fn main() { let pool = Pool::from(vec![ Computer {}, Computer {}, ]); let s = pool.get().await.unwrap(); assert_eq!(s.get_answer().await, 42); } ``` ## FAQ ### Why does deadpool depend on `tokio`? I thought it was runtime agnostic... Deadpool depends on `tokio::sync::Semaphore`. This does **not** mean that the tokio runtime or anything else of tokio is being used or will be part of your build. You can easily check this by running the following command in your own code base: ```shell cargo tree --format "{p} {f}" ``` ## License Licensed under either of - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or - MIT license ([LICENSE-MIT](LICENSE-MIT) or at your option. deadpool-0.12.2/benches/managed.rs000064400000000000000000000050641046102023000150530ustar 00000000000000use std::{convert::TryInto, fmt::Display}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use deadpool::managed::Metrics; use tokio::task::JoinHandle; //const ITERATIONS: usize = 1_048_576; const ITERATIONS: usize = 1 << 15; #[derive(Copy, Clone, Debug)] struct Config { pool_size: usize, workers: usize, } impl Display for Config { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "w{}s{}", self.workers, self.pool_size) } } impl Config { fn operations_per_worker(&self) -> usize { ITERATIONS / self.workers } } #[rustfmt::skip] const CONFIGS: &[Config] = &[ // 8 workers Config { workers: 8, pool_size: 2 }, Config { workers: 8, pool_size: 4 }, Config { workers: 8, pool_size: 8 }, // 16 workers Config { workers: 16, pool_size: 4 }, Config { workers: 16, pool_size: 8 }, Config { workers: 16, pool_size: 16 }, // 32 workers Config { workers: 32, pool_size: 8 }, Config { workers: 32, pool_size: 16 }, Config { workers: 32, pool_size: 32 }, ]; struct Manager {} impl deadpool::managed::Manager for Manager { type Type = (); type Error = (); async fn create(&self) -> Result { Ok(()) } async fn recycle( &self, _: &mut Self::Type, _: &Metrics, ) -> deadpool::managed::RecycleResult { Ok(()) } } type Pool = deadpool::managed::Pool; async fn bench_get(cfg: Config) { let pool = Pool::builder(Manager {}) .max_size(cfg.pool_size) .build() .unwrap(); let join_handles: Vec> = (0..cfg.workers) .map(|_| { let pool = pool.clone(); tokio::spawn(async move { for _ in 0..cfg.operations_per_worker() { let _ = pool.get().await; } }) }) .collect(); for join_handle in join_handles { join_handle.await.unwrap(); } } fn criterion_benchmark(c: &mut Criterion) { let runtime = tokio::runtime::Runtime::new().unwrap(); let mut group = c.benchmark_group("managed"); group.throughput(criterion::Throughput::Elements( ITERATIONS.try_into().expect("Can't convert u64 to usize"), )); for &config in CONFIGS { group.bench_with_input(BenchmarkId::new("get", config), &config, |b, &cfg| { b.to_async(&runtime).iter(|| bench_get(cfg)) }); } } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); deadpool-0.12.2/benches/unmanaged.rs000064400000000000000000000007521046102023000154150ustar 00000000000000use criterion::{criterion_group, criterion_main, Criterion}; use deadpool::unmanaged::Pool; const ITERATIONS: usize = 1_000_000; #[tokio::main] async fn use_pool() { let pool = Pool::new(16); pool.add(()).await.unwrap(); for _ in 0..ITERATIONS { let _ = pool.get().await.unwrap(); } } fn criterion_benchmark(c: &mut Criterion) { c.bench_function("use_pool", |b| b.iter(use_pool)); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); deadpool-0.12.2/release.toml000064400000000000000000000010241046102023000140070ustar 00000000000000pre-release-replacements = [ { file = "CHANGELOG.md", search = "[Unreleased]", replace = "[{{version}}] {{date}}" }, { file = "CHANGELOG.md", search = "\\.\\.\\.HEAD", replace = "...{{tag_name}}", exactly = 1 }, { file = "CHANGELOG.md", search = "", replace = "\n\n## [Unreleased]", exactly = 1 }, { file = "CHANGELOG.md", search = "", replace = "\n[Unreleased]: https://github.com/bikeshedder/deadpool/compare/{{tag_name}}...HEAD", exactly = 1 }, ] deadpool-0.12.2/src/lib.rs000064400000000000000000000027531046102023000134070ustar 00000000000000#![doc = include_str!("../README.md")] #![cfg_attr(docsrs, feature(doc_cfg))] #![deny( nonstandard_style, rust_2018_idioms, rustdoc::broken_intra_doc_links, rustdoc::private_intra_doc_links )] #![forbid(non_ascii_idents, unsafe_code)] #![warn( deprecated_in_future, missing_copy_implementations, missing_debug_implementations, missing_docs, unreachable_pub, unused_import_braces, unused_labels, unused_lifetimes, unused_qualifications, unused_results )] #![allow(clippy::uninlined_format_args)] #[cfg(feature = "managed")] #[cfg_attr(docsrs, doc(cfg(feature = "managed")))] pub mod managed; #[cfg(feature = "unmanaged")] #[cfg_attr(docsrs, doc(cfg(feature = "unmanaged")))] pub mod unmanaged; pub use deadpool_runtime::{Runtime, SpawnBlockingError}; /// The current pool status. /// /// **The status returned by the pool is not guaranteed to be consistent!** /// /// While this features provides [eventual consistency][1] the numbers will be /// off when accessing the status of a pool under heavy load. These numbers /// are meant for an overall insight. /// /// [1]: (https://en.wikipedia.org/wiki/Eventual_consistency) #[derive(Clone, Copy, Debug)] pub struct Status { /// The maximum size of the pool. pub max_size: usize, /// The current size of the pool. pub size: usize, /// The number of available objects in the pool. pub available: usize, /// The number of futures waiting for an object. pub waiting: usize, } deadpool-0.12.2/src/managed/builder.rs000064400000000000000000000127071046102023000156630ustar 00000000000000use std::{fmt, marker::PhantomData, time::Duration}; use crate::Runtime; use super::{ hooks::{Hook, Hooks}, Manager, Object, Pool, PoolConfig, QueueMode, Timeouts, }; /// Possible errors returned when [`PoolBuilder::build()`] fails to build a /// [`Pool`]. #[derive(Copy, Clone, Debug)] pub enum BuildError { /// [`Runtime`] is required du to configured timeouts. NoRuntimeSpecified, } impl fmt::Display for BuildError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NoRuntimeSpecified => write!( f, "Error occurred while building the pool: Timeouts require a runtime", ), } } } impl std::error::Error for BuildError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::NoRuntimeSpecified => None, } } } /// Builder for [`Pool`]s. /// /// Instances of this are created by calling the [`Pool::builder()`] method. #[must_use = "builder does nothing itself, use `.build()` to build it"] pub struct PoolBuilder> where M: Manager, W: From>, { pub(crate) manager: M, pub(crate) config: PoolConfig, pub(crate) runtime: Option, pub(crate) hooks: Hooks, _wrapper: PhantomData W>, } // Implemented manually to avoid unnecessary trait bound on `W` type parameter. impl fmt::Debug for PoolBuilder where M: fmt::Debug + Manager, W: From>, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PoolBuilder") .field("manager", &self.manager) .field("config", &self.config) .field("runtime", &self.runtime) .field("hooks", &self.hooks) .field("_wrapper", &self._wrapper) .finish() } } impl PoolBuilder where M: Manager, W: From>, { pub(crate) fn new(manager: M) -> Self { Self { manager, config: PoolConfig::default(), runtime: None, hooks: Hooks::default(), _wrapper: PhantomData, } } /// Builds the [`Pool`]. /// /// # Errors /// /// See [`BuildError`] for details. pub fn build(self) -> Result, BuildError> { // Return an error if a timeout is configured without runtime. let t = &self.config.timeouts; if (t.wait.is_some() || t.create.is_some() || t.recycle.is_some()) && self.runtime.is_none() { return Err(BuildError::NoRuntimeSpecified); } Ok(Pool::from_builder(self)) } /// Sets a [`PoolConfig`] to build the [`Pool`] with. pub fn config(mut self, value: PoolConfig) -> Self { self.config = value; self } /// Sets the [`PoolConfig::max_size`]. pub fn max_size(mut self, value: usize) -> Self { self.config.max_size = value; self } /// Sets the [`PoolConfig::timeouts`]. pub fn timeouts(mut self, value: Timeouts) -> Self { self.config.timeouts = value; self } /// Sets the [`Timeouts::wait`] value of the [`PoolConfig::timeouts`]. pub fn wait_timeout(mut self, value: Option) -> Self { self.config.timeouts.wait = value; self } /// Sets the [`Timeouts::create`] value of the [`PoolConfig::timeouts`]. pub fn create_timeout(mut self, value: Option) -> Self { self.config.timeouts.create = value; self } /// Sets the [`Timeouts::recycle`] value of the [`PoolConfig::timeouts`]. pub fn recycle_timeout(mut self, value: Option) -> Self { self.config.timeouts.recycle = value; self } /// Sets the [`PoolConfig::queue_mode`]. pub fn queue_mode(mut self, value: QueueMode) -> Self { self.config.queue_mode = value; self } /// Attaches a `post_create` hook. /// /// The given `hook` will be called each time right after a new [`Object`] /// has been created. pub fn post_create(mut self, hook: impl Into>) -> Self { self.hooks.post_create.push(hook.into()); self } /// Attaches a `pre_recycle` hook. /// /// The given `hook` will be called each time right before an [`Object`] will /// be recycled. pub fn pre_recycle(mut self, hook: impl Into>) -> Self { self.hooks.pre_recycle.push(hook.into()); self } /// Attaches a `post_recycle` hook. /// /// The given `hook` will be called each time right after an [`Object`] has /// been recycled. pub fn post_recycle(mut self, hook: impl Into>) -> Self { self.hooks.post_recycle.push(hook.into()); self } /// Sets the [`Runtime`]. /// /// # Important /// /// The [`Runtime`] is optional. Most [`Pool`]s don't need a /// [`Runtime`]. If want to utilize timeouts, however a [`Runtime`] must be /// specified as you will otherwise get a [`PoolError::NoRuntimeSpecified`] /// when trying to use [`Pool::timeout_get()`]. /// /// [`PoolBuilder::build()`] will fail with a /// [`BuildError::NoRuntimeSpecified`] if you try to build a /// [`Pool`] with timeouts and no [`Runtime`] specified. /// /// [`PoolError::NoRuntimeSpecified`]: super::PoolError::NoRuntimeSpecified pub fn runtime(mut self, value: Runtime) -> Self { self.runtime = Some(value); self } } deadpool-0.12.2/src/managed/config.rs000064400000000000000000000073541046102023000155040ustar 00000000000000use std::{fmt, time::Duration}; use super::BuildError; /// [`Pool`] configuration. /// /// [`Pool`]: super::Pool #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct PoolConfig { /// Maximum size of the [`Pool`]. /// /// Default: `cpu_count * 4` /// /// [`Pool`]: super::Pool pub max_size: usize, /// Timeouts of the [`Pool`]. /// /// Default: No timeouts /// /// [`Pool`]: super::Pool #[cfg_attr(feature = "serde", serde(default))] pub timeouts: Timeouts, /// Queue mode of the [`Pool`]. /// /// Determines the order of objects being queued and dequeued. /// /// Default: `Fifo` /// /// [`Pool`]: super::Pool #[cfg_attr(feature = "serde", serde(default))] pub queue_mode: QueueMode, } impl PoolConfig { /// Creates a new [`PoolConfig`] without any timeouts and with the provided /// `max_size`. #[must_use] pub fn new(max_size: usize) -> Self { Self { max_size, timeouts: Timeouts::default(), queue_mode: QueueMode::default(), } } } impl Default for PoolConfig { /// Creates a new [`PoolConfig`] with the `max_size` being set to /// `cpu_count * 4` ignoring any logical CPUs (Hyper-Threading). fn default() -> Self { Self::new(num_cpus::get_physical() * 4) } } /// Timeouts when getting [`Object`]s from a [`Pool`]. /// /// [`Object`]: super::Object /// [`Pool`]: super::Pool #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Timeouts { /// Timeout when waiting for a slot to become available. pub wait: Option, /// Timeout when creating a new object. pub create: Option, /// Timeout when recycling an object. pub recycle: Option, } impl Timeouts { /// Create an empty [`Timeouts`] config (no timeouts set). #[must_use] pub const fn new() -> Self { Self { create: None, wait: None, recycle: None, } } /// Creates a new [`Timeouts`] config with only the `wait` timeout being /// set. #[must_use] pub const fn wait_millis(wait: u64) -> Self { Self { create: None, wait: Some(Duration::from_millis(wait)), recycle: None, } } } // Implemented manually to provide a custom documentation. impl Default for Timeouts { /// Creates an empty [`Timeouts`] config (no timeouts set). fn default() -> Self { Self::new() } } /// Mode for dequeuing [`Object`]s from a [`Pool`]. /// /// [`Object`]: super::Object /// [`Pool`]: super::Pool #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum QueueMode { /// Dequeue the object that was least recently added (first in first out). Fifo, /// Dequeue the object that was most recently added (last in first out). Lifo, } impl Default for QueueMode { fn default() -> Self { Self::Fifo } } /// This error is used when building pools via the config `create_pool` /// methods. #[derive(Debug)] pub enum CreatePoolError { /// This variant is used for configuration errors Config(C), /// This variant is used for errors while building the pool Build(BuildError), } impl fmt::Display for CreatePoolError where C: fmt::Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Config(e) => write!(f, "Config: {}", e), Self::Build(e) => write!(f, "Build: {}", e), } } } impl std::error::Error for CreatePoolError where C: std::error::Error {} deadpool-0.12.2/src/managed/dropguard.rs000064400000000000000000000022701046102023000162160ustar 00000000000000/// This structure calls a function/closure when it is dropped. /// The [`DropGuard::disarm`] method stops this from happening. pub(crate) struct DropGuard(pub(crate) F); impl DropGuard { pub(crate) fn disarm(self) { std::mem::forget(self) } } impl Drop for DropGuard where F: Fn(), { fn drop(&mut self) { (self.0)() } } #[test] fn test_dropguard_drop() { use std::sync::atomic::{AtomicUsize, Ordering}; let count = AtomicUsize::new(0); assert_eq!(count.load(Ordering::Relaxed), 0); { let _ = count.fetch_add(1, Ordering::Relaxed); let _ = DropGuard(|| { let _ = count.fetch_sub(1, Ordering::Relaxed); }); } assert_eq!(count.load(Ordering::Relaxed), 0); } #[test] fn test_dropguard_disarm() { use std::sync::atomic::{AtomicUsize, Ordering}; let count = AtomicUsize::new(0); assert_eq!(count.load(Ordering::Relaxed), 0); { let _ = count.fetch_add(1, Ordering::Relaxed); let guard = DropGuard(|| { let _ = count.fetch_sub(1, Ordering::Relaxed); }); guard.disarm(); } assert_eq!(count.load(Ordering::Relaxed), 1); } deadpool-0.12.2/src/managed/errors.rs000064400000000000000000000067711046102023000155550ustar 00000000000000use std::{borrow::Cow, fmt}; use super::hooks::HookError; /// Possible errors returned by the [`Manager::recycle()`] method. /// /// [`Manager::recycle()`]: super::Manager::recycle #[derive(Debug)] pub enum RecycleError { /// Recycling failed for some other reason. Message(Cow<'static, str>), /// Error caused by the backend. Backend(E), } impl RecycleError { /// Convenience constructor function for the `HookError::Message` /// variant. pub fn message(msg: impl Into>) -> Self { Self::Message(msg.into()) } } impl From for RecycleError { fn from(e: E) -> Self { Self::Backend(e) } } impl fmt::Display for RecycleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Message(msg) => write!(f, "Error occurred while recycling an object: {}", msg), Self::Backend(e) => write!(f, "Error occurred while recycling an object: {}", e), } } } impl std::error::Error for RecycleError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Message(_) => None, Self::Backend(e) => Some(e), } } } /// Possible steps causing the timeout in an error returned by [`Pool::get()`] /// method. /// /// [`Pool::get()`]: super::Pool::get #[derive(Clone, Copy, Debug)] pub enum TimeoutType { /// Timeout happened while waiting for a slot to become available. Wait, /// Timeout happened while creating a new object. Create, /// Timeout happened while recycling an object. Recycle, } /// Possible errors returned by [`Pool::get()`] method. /// /// [`Pool::get()`]: super::Pool::get #[derive(Debug)] pub enum PoolError { /// Timeout happened. Timeout(TimeoutType), /// Backend reported an error. Backend(E), /// [`Pool`] has been closed. /// /// [`Pool`]: super::Pool Closed, /// No [`Runtime`] was specified. /// /// [`Runtime`]: crate::Runtime NoRuntimeSpecified, /// A `post_create` hook reported an error. PostCreateHook(HookError), } impl From for PoolError { fn from(e: E) -> Self { Self::Backend(e) } } impl fmt::Display for PoolError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Timeout(tt) => match tt { TimeoutType::Wait => write!( f, "Timeout occurred while waiting for a slot to become available" ), TimeoutType::Create => write!(f, "Timeout occurred while creating a new object"), TimeoutType::Recycle => write!(f, "Timeout occurred while recycling an object"), }, Self::Backend(e) => write!(f, "Error occurred while creating a new object: {}", e), Self::Closed => write!(f, "Pool has been closed"), Self::NoRuntimeSpecified => write!(f, "No runtime specified"), Self::PostCreateHook(e) => writeln!(f, "`post_create` hook failed: {}", e), } } } impl std::error::Error for PoolError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Timeout(_) | Self::Closed | Self::NoRuntimeSpecified => None, Self::Backend(e) => Some(e), Self::PostCreateHook(e) => Some(e), } } } deadpool-0.12.2/src/managed/hooks.rs000064400000000000000000000114511046102023000153530ustar 00000000000000//! Hooks allowing to run code when creating and/or recycling objects. use std::{borrow::Cow, fmt, future::Future, pin::Pin}; use super::{Manager, Metrics, ObjectInner}; /// The result returned by hooks pub type HookResult = Result<(), HookError>; /// The boxed future that should be returned by async hooks pub type HookFuture<'a, E> = Pin> + Send + 'a>>; /// Function signature for sync callbacks type SyncFn = dyn Fn(&mut ::Type, &Metrics) -> HookResult<::Error> + Sync + Send; /// Function siganture for async callbacks type AsyncFn = dyn for<'a> Fn(&'a mut ::Type, &'a Metrics) -> HookFuture<'a, ::Error> + Sync + Send; /// Wrapper for hook functions pub enum Hook { /// Use a plain function (non-async) as a hook Fn(Box>), /// Use an async function as a hook AsyncFn(Box>), } impl Hook { /// Create Hook from sync function pub fn sync_fn( f: impl Fn(&mut M::Type, &Metrics) -> HookResult + Sync + Send + 'static, ) -> Self { Self::Fn(Box::new(f)) } /// Create Hook from async function pub fn async_fn( f: impl for<'a> Fn(&'a mut M::Type, &'a Metrics) -> HookFuture<'a, M::Error> + Sync + Send + 'static, ) -> Self { Self::AsyncFn(Box::new(f)) } } impl fmt::Debug for Hook { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Fn(_) => f .debug_tuple("Fn") //.field(arg0) .finish(), Self::AsyncFn(_) => f .debug_tuple("AsyncFn") //.field(arg0) .finish(), } } } /// Error which is returned by `pre_create`, `pre_recycle` and /// `post_recycle` hooks. #[derive(Debug)] pub enum HookError { /// Hook failed for some other reason. Message(Cow<'static, str>), /// Error caused by the backend. Backend(E), } impl HookError { /// Convenience constructor function for the `HookError::Message` /// variant. pub fn message(msg: impl Into>) -> Self { Self::Message(msg.into()) } } impl fmt::Display for HookError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Message(msg) => write!(f, "{}", msg), Self::Backend(e) => write!(f, "{}", e), } } } impl std::error::Error for HookError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Message(_) => None, Self::Backend(e) => Some(e), } } } pub(crate) struct HookVec { vec: Vec>, } // Implemented manually to avoid unnecessary trait bound on `M` type parameter. impl fmt::Debug for HookVec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HookVec") //.field("fns", &self.fns) .finish_non_exhaustive() } } // Implemented manually to avoid unnecessary trait bound on `M` type parameter. impl Default for HookVec { fn default() -> Self { Self { vec: Vec::new() } } } impl HookVec { pub(crate) async fn apply( &self, inner: &mut ObjectInner, ) -> Result<(), HookError> { for hook in &self.vec { match hook { Hook::Fn(f) => f(&mut inner.obj, &inner.metrics)?, Hook::AsyncFn(f) => f(&mut inner.obj, &inner.metrics).await?, }; } Ok(()) } pub(crate) fn push(&mut self, hook: Hook) { self.vec.push(hook); } } /// Collection of all the hooks that can be configured for a [`Pool`]. /// /// [`Pool`]: super::Pool pub(crate) struct Hooks { pub(crate) post_create: HookVec, pub(crate) pre_recycle: HookVec, pub(crate) post_recycle: HookVec, } // Implemented manually to avoid unnecessary trait bound on `M` type parameter. impl fmt::Debug for Hooks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Hooks") .field("post_create", &self.post_create) .field("pre_recycle", &self.post_recycle) .field("post_recycle", &self.post_recycle) .finish() } } // Implemented manually to avoid unnecessary trait bound on `M` type parameter. impl Default for Hooks { fn default() -> Self { Self { pre_recycle: HookVec::default(), post_create: HookVec::default(), post_recycle: HookVec::default(), } } } deadpool-0.12.2/src/managed/metrics.rs000064400000000000000000000022221046102023000156720ustar 00000000000000#[cfg(not(target_arch = "wasm32"))] use std::time::{Duration, Instant}; /// Statistics regarding an object returned by the pool #[derive(Clone, Copy, Debug)] #[must_use] pub struct Metrics { #[cfg(not(target_arch = "wasm32"))] /// The instant when this object was created pub created: Instant, #[cfg(not(target_arch = "wasm32"))] /// The instant when this object was last used pub recycled: Option, /// The number of times the objects was recycled pub recycle_count: usize, } impl Metrics { #[cfg(not(target_arch = "wasm32"))] /// Access the age of this object pub fn age(&self) -> Duration { self.created.elapsed() } #[cfg(not(target_arch = "wasm32"))] /// Get the time elapsed when this object was last used pub fn last_used(&self) -> Duration { self.recycled.unwrap_or(self.created).elapsed() } } impl Default for Metrics { fn default() -> Self { Self { #[cfg(not(target_arch = "wasm32"))] created: Instant::now(), #[cfg(not(target_arch = "wasm32"))] recycled: None, recycle_count: 0, } } } deadpool-0.12.2/src/managed/mod.rs000064400000000000000000000475221046102023000150170ustar 00000000000000//! Managed version of the pool. //! //! "Managed" means that it requires a [`Manager`] which is responsible for //! creating and recycling objects as they are needed. //! //! # Example //! //! ```rust //! use deadpool::managed; //! //! #[derive(Debug)] //! enum Error { Fail } //! //! struct Computer {} //! //! impl Computer { //! async fn get_answer(&self) -> i32 { //! 42 //! } //! } //! //! struct Manager {} //! //! impl managed::Manager for Manager { //! type Type = Computer; //! type Error = Error; //! //! async fn create(&self) -> Result { //! Ok(Computer {}) //! } //! async fn recycle(&self, conn: &mut Computer, _: &managed::Metrics) -> managed::RecycleResult { //! Ok(()) //! } //! } //! //! type Pool = managed::Pool; //! //! #[tokio::main] //! async fn main() { //! let mgr = Manager {}; //! let pool = Pool::builder(mgr).max_size(16).build().unwrap(); //! let mut conn = pool.get().await.unwrap(); //! let answer = conn.get_answer().await; //! assert_eq!(answer, 42); //! } //! ``` //! //! For a more complete example please see //! [`deadpool-postgres`](https://crates.io/crates/deadpool-postgres) crate. mod builder; mod config; mod dropguard; mod errors; mod hooks; mod metrics; pub mod reexports; use std::{ collections::VecDeque, fmt, future::Future, marker::PhantomData, ops::{Deref, DerefMut}, sync::{ atomic::{AtomicUsize, Ordering}, Arc, Mutex, Weak, }, time::Duration, }; #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use deadpool_runtime::Runtime; use tokio::sync::{Semaphore, TryAcquireError}; pub use crate::Status; use self::dropguard::DropGuard; pub use self::{ builder::{BuildError, PoolBuilder}, config::{CreatePoolError, PoolConfig, QueueMode, Timeouts}, errors::{PoolError, RecycleError, TimeoutType}, hooks::{Hook, HookError, HookFuture, HookResult}, metrics::Metrics, }; /// Result type of the [`Manager::recycle()`] method. pub type RecycleResult = Result<(), RecycleError>; /// Manager responsible for creating new [`Object`]s or recycling existing ones. pub trait Manager: Sync + Send { /// Type of [`Object`]s that this [`Manager`] creates and recycles. type Type: Send; /// Error that this [`Manager`] can return when creating and/or recycling /// [`Object`]s. type Error: Send; /// Creates a new instance of [`Manager::Type`]. fn create(&self) -> impl Future> + Send; /// Tries to recycle an instance of [`Manager::Type`]. /// /// # Errors /// /// Returns [`Manager::Error`] if the instance couldn't be recycled. fn recycle( &self, obj: &mut Self::Type, metrics: &Metrics, ) -> impl Future> + Send; /// Detaches an instance of [`Manager::Type`] from this [`Manager`]. /// /// This method is called when using the [`Object::take()`] method for /// removing an [`Object`] from a [`Pool`]. If the [`Manager`] doesn't hold /// any references to the handed out [`Object`]s then the default /// implementation can be used which does nothing. fn detach(&self, _obj: &mut Self::Type) {} } /// Wrapper around the actual pooled object which implements [`Deref`], /// [`DerefMut`] and [`Drop`] traits. /// /// Use this object just as if it was of type `T` and upon leaving a scope the /// [`Drop::drop()`] will take care of returning it to the pool. #[must_use] pub struct Object { /// The actual object inner: Option>, /// Pool to return the pooled object to. pool: Weak>, } impl fmt::Debug for Object where M: fmt::Debug + Manager, M::Type: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Object") .field("inner", &self.inner) .finish() } } struct UnreadyObject<'a, M: Manager> { inner: Option>, pool: &'a PoolInner, } impl UnreadyObject<'_, M> { fn ready(mut self) -> ObjectInner { self.inner.take().unwrap() } fn inner(&mut self) -> &mut ObjectInner { self.inner.as_mut().unwrap() } } impl Drop for UnreadyObject<'_, M> { fn drop(&mut self) { if let Some(mut inner) = self.inner.take() { self.pool.slots.lock().unwrap().size -= 1; self.pool.manager.detach(&mut inner.obj); } } } #[derive(Debug)] pub(crate) struct ObjectInner { /// Actual pooled object. obj: M::Type, /// Object metrics. metrics: Metrics, } impl Object { /// Takes this [`Object`] from its [`Pool`] permanently. This reduces the /// size of the [`Pool`]. #[must_use] pub fn take(mut this: Self) -> M::Type { let mut inner = this.inner.take().unwrap().obj; if let Some(pool) = Object::pool(&this) { pool.inner.detach_object(&mut inner) } inner } /// Get object statistics pub fn metrics(this: &Self) -> &Metrics { &this.inner.as_ref().unwrap().metrics } /// Returns the [`Pool`] this [`Object`] belongs to. /// /// Since [`Object`]s only hold a [`Weak`] reference to the [`Pool`] they /// come from, this can fail and return [`None`] instead. pub fn pool(this: &Self) -> Option> { this.pool.upgrade().map(|inner| Pool { inner, _wrapper: PhantomData, }) } } impl Drop for Object { fn drop(&mut self) { if let Some(inner) = self.inner.take() { if let Some(pool) = self.pool.upgrade() { pool.return_object(inner) } } } } impl Deref for Object { type Target = M::Type; fn deref(&self) -> &M::Type { &self.inner.as_ref().unwrap().obj } } impl DerefMut for Object { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner.as_mut().unwrap().obj } } impl AsRef for Object { fn as_ref(&self) -> &M::Type { self } } impl AsMut for Object { fn as_mut(&mut self) -> &mut M::Type { self } } /// Generic object and connection pool. /// /// This struct can be cloned and transferred across thread boundaries and uses /// reference counting for its internal state. pub struct Pool> = Object> { inner: Arc>, _wrapper: PhantomData W>, } // Implemented manually to avoid unnecessary trait bound on `W` type parameter. impl fmt::Debug for Pool where M: fmt::Debug + Manager, M::Type: fmt::Debug, W: From>, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Pool") .field("inner", &self.inner) .field("wrapper", &self._wrapper) .finish() } } impl>> Clone for Pool { fn clone(&self) -> Self { Self { inner: self.inner.clone(), _wrapper: PhantomData, } } } impl>> Pool { /// Instantiates a builder for a new [`Pool`]. /// /// This is the only way to create a [`Pool`] instance. pub fn builder(manager: M) -> PoolBuilder { PoolBuilder::new(manager) } pub(crate) fn from_builder(builder: PoolBuilder) -> Self { Self { inner: Arc::new(PoolInner { manager: builder.manager, slots: Mutex::new(Slots { vec: VecDeque::with_capacity(builder.config.max_size), size: 0, max_size: builder.config.max_size, }), users: AtomicUsize::new(0), semaphore: Semaphore::new(builder.config.max_size), config: builder.config, hooks: builder.hooks, runtime: builder.runtime, }), _wrapper: PhantomData, } } /// Retrieves an [`Object`] from this [`Pool`] or waits for one to /// become available. /// /// # Errors /// /// See [`PoolError`] for details. pub async fn get(&self) -> Result> { self.timeout_get(&self.timeouts()).await } /// Retrieves an [`Object`] from this [`Pool`] using a different `timeout` /// than the configured one. /// /// # Errors /// /// See [`PoolError`] for details. pub async fn timeout_get(&self, timeouts: &Timeouts) -> Result> { let _ = self.inner.users.fetch_add(1, Ordering::Relaxed); let users_guard = DropGuard(|| { let _ = self.inner.users.fetch_sub(1, Ordering::Relaxed); }); let non_blocking = match timeouts.wait { Some(t) => t.as_nanos() == 0, None => false, }; let permit = if non_blocking { self.inner.semaphore.try_acquire().map_err(|e| match e { TryAcquireError::Closed => PoolError::Closed, TryAcquireError::NoPermits => PoolError::Timeout(TimeoutType::Wait), })? } else { apply_timeout( self.inner.runtime, TimeoutType::Wait, timeouts.wait, async { self.inner .semaphore .acquire() .await .map_err(|_| PoolError::Closed) }, ) .await? }; let inner_obj = loop { let inner_obj = match self.inner.config.queue_mode { QueueMode::Fifo => self.inner.slots.lock().unwrap().vec.pop_front(), QueueMode::Lifo => self.inner.slots.lock().unwrap().vec.pop_back(), }; let inner_obj = if let Some(inner_obj) = inner_obj { self.try_recycle(timeouts, inner_obj).await? } else { self.try_create(timeouts).await? }; if let Some(inner_obj) = inner_obj { break inner_obj; } }; users_guard.disarm(); permit.forget(); Ok(Object { inner: Some(inner_obj), pool: Arc::downgrade(&self.inner), } .into()) } #[inline] async fn try_recycle( &self, timeouts: &Timeouts, inner_obj: ObjectInner, ) -> Result>, PoolError> { let mut unready_obj = UnreadyObject { inner: Some(inner_obj), pool: &self.inner, }; let inner = unready_obj.inner(); // Apply pre_recycle hooks if let Err(_e) = self.inner.hooks.pre_recycle.apply(inner).await { // TODO log pre_recycle error return Ok(None); } if apply_timeout( self.inner.runtime, TimeoutType::Recycle, timeouts.recycle, self.inner.manager.recycle(&mut inner.obj, &inner.metrics), ) .await .is_err() { return Ok(None); } // Apply post_recycle hooks if let Err(_e) = self.inner.hooks.post_recycle.apply(inner).await { // TODO log post_recycle error return Ok(None); } inner.metrics.recycle_count += 1; #[cfg(not(target_arch = "wasm32"))] { inner.metrics.recycled = Some(Instant::now()); } Ok(Some(unready_obj.ready())) } #[inline] async fn try_create( &self, timeouts: &Timeouts, ) -> Result>, PoolError> { let mut unready_obj = UnreadyObject { inner: Some(ObjectInner { obj: apply_timeout( self.inner.runtime, TimeoutType::Create, timeouts.create, self.inner.manager.create(), ) .await?, metrics: Metrics::default(), }), pool: &self.inner, }; self.inner.slots.lock().unwrap().size += 1; // Apply post_create hooks if let Err(e) = self .inner .hooks .post_create .apply(unready_obj.inner()) .await { return Err(PoolError::PostCreateHook(e)); } Ok(Some(unready_obj.ready())) } /** * Resize the pool. This change the `max_size` of the pool dropping * excess objects and/or making space for new ones. * * If the pool is closed this method does nothing. The [`Pool::status`] method * always reports a `max_size` of 0 for closed pools. */ pub fn resize(&self, max_size: usize) { if self.inner.semaphore.is_closed() { return; } let mut slots = self.inner.slots.lock().unwrap(); let old_max_size = slots.max_size; slots.max_size = max_size; // shrink pool if max_size < old_max_size { while slots.size > slots.max_size { if let Ok(permit) = self.inner.semaphore.try_acquire() { permit.forget(); if slots.vec.pop_front().is_some() { slots.size -= 1; } } else { break; } } // Create a new VecDeque with a smaller capacity let mut vec = VecDeque::with_capacity(max_size); for obj in slots.vec.drain(..) { vec.push_back(obj); } slots.vec = vec; } // grow pool if max_size > old_max_size { let additional = slots.max_size - old_max_size; slots.vec.reserve_exact(additional); self.inner.semaphore.add_permits(additional); } } /// Retains only the objects specified by the given function. /// /// This function is typically used to remove objects from /// the pool based on their current state or metrics. /// /// **Caution:** This function blocks the entire pool while /// it is running. Therefore the given function should not /// block. /// /// The following example starts a background task that /// runs every 30 seconds and removes objects from the pool /// that haven't been used for more than one minute. /// /// ```rust,ignore /// let interval = Duration::from_secs(30); /// let max_age = Duration::from_secs(60); /// tokio::spawn(async move { /// loop { /// tokio::time::sleep(interval).await; /// pool.retain(|_, metrics| metrics.last_used() < max_age); /// } /// }); /// ``` pub fn retain( &self, mut predicate: impl FnMut(&M::Type, Metrics) -> bool, ) -> RetainResult { let mut removed = Vec::with_capacity(self.status().size); let mut guard = self.inner.slots.lock().unwrap(); let mut i = 0; // This code can be simplified once `Vec::extract_if` lands in stable Rust. // https://doc.rust-lang.org/std/vec/struct.Vec.html#method.extract_if while i < guard.vec.len() { let obj = &mut guard.vec[i]; if predicate(&mut obj.obj, obj.metrics) { i += 1; } else { let mut obj = guard.vec.remove(i).unwrap(); self.manager().detach(&mut obj.obj); removed.push(obj.obj); } } guard.size -= removed.len(); RetainResult { retained: i, removed, } } /// Get current timeout configuration pub fn timeouts(&self) -> Timeouts { self.inner.config.timeouts } /// Closes this [`Pool`]. /// /// All current and future tasks waiting for [`Object`]s will return /// [`PoolError::Closed`] immediately. /// /// This operation resizes the pool to 0. pub fn close(&self) { self.resize(0); self.inner.semaphore.close(); } /// Indicates whether this [`Pool`] has been closed. pub fn is_closed(&self) -> bool { self.inner.semaphore.is_closed() } /// Retrieves [`Status`] of this [`Pool`]. #[must_use] pub fn status(&self) -> Status { let slots = self.inner.slots.lock().unwrap(); let users = self.inner.users.load(Ordering::Relaxed); let (available, waiting) = if users < slots.size { (slots.size - users, 0) } else { (0, users - slots.size) }; Status { max_size: slots.max_size, size: slots.size, available, waiting, } } /// Returns [`Manager`] of this [`Pool`]. #[must_use] pub fn manager(&self) -> &M { &self.inner.manager } } struct PoolInner { manager: M, slots: Mutex>>, /// Number of available [`Object`]s in the [`Pool`]. If there are no /// [`Object`]s in the [`Pool`] this number can become negative and store /// the number of [`Future`]s waiting for an [`Object`]. users: AtomicUsize, semaphore: Semaphore, config: PoolConfig, runtime: Option, hooks: hooks::Hooks, } #[derive(Debug)] struct Slots { vec: VecDeque, size: usize, max_size: usize, } // Implemented manually to avoid unnecessary trait bound on the struct. impl fmt::Debug for PoolInner where M: fmt::Debug + Manager, M::Type: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PoolInner") .field("manager", &self.manager) .field("slots", &self.slots) .field("used", &self.users) .field("semaphore", &self.semaphore) .field("config", &self.config) .field("runtime", &self.runtime) .field("hooks", &self.hooks) .finish() } } impl PoolInner { fn return_object(&self, mut inner: ObjectInner) { let _ = self.users.fetch_sub(1, Ordering::Relaxed); let mut slots = self.slots.lock().unwrap(); if slots.size <= slots.max_size { slots.vec.push_back(inner); drop(slots); self.semaphore.add_permits(1); } else { slots.size -= 1; drop(slots); self.manager.detach(&mut inner.obj); } } fn detach_object(&self, obj: &mut M::Type) { let _ = self.users.fetch_sub(1, Ordering::Relaxed); let mut slots = self.slots.lock().unwrap(); let add_permits = slots.size <= slots.max_size; slots.size -= 1; drop(slots); if add_permits { self.semaphore.add_permits(1); } self.manager.detach(obj); } } async fn apply_timeout( runtime: Option, timeout_type: TimeoutType, duration: Option, future: impl Future>>>, ) -> Result> { match (runtime, duration) { (_, None) => future.await.map_err(Into::into), (Some(runtime), Some(duration)) => runtime .timeout(duration, future) .await .ok_or(PoolError::Timeout(timeout_type))? .map_err(Into::into), (None, Some(_)) => Err(PoolError::NoRuntimeSpecified), } } #[derive(Debug)] /// This is the result returned by `Pool::retain` pub struct RetainResult { /// Number of retained objects pub retained: usize, /// Objects that were removed from the pool pub removed: Vec, } impl Default for RetainResult { fn default() -> Self { Self { retained: Default::default(), removed: Default::default(), } } } deadpool-0.12.2/src/managed/reexports.rs000064400000000000000000000044511046102023000162650ustar 00000000000000//! This module contains all things that should be reexported //! by backend implementations in order to avoid direct dependencies //! on the `deadpool` crate itself. //! //! Crates based on `deadpool::managed` should include this line: //! ```rust,ignore //! pub use deadpool::managed::reexports::*; //! deadpool::managed_reexports!( //! "name_of_crate", //! Manager, //! Object, //! Error, //! ConfigError //! ); //! ``` pub use crate::{ managed::{Metrics, PoolConfig, Status, Timeouts}, Runtime, }; /// This macro creates all the type aliases usually reexported by /// deadpool-* crates. Crates that implement a deadpool manager should /// be considered stand alone crates and users of it should not need /// to use `deadpool` directly. #[macro_export] macro_rules! managed_reexports { ($crate_name:literal, $Manager:ty, $Wrapper:ty, $Error:ty, $ConfigError:ty) => { #[doc=concat!("Type alias for using [`deadpool::managed::Pool`] with [`", $crate_name, "`].")] pub type Pool = deadpool::managed::Pool<$Manager, $Wrapper>; #[doc=concat!("Type alias for using [`deadpool::managed::PoolBuilder`] with [`", $crate_name, "`].")] pub type PoolBuilder = deadpool::managed::PoolBuilder<$Manager, $Wrapper>; #[doc=concat!("Type alias for using [`deadpool::managed::BuildError`] with [`", $crate_name, "`].")] pub type BuildError = deadpool::managed::BuildError; #[doc=concat!("Type alias for using [`deadpool::managed::CreatePoolError`] with [`", $crate_name, "`].")] pub type CreatePoolError = deadpool::managed::CreatePoolError<$ConfigError>; #[doc=concat!("Type alias for using [`deadpool::managed::PoolError`] with [`", $crate_name, "`].")] pub type PoolError = deadpool::managed::PoolError<$Error>; #[doc=concat!("Type alias for using [`deadpool::managed::Object`] with [`", $crate_name, "`].")] pub type Object = deadpool::managed::Object<$Manager>; #[doc=concat!("Type alias for using [`deadpool::managed::Hook`] with [`", $crate_name, "`].")] pub type Hook = deadpool::managed::Hook<$Manager>; #[doc=concat!("Type alias for using [`deadpool::managed::HookError`] with [`", $crate_name, "`].")] pub type HookError = deadpool::managed::HookError<$Error>; }; } deadpool-0.12.2/src/unmanaged/config.rs000064400000000000000000000017631046102023000160450ustar 00000000000000use std::time::Duration; use crate::Runtime; /// Pool configuration. #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct PoolConfig { /// Maximum size of the pool. pub max_size: usize, /// Timeout for [`Pool::get()`] operation. /// /// [`Pool::get()`]: super::Pool::get pub timeout: Option, /// [`Runtime`] to be used. #[cfg_attr(feature = "serde", serde(skip))] pub runtime: Option, } impl PoolConfig { /// Create a new [`PoolConfig`] without any timeouts. #[must_use] pub fn new(max_size: usize) -> Self { Self { max_size, timeout: None, runtime: None, } } } impl Default for PoolConfig { /// Create a [`PoolConfig`] where [`PoolConfig::max_size`] is set to /// `cpu_count * 4` ignoring any logical CPUs (Hyper-Threading). fn default() -> Self { Self::new(num_cpus::get_physical() * 4) } } deadpool-0.12.2/src/unmanaged/errors.rs000064400000000000000000000014121046102023000161030ustar 00000000000000use std::fmt; /// Possible errors of [`Pool::get()`] operation. /// /// [`Pool::get()`]: super::Pool::get #[derive(Clone, Copy, Debug)] pub enum PoolError { /// Operation timeout happened. Timeout, /// Pool has been closed. Closed, /// No runtime specified. NoRuntimeSpecified, } impl fmt::Display for PoolError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Timeout => write!( f, "Timeout occurred while waiting for an object to become available", ), Self::Closed => write!(f, "Pool has been closed"), Self::NoRuntimeSpecified => write!(f, "No runtime specified"), } } } impl std::error::Error for PoolError {} deadpool-0.12.2/src/unmanaged/mod.rs000064400000000000000000000313231046102023000153520ustar 00000000000000//! Unmanaged version of the pool. //! //! "Unmanaged" means that no manager is used to create and recycle objects. //! Objects either need to be created upfront or by adding them using the //! [`Pool::add()`] or [`Pool::try_add()`] methods. //! //! # Example //! //! ```rust //! use deadpool::unmanaged::Pool; //! //! struct Computer {} //! //! impl Computer { //! async fn get_answer(&self) -> i32 { //! 42 //! } //! } //! //! #[tokio::main] //! async fn main() { //! let pool = Pool::from(vec![ //! Computer {}, //! Computer {}, //! ]); //! let s = pool.get().await.unwrap(); //! assert_eq!(s.get_answer().await, 42); //! } //! ``` mod config; mod errors; use std::{ convert::TryInto, ops::{Deref, DerefMut}, sync::{ atomic::{AtomicIsize, AtomicUsize, Ordering}, Arc, Mutex, Weak, }, time::Duration, }; use tokio::sync::{Semaphore, TryAcquireError}; pub use crate::Status; pub use self::{config::PoolConfig, errors::PoolError}; /// Wrapper around the actual pooled object which implements [`Deref`], /// [`DerefMut`] and [`Drop`] traits. /// /// Use this object just as if it was of type `T` and upon leaving a scope the /// [`Drop::drop()`] will take care of returning it to the pool. #[derive(Debug)] #[must_use] pub struct Object { /// Actual pooled object. obj: Option, /// Pool to return the pooled object to. pool: Weak>, } impl Object { /// Takes this object from the pool permanently. This reduces the size of /// the pool. If needed, the object can later be added back to the pool /// using the [`Pool::add()`] or [`Pool::try_add()`] methods. #[must_use] pub fn take(mut this: Self) -> T { if let Some(pool) = this.pool.upgrade() { let _ = pool.size.fetch_sub(1, Ordering::Relaxed); pool.size_semaphore.add_permits(1); } this.obj.take().unwrap() } } impl Drop for Object { fn drop(&mut self) { if let Some(obj) = self.obj.take() { if let Some(pool) = self.pool.upgrade() { { let mut queue = pool.queue.lock().unwrap(); queue.push(obj); } let _ = pool.available.fetch_add(1, Ordering::Relaxed); pool.semaphore.add_permits(1); pool.clean_up(); } } } } impl Deref for Object { type Target = T; fn deref(&self) -> &T { self.obj.as_ref().unwrap() } } impl DerefMut for Object { fn deref_mut(&mut self) -> &mut T { self.obj.as_mut().unwrap() } } impl AsRef for Object { fn as_ref(&self) -> &T { self } } impl AsMut for Object { fn as_mut(&mut self) -> &mut T { self } } /// Generic object and connection pool. This is the static version of the pool /// which doesn't include. /// /// This struct can be cloned and transferred across thread boundaries and uses /// reference counting for its internal state. /// /// A pool of existing objects can be created from an existing collection of /// objects if it has a known exact size: /// ```rust /// use deadpool::unmanaged::Pool; /// let pool = Pool::from(vec![1, 2, 3]); /// ``` #[derive(Debug)] pub struct Pool { inner: Arc>, } impl Clone for Pool { fn clone(&self) -> Self { Self { inner: self.inner.clone(), } } } impl Default for Pool { fn default() -> Self { Self::from_config(&PoolConfig::default()) } } impl Pool { /// Creates a new empty [`Pool`] with the given `max_size`. #[must_use] pub fn new(max_size: usize) -> Self { Self::from_config(&PoolConfig::new(max_size)) } /// Create a new empty [`Pool`] using the given [`PoolConfig`]. #[must_use] pub fn from_config(config: &PoolConfig) -> Self { Self { inner: Arc::new(PoolInner { config: *config, queue: Mutex::new(Vec::with_capacity(config.max_size)), size: AtomicUsize::new(0), size_semaphore: Semaphore::new(config.max_size), available: AtomicIsize::new(0), semaphore: Semaphore::new(0), }), } } /// Retrieves an [`Object`] from this [`Pool`] or waits for the one to /// become available. /// /// # Errors /// /// See [`PoolError`] for details. pub async fn get(&self) -> Result, PoolError> { self.timeout_get(self.inner.config.timeout).await } /// Retrieves an [`Object`] from this [`Pool`] and doesn't wait if there is /// currently no [`Object`] is available and the maximum [`Pool`] size has /// been reached. /// /// # Errors /// /// See [`PoolError`] for details. pub fn try_get(&self) -> Result, PoolError> { let inner = self.inner.as_ref(); let permit = inner.semaphore.try_acquire().map_err(|e| match e { TryAcquireError::NoPermits => PoolError::Timeout, TryAcquireError::Closed => PoolError::Closed, })?; let obj = { let mut queue = inner.queue.lock().unwrap(); queue.pop().unwrap() }; permit.forget(); let _ = inner.available.fetch_sub(1, Ordering::Relaxed); Ok(Object { pool: Arc::downgrade(&self.inner), obj: Some(obj), }) } /// Retrieves an [`Object`] from this [`Pool`] using a different `timeout` /// than the configured one. /// /// # Errors /// /// See [`PoolError`] for details. pub async fn timeout_get(&self, timeout: Option) -> Result, PoolError> { let inner = self.inner.as_ref(); let permit = match (timeout, inner.config.runtime) { (None, _) => inner .semaphore .acquire() .await .map_err(|_| PoolError::Closed), (Some(timeout), _) if timeout.as_nanos() == 0 => { inner.semaphore.try_acquire().map_err(|e| match e { TryAcquireError::NoPermits => PoolError::Timeout, TryAcquireError::Closed => PoolError::Closed, }) } (Some(timeout), Some(runtime)) => runtime .timeout(timeout, inner.semaphore.acquire()) .await .ok_or(PoolError::Timeout)? .map_err(|_| PoolError::Closed), (Some(_), None) => Err(PoolError::NoRuntimeSpecified), }?; let obj = { let mut queue = inner.queue.lock().unwrap(); queue.pop().unwrap() }; permit.forget(); let _ = inner.available.fetch_sub(1, Ordering::Relaxed); Ok(Object { pool: Arc::downgrade(&self.inner), obj: Some(obj), }) } /// Adds an `object` to this [`Pool`]. /// /// If the [`Pool`] size has already reached its maximum, then this function /// blocks until the `object` can be added to the [`Pool`]. /// /// # Errors /// /// If the [`Pool`] has been closed a tuple containing the `object` and /// the [`PoolError`] is returned instead. pub async fn add(&self, object: T) -> Result<(), (T, PoolError)> { match self.inner.size_semaphore.acquire().await { Ok(permit) => { permit.forget(); self._add(object); Ok(()) } Err(_) => Err((object, PoolError::Closed)), } } /// Tries to add an `object` to this [`Pool`]. /// /// # Errors /// /// If the [`Pool`] size has already reached its maximum, or the [`Pool`] /// has been closed, then a tuple containing the `object` and the /// [`PoolError`] is returned instead. pub fn try_add(&self, object: T) -> Result<(), (T, PoolError)> { match self.inner.size_semaphore.try_acquire() { Ok(permit) => { permit.forget(); self._add(object); Ok(()) } Err(e) => Err(match e { TryAcquireError::NoPermits => (object, PoolError::Timeout), TryAcquireError::Closed => (object, PoolError::Closed), }), } } /// Internal function which adds an `object` to this [`Pool`]. /// /// Prior calling this it must be guaranteed that `size` doesn't exceed /// `max_size`. In the methods `add` and `try_add` this is ensured by using /// the `size_semaphore`. fn _add(&self, object: T) { let _ = self.inner.size.fetch_add(1, Ordering::Relaxed); { let mut queue = self.inner.queue.lock().unwrap(); queue.push(object); } let _ = self.inner.available.fetch_add(1, Ordering::Relaxed); self.inner.semaphore.add_permits(1); } /// Removes an [`Object`] from this [`Pool`]. pub async fn remove(&self) -> Result { self.get().await.map(Object::take) } /// Tries to remove an [`Object`] from this [`Pool`]. pub fn try_remove(&self) -> Result { self.try_get().map(Object::take) } /// Removes an [`Object`] from this [`Pool`] using a different `timeout` /// than the configured one. pub async fn timeout_remove(&self, timeout: Option) -> Result { self.timeout_get(timeout).await.map(Object::take) } /// Closes this [`Pool`]. /// /// All current and future tasks waiting for [`Object`]s will return /// [`PoolError::Closed`] immediately. pub fn close(&self) { self.inner.semaphore.close(); self.inner.size_semaphore.close(); self.inner.clear(); } /// Indicates whether this [`Pool`] has been closed. pub fn is_closed(&self) -> bool { self.inner.is_closed() } /// Retrieves [`Status`] of this [`Pool`]. #[must_use] pub fn status(&self) -> Status { let max_size = self.inner.config.max_size; let size = self.inner.size.load(Ordering::Relaxed); let available = self.inner.available.load(Ordering::Relaxed); Status { max_size, size, available: if available > 0 { available as usize } else { 0 }, waiting: if available < 0 { (-available) as usize } else { 0 }, } } } #[derive(Debug)] struct PoolInner { config: PoolConfig, queue: Mutex>, size: AtomicUsize, /// This semaphore has as many permits as `max_size - size`. Every time /// an [`Object`] is added to the [`Pool`] a permit is removed from the /// semaphore and every time an [`Object`] is removed a permit is returned /// back. size_semaphore: Semaphore, /// Number of available [`Object`]s in the [`Pool`]. If there are no /// [`Object`]s in the [`Pool`] this number can become negative and store /// the number of [`Future`]s waiting for an [`Object`]. /// /// [`Future`]: std::future::Future available: AtomicIsize, semaphore: Semaphore, } impl PoolInner { /// Cleans up internals of this [`Pool`]. /// /// This method is called after closing the [`Pool`] and whenever an /// [`Object`] is returned to the [`Pool`] and makes sure closed [`Pool`]s /// don't contain any [`Object`]s. fn clean_up(&self) { if self.is_closed() { self.clear(); } } /// Removes all the [`Object`]s which are currently part of this [`Pool`]. fn clear(&self) { let mut queue = self.queue.lock().unwrap(); let _ = self.size.fetch_sub(queue.len(), Ordering::Relaxed); let _ = self .available .fetch_sub(queue.len() as isize, Ordering::Relaxed); queue.clear(); } /// Indicates whether this [`Pool`] has been closed. fn is_closed(&self) -> bool { matches!( self.semaphore.try_acquire_many(0), Err(TryAcquireError::Closed) ) } } impl From for Pool where I: IntoIterator, ::IntoIter: ExactSizeIterator, { /// Creates a new [`Pool`] from the given [`ExactSizeIterator`] of /// [`Object`]s. fn from(iter: I) -> Self { let queue = iter.into_iter().collect::>(); let len = queue.len(); Self { inner: Arc::new(PoolInner { queue: Mutex::new(queue), config: PoolConfig::new(len), size: AtomicUsize::new(len), size_semaphore: Semaphore::new(0), available: AtomicIsize::new(len.try_into().unwrap()), semaphore: Semaphore::new(len), }), } } } deadpool-0.12.2/test-build.sh000075500000000000000000000021371046102023000141130ustar 00000000000000#!/bin/bash set -xe cargo build cargo build --no-default-features cargo build --no-default-features --features managed cargo build --no-default-features --features unmanaged cargo build --no-default-features --features serde cargo build --no-default-features --features managed,serde cargo build --no-default-features --features unmanaged,serde cargo build --no-default-features --features managed,unmanaged ( cd postgres cargo build cargo build --no-default-features cargo build --no-default-features --features rt_tokio_1 cargo build --no-default-features --features rt_async-std_1 ) ( cd redis cargo build cargo build --no-default-features --features rt_tokio_1 cargo build --no-default-features --features rt_async-std_1 cargo build --no-default-features --features rt_tokio_1 cluster cargo build --no-default-features --features rt_async-std_1 cluster ) ( cd lapin cargo build cargo build --no-default-features cargo build --no-default-features --features rt_tokio_1 cargo build --no-default-features --features rt_async-std_1 ) ( cd sqlite cargo build cargo build --no-default-features ) deadpool-0.12.2/tests/managed.rs000064400000000000000000000142751046102023000146120ustar 00000000000000#![cfg(feature = "managed")] use std::{convert::Infallible, time::Duration}; use tokio::time; use deadpool::managed::{self, Metrics, Object, PoolError, RecycleResult, Timeouts}; type Pool = managed::Pool; struct Manager {} impl managed::Manager for Manager { type Type = usize; type Error = Infallible; async fn create(&self) -> Result { Ok(0) } async fn recycle(&self, _conn: &mut usize, _: &Metrics) -> RecycleResult { Ok(()) } } #[tokio::test] async fn basic() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(16).build().unwrap(); let status = pool.status(); assert_eq!(status.size, 0); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); let obj0 = pool.get().await.unwrap(); let status = pool.status(); assert_eq!(status.size, 1); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); let obj1 = pool.get().await.unwrap(); let status = pool.status(); assert_eq!(status.size, 2); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); let obj2 = pool.get().await.unwrap(); let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); drop(obj0); let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 1); assert_eq!(status.waiting, 0); drop(obj1); let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 2); assert_eq!(status.waiting, 0); drop(obj2); let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 3); assert_eq!(status.waiting, 0); } #[tokio::test] async fn closing() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(1).build().unwrap(); // fetch the only object from the pool let obj = pool.get().await; let join_handle = { let pool = pool.clone(); tokio::spawn(async move { pool.get().await }) }; tokio::task::yield_now().await; assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 1); pool.close(); tokio::task::yield_now().await; assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 0); assert!(matches!(join_handle.await.unwrap(), Err(PoolError::Closed))); assert!(matches!(pool.get().await, Err(PoolError::Closed))); assert!(matches!( pool.timeout_get(&Timeouts { wait: Some(Duration::ZERO), ..pool.timeouts() }) .await, Err(PoolError::Closed) )); drop(obj); tokio::task::yield_now().await; assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 0); } #[tokio::test(flavor = "multi_thread")] async fn concurrent() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(3).build().unwrap(); // Spawn tasks let futures = (0..100) .map(|_| { let pool = pool.clone(); tokio::spawn(async move { let mut obj = pool.get().await.unwrap(); *obj += 1; time::sleep(Duration::from_millis(1)).await; }) }) .collect::>(); // Await tasks to finish for future in futures { future.await.unwrap(); } // Verify let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 3); assert_eq!(status.waiting, 0); let values = [ pool.get().await.unwrap(), pool.get().await.unwrap(), pool.get().await.unwrap(), ]; assert_eq!(values.iter().map(|obj| **obj).sum::(), 100); } #[tokio::test(flavor = "multi_thread")] async fn object_take() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(2).build().unwrap(); let obj0 = pool.get().await.unwrap(); let obj1 = pool.get().await.unwrap(); let status = pool.status(); assert_eq!(status.size, 2); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); let _ = Object::take(obj0); let status = pool.status(); assert_eq!(status.size, 1); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); let _ = Object::take(obj1); let status = pool.status(); assert_eq!(status.size, 0); assert_eq!(status.available, 0); let obj0 = pool.get().await.unwrap(); let obj1 = pool.get().await.unwrap(); let status = pool.status(); assert_eq!(status.size, 2); assert_eq!(status.available, 0); assert_eq!(status.waiting, 0); drop(obj0); drop(obj1); let status = pool.status(); assert_eq!(status.size, 2); assert_eq!(status.available, 2); assert_eq!(status.waiting, 0); } #[tokio::test] async fn retain() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(4).build().unwrap(); { let _a = pool.get().await; let _b = pool.get().await; tokio::time::sleep(Duration::from_millis(5)).await; let _c = pool.get().await; tokio::time::sleep(Duration::from_millis(5)).await; } assert_eq!(pool.status().size, 3); let retain_result = pool.retain(|_, metrics| metrics.age() <= Duration::from_millis(10)); assert_eq!(retain_result.retained, 1); assert_eq!(retain_result.removed.len(), 2); assert_eq!(pool.status().size, 1); tokio::time::sleep(Duration::from_millis(5)).await; let retain_result = pool.retain(|_, metrics| metrics.age() <= Duration::from_millis(10)); assert_eq!(retain_result.retained, 0); assert_eq!(retain_result.removed.len(), 1); assert_eq!(pool.status().size, 0); } #[tokio::test] async fn retain_fnmut() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(4).build().unwrap(); { let _a = pool.get().await; let _b = pool.get().await; let _c = pool.get().await; let _c = pool.get().await; } let mut removed = 0; { let retain_result = pool.retain(|_, _| { removed += 1; false }); assert_eq!(retain_result.retained, 0); assert_eq!(retain_result.removed.len(), 4); } assert_eq!(pool.status().size, 0); } deadpool-0.12.2/tests/managed_cancellation.rs000064400000000000000000000113631046102023000173210ustar 00000000000000use std::time::Duration; use deadpool::managed::{Hook, HookError, Manager, Metrics, Pool, RecycleResult}; use itertools::Itertools; use tokio::time::{sleep, timeout}; #[derive(Copy, Clone, Debug, Eq, PartialEq)] enum Gate { Ok, Err, Slow, Never, } impl Gate { async fn open(&self) -> Result<(), ()> { match self { Self::Ok => Ok(()), Self::Err => Err(()), Self::Never => { sleep(Duration::MAX).await; unreachable!(); } Self::Slow => { sleep(Duration::from_nanos(2)).await; Ok(()) } } } } #[derive(Copy, Clone, Debug)] struct Gates { create: Gate, recycle: Gate, post_create: Gate, pre_recycle: Gate, post_recycle: Gate, } fn configs() -> impl Iterator { (0..5) .map(|_| &[Gate::Ok, Gate::Err, Gate::Slow, Gate::Never]) .multi_cartesian_product() .map(move |gates| Gates { create: *gates[0], recycle: *gates[1], post_create: *gates[2], pre_recycle: *gates[3], post_recycle: *gates[4], }) } fn pools(max_size: usize) -> impl Iterator> { configs().map(move |gates| { let manager = GatedManager { gates }; Pool::builder(manager) .max_size(max_size) .post_create(Hook::async_fn(move |_, _| { Box::pin(async move { gates .post_create .open() .await .map_err(|_| HookError::message("Fail"))?; Ok(()) }) })) .pre_recycle(Hook::async_fn(move |_, _| { Box::pin(async move { gates .pre_recycle .open() .await .map_err(|_| HookError::message("pre_recycle gate set to error"))?; Ok(()) }) })) .post_recycle(Hook::async_fn(move |_, _| { Box::pin(async move { gates .post_recycle .open() .await .map_err(|_| HookError::message("post_recycle gate set to error"))?; Ok(()) }) })) .build() .unwrap() }) } struct GatedManager { gates: Gates, } impl Manager for GatedManager { type Type = (); type Error = (); async fn create(&self) -> Result { self.gates.create.open().await?; Ok(()) } async fn recycle(&self, _conn: &mut Self::Type, _: &Metrics) -> RecycleResult { self.gates.recycle.open().await?; Ok(()) } } // This tests various combinations of configurations with // succeeding, failing, slow and hanging managers and hooks. // It currently tests 4^5 (=1024) possible combinations and // therefore takes some time to complete. It is probably not // neccesary to test all combinations, but doing so doesn't // hurt either and it is a good stress test of the pool. #[tokio::test(flavor = "multi_thread", worker_threads = 16)] async fn test_cancellations() { for pool in pools(2) { let handles = (0..8) .map(|i| { let pool = pool.clone(); tokio::spawn(async move { loop { let _obj = timeout(Duration::from_nanos(i), pool.get()).await; sleep(Duration::from_nanos(i)).await; } }) }) .collect::>(); for _ in 0..10 { tokio::time::sleep(Duration::from_millis(1)).await; let status = pool.status(); assert!( status.size <= status.max_size, "size({}) > max_size({}), gates: {:?}", status.size, status.max_size, pool.manager().gates ); } for handle in &handles { handle.abort(); } for handle in handles { let _ = handle.await; } let status = pool.status(); assert!( status.size <= status.max_size, "size({}) > max_size({}), gates: {:?}", status.size, status.max_size, pool.manager().gates ); assert!( status.available <= status.max_size, "available({}) > max_size({}), gates: {:?}", status.available, status.max_size, pool.manager().gates ); } } deadpool-0.12.2/tests/managed_config.rs000064400000000000000000000033151046102023000161300ustar 00000000000000#![cfg(all(feature = "managed", feature = "serde"))] use std::{collections::HashMap, env, time::Duration}; use config::Config; use serde::{Deserialize, Serialize}; use deadpool::managed::PoolConfig; struct Env { backup: HashMap>, } impl Env { pub fn new() -> Self { Self { backup: HashMap::new(), } } pub fn set(&mut self, name: &str, value: &str) { self.backup.insert(name.to_string(), env::var(name).ok()); env::set_var(name, value); } } impl Drop for Env { fn drop(&mut self) { for (name, value) in self.backup.iter() { match value { Some(value) => env::set_var(name.as_str(), value), None => env::remove_var(name.as_str()), } } } } #[derive(Debug, Serialize, Deserialize)] struct TestConfig { pool: PoolConfig, } #[test] fn from_env() { let mut env = Env::new(); env.set("POOL__MAX_SIZE", "42"); env.set("POOL__TIMEOUTS__WAIT__SECS", "1"); env.set("POOL__TIMEOUTS__WAIT__NANOS", "0"); env.set("POOL__TIMEOUTS__CREATE__SECS", "2"); env.set("POOL__TIMEOUTS__CREATE__NANOS", "0"); env.set("POOL__TIMEOUTS__RECYCLE__SECS", "3"); env.set("POOL__TIMEOUTS__RECYCLE__NANOS", "0"); let cfg = Config::builder() .add_source(config::Environment::default().separator("__")) .build() .unwrap() .try_deserialize::() .unwrap(); assert_eq!(cfg.pool.max_size, 42); assert_eq!(cfg.pool.timeouts.wait, Some(Duration::from_secs(1))); assert_eq!(cfg.pool.timeouts.create, Some(Duration::from_secs(2))); assert_eq!(cfg.pool.timeouts.recycle, Some(Duration::from_secs(3))); } deadpool-0.12.2/tests/managed_deadlock.rs000064400000000000000000000067261046102023000164420ustar 00000000000000#![cfg(feature = "managed")] use std::{sync::Arc, time::Duration}; use tokio::{ sync::{mpsc, Mutex}, task, time, }; use deadpool::managed::{self, Metrics, RecycleError, RecycleResult}; type Pool = managed::Pool; #[derive(Clone)] struct Manager { create_rx: Arc>>>, recycle_rx: Arc>>>, remote_control: RemoteControl, } #[derive(Clone)] struct RemoteControl { create_tx: mpsc::Sender>, _recycle_tx: mpsc::Sender>, } impl RemoteControl { pub fn create_ok(&mut self) { self.create_tx.try_send(Ok(())).unwrap(); } pub fn create_err(&mut self) { self.create_tx.try_send(Err(())).unwrap(); } /* pub fn recycle_ok(&mut self) { self.recycle_tx.try_send(Ok(())).unwrap(); } pub fn recycle_err(&mut self) { self.recycle_tx.try_send(Err(())).unwrap(); } */ } impl Manager { pub fn new() -> Self { let (create_tx, create_rx) = mpsc::channel(16); let (recycle_tx, recycle_rx) = mpsc::channel(16); Self { create_rx: Arc::new(Mutex::new(create_rx)), recycle_rx: Arc::new(Mutex::new(recycle_rx)), remote_control: RemoteControl { create_tx, _recycle_tx: recycle_tx, }, } } } impl managed::Manager for Manager { type Type = (); type Error = (); async fn create(&self) -> Result<(), ()> { self.create_rx.lock().await.recv().await.unwrap() } async fn recycle(&self, _conn: &mut (), _: &Metrics) -> RecycleResult<()> { match self.recycle_rx.lock().await.recv().await.unwrap() { Ok(()) => Ok(()), Err(e) => Err(RecycleError::Backend(e)), } } } // When the pool is drained, all connections fail to create. #[tokio::test(flavor = "current_thread")] async fn pool_drained() { let manager = Manager::new(); let mut rc = manager.remote_control.clone(); let pool = Pool::builder(manager).max_size(1).build().unwrap(); let pool_clone = pool.clone(); // let first task grab the only connection let get_1 = tokio::spawn(async move { pool_clone.get().await }); task::yield_now().await; assert_eq!(pool.status().size, 0); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 1); // let second task wait for the connection let pool_clone = pool.clone(); let get_2 = tokio::spawn(async move { pool_clone.get().await }); task::yield_now().await; assert_eq!(pool.status().size, 0); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 2); // first task receives an error rc.create_err(); assert!(get_1.await.unwrap().is_err()); assert_eq!(pool.status().size, 0); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 1); // the second task should now be able to create an object rc.create_ok(); let get_2_result = time::timeout(Duration::from_millis(10), get_2).await; assert!(get_2_result.is_ok(), "get_2 should not time out"); assert_eq!(pool.status().size, 1); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 0); assert!( get_2_result.unwrap().unwrap().is_ok(), "get_2 should receive an object" ); assert_eq!(pool.status().size, 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().waiting, 0); } deadpool-0.12.2/tests/managed_hooks.rs000064400000000000000000000124241046102023000160070ustar 00000000000000#![cfg(feature = "managed")] use std::sync::atomic::{AtomicUsize, Ordering}; use deadpool::managed::{Hook, HookError, Manager, Metrics, Pool, RecycleResult}; struct Computer { next_id: AtomicUsize, } impl Computer { pub fn new(start: usize) -> Self { Self { next_id: AtomicUsize::new(start), } } } impl Manager for Computer { type Type = usize; type Error = (); async fn create(&self) -> Result { Ok(self.next_id.fetch_add(1, Ordering::Relaxed)) } async fn recycle(&self, _: &mut Self::Type, _: &Metrics) -> RecycleResult { Ok(()) } } #[tokio::test] async fn post_create_ok() { let manager = Computer::new(42); let pool = Pool::::builder(manager) .max_size(1) .post_create(Hook::sync_fn(|obj, _| { *obj += 1; Ok(()) })) .build() .unwrap(); assert!(*pool.get().await.unwrap() == 43); } #[tokio::test] async fn post_create_ok_async() { let manager = Computer::new(42); let pool = Pool::::builder(manager) .max_size(1) .post_create(Hook::async_fn(|obj, _| { Box::pin(async move { *obj += 1; Ok(()) }) })) .build() .unwrap(); assert!(*pool.get().await.unwrap() == 43); } #[tokio::test] async fn post_create_err_abort() { let manager = Computer::new(0); let pool = Pool::::builder(manager) .max_size(3) .post_create(Hook::sync_fn(|obj, _| { (*obj % 2 == 0) .then_some(()) .ok_or(HookError::message("odd creation")) })) .build() .unwrap(); let obj1 = pool.get().await.unwrap(); assert_eq!(*obj1, 0); assert!(pool.get().await.is_err()); let obj2 = pool.get().await.unwrap(); assert_eq!(*obj2, 2); assert!(pool.get().await.is_err()); let obj2 = pool.get().await.unwrap(); assert_eq!(*obj2, 4); } #[tokio::test] async fn pre_recycle_ok() { let manager = Computer::new(42); let pool = Pool::::builder(manager) .max_size(1) .pre_recycle(Hook::sync_fn(|obj, _| { *obj += 1; Ok(()) })) .build() .unwrap(); assert!(*pool.get().await.unwrap() == 42); assert!(*pool.get().await.unwrap() == 43); assert!(*pool.get().await.unwrap() == 44); assert!(*pool.get().await.unwrap() == 45); } #[tokio::test] async fn pre_recycle_err_continue() { let manager = Computer::new(0); let pool = Pool::::builder(manager) .max_size(1) .pre_recycle(Hook::sync_fn(|_, metrics| { if metrics.recycle_count > 0 { Err(HookError::message("Fail!")) } else { Ok(()) } })) .build() .unwrap(); assert_eq!(*pool.get().await.unwrap(), 0); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 0); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 2); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 2); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); } #[tokio::test] async fn post_recycle_ok() { let manager = Computer::new(42); let pool = Pool::::builder(manager) .max_size(1) .post_recycle(Hook::sync_fn(|obj, _| { *obj += 1; Ok(()) })) .build() .unwrap(); assert!(*pool.get().await.unwrap() == 42); assert!(*pool.get().await.unwrap() == 43); assert!(*pool.get().await.unwrap() == 44); assert!(*pool.get().await.unwrap() == 45); } #[tokio::test] async fn post_recycle_err_continue() { let manager = Computer::new(0); let pool = Pool::::builder(manager) .max_size(1) .post_recycle(Hook::sync_fn(|_, metrics| { if metrics.recycle_count > 0 { Err(HookError::message("Fail!")) } else { Ok(()) } })) .build() .unwrap(); assert_eq!(*pool.get().await.unwrap(), 0); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 0); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 2); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); assert_eq!(*pool.get().await.unwrap(), 2); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().size, 1); } deadpool-0.12.2/tests/managed_resize.rs000064400000000000000000000111771046102023000161710ustar 00000000000000#![cfg(feature = "managed")] use std::convert::Infallible; use deadpool::managed::{self, Metrics, Object, RecycleResult}; type Pool = managed::Pool>; struct Manager {} impl managed::Manager for Manager { type Type = (); type Error = Infallible; async fn create(&self) -> Result<(), Infallible> { Ok(()) } async fn recycle(&self, _conn: &mut (), _: &Metrics) -> RecycleResult { Ok(()) } } // Regression test for https://github.com/bikeshedder/deadpool/issues/380 #[tokio::test] async fn test_grow_reuse_existing() { // Shrink doesn't discard objects currently borrowed from the pool but // keeps track of them so that repeatedly growing and shrinking will // not cause excessive object creation. This logic used to contain a bug // causing an overflow. let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(2).build().unwrap(); let obj1 = pool.get().await.unwrap(); let obj2 = pool.get().await.unwrap(); assert!(pool.status().size == 2); assert!(pool.status().max_size == 2); pool.resize(0); // At this point the two objects are still tracked assert!(pool.status().size == 2); assert!(pool.status().max_size == 0); pool.resize(1); // Only one of the objects should be returned to the pool assert!(pool.status().size == 2); assert!(pool.status().max_size == 1); drop(obj1); // The first drop brings the size to 1. assert!(pool.status().size == 1); assert!(pool.status().max_size == 1); drop(obj2); assert!(pool.status().size == 1); assert!(pool.status().max_size == 1); } #[tokio::test] async fn resize_pool_shrink() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(2).build().unwrap(); let obj0 = pool.get().await.unwrap(); let obj1 = pool.get().await.unwrap(); pool.resize(1); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 2); drop(obj1); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 1); drop(obj0); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 1); } #[tokio::test] async fn resize_pool_grow() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(1).build().unwrap(); let obj0 = pool.get().await.unwrap(); pool.resize(2); assert_eq!(pool.status().max_size, 2); assert_eq!(pool.status().size, 1); let obj1 = pool.get().await.unwrap(); assert_eq!(pool.status().max_size, 2); assert_eq!(pool.status().size, 2); drop(obj1); assert_eq!(pool.status().max_size, 2); assert_eq!(pool.status().size, 2); drop(obj0); assert_eq!(pool.status().max_size, 2); assert_eq!(pool.status().size, 2); } #[tokio::test] async fn resize_pool_shrink_grow() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(1).build().unwrap(); let obj0 = pool.get().await.unwrap(); pool.resize(2); pool.resize(0); pool.resize(5); assert_eq!(pool.status().max_size, 5); assert_eq!(pool.status().size, 1); drop(obj0); assert_eq!(pool.status().max_size, 5); assert_eq!(pool.status().size, 1); } #[tokio::test] async fn resize_pool_grow_concurrent() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(0).build().unwrap(); let join_handle = { let pool = pool.clone(); tokio::spawn(async move { pool.get().await }) }; tokio::task::yield_now().await; assert_eq!(pool.status().max_size, 0); assert_eq!(pool.status().size, 0); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 1); pool.resize(1); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 0); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 1); tokio::task::yield_now().await; assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 1); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 0); let obj0 = join_handle.await.unwrap().unwrap(); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 1); assert_eq!(pool.status().available, 0); assert_eq!(pool.status().waiting, 0); drop(obj0); assert_eq!(pool.status().max_size, 1); assert_eq!(pool.status().size, 1); assert_eq!(pool.status().available, 1); assert_eq!(pool.status().waiting, 0); } #[tokio::test] async fn close_resize() { let mgr = Manager {}; let pool = Pool::builder(mgr).max_size(1).build().unwrap(); pool.close(); pool.resize(16); assert_eq!(pool.status().size, 0); assert_eq!(pool.status().max_size, 0); } deadpool-0.12.2/tests/managed_timeout.rs000064400000000000000000000032631046102023000163530ustar 00000000000000#![cfg(all( feature = "managed", any(feature = "rt_tokio_1", feature = "rt_async-std_1") ))] use std::{convert::Infallible, future::Future, pin::Pin, task, time::Duration}; use deadpool::{ managed::{self, Metrics, Object, PoolConfig, PoolError, RecycleResult, Timeouts}, Runtime, }; type Pool = managed::Pool>; struct Manager {} struct Never; impl Future for Never { type Output = (); fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> task::Poll { task::Poll::Pending } } impl managed::Manager for Manager { type Type = usize; type Error = Infallible; async fn create(&self) -> Result { Never.await; unreachable!(); } async fn recycle(&self, _conn: &mut usize, _: &Metrics) -> RecycleResult { Never.await; unreachable!(); } } async fn test_managed_timeout(runtime: Runtime) { let mgr = Manager {}; let cfg = PoolConfig { max_size: 16, timeouts: Timeouts { create: Some(Duration::from_millis(0)), wait: Some(Duration::from_millis(0)), recycle: Some(Duration::from_millis(0)), }, ..Default::default() }; let pool = Pool::builder(mgr) .config(cfg) .runtime(runtime) .build() .unwrap(); assert!(matches!(pool.get().await, Err(PoolError::Timeout(_)))); } #[cfg(feature = "rt_tokio_1")] #[tokio::test] async fn rt_tokio_1() { test_managed_timeout(Runtime::Tokio1).await; } #[cfg(feature = "rt_async-std_1")] #[async_std::test] async fn rt_async_std_1() { test_managed_timeout(Runtime::AsyncStd1).await; } deadpool-0.12.2/tests/managed_unreliable_manager.rs000064400000000000000000000045751046102023000205100ustar 00000000000000#![cfg(feature = "managed")] use std::{ sync::atomic::{AtomicUsize, Ordering}, time::Duration, }; use tokio::time; use deadpool::managed::{self, Metrics, RecycleError, RecycleResult}; type Pool = managed::Pool; struct Manager { create_fail: bool, recycle_fail: bool, detached: AtomicUsize, } impl managed::Manager for Manager { type Type = (); type Error = (); async fn create(&self) -> Result<(), ()> { if self.create_fail { Err(()) } else { Ok(()) } } async fn recycle(&self, _conn: &mut (), _: &Metrics) -> RecycleResult<()> { if self.recycle_fail { Err(RecycleError::Backend(())) } else { Ok(()) } } fn detach(&self, _obj: &mut Self::Type) { self.detached.fetch_add(1, Ordering::Relaxed); } } #[tokio::test] async fn create() { let manager = Manager { create_fail: true, recycle_fail: false, detached: AtomicUsize::new(0), }; let pool = Pool::builder(manager).max_size(16).build().unwrap(); { assert!(pool.get().await.is_err()); } let status = pool.status(); assert_eq!(status.available, 0); assert_eq!(status.size, 0); { assert!(time::timeout(Duration::from_millis(10), pool.get()) .await .unwrap() .is_err()); } assert_eq!(status.available, 0); assert_eq!(status.size, 0); } #[tokio::test] async fn recycle() { let manager = Manager { create_fail: false, recycle_fail: true, detached: AtomicUsize::new(0), }; let pool = Pool::builder(manager).max_size(16).build().unwrap(); { let _a = pool.get().await.unwrap(); let _b = pool.get().await.unwrap(); } let status = pool.status(); assert_eq!(status.available, 2); assert_eq!(status.size, 2); assert_eq!(pool.manager().detached.load(Ordering::Relaxed), 0); { let _a = pool.get().await.unwrap(); // All connections fail to recycle. Thus reducing the // available counter to 0. let status = pool.status(); assert_eq!(status.available, 0); assert_eq!(status.size, 1); assert_eq!(pool.manager().detached.load(Ordering::Relaxed), 2); } let status = pool.status(); assert_eq!(status.available, 1); assert_eq!(status.size, 1); } deadpool-0.12.2/tests/unmanaged.rs000064400000000000000000000103351046102023000151460ustar 00000000000000#![cfg(feature = "unmanaged")] use std::time::Duration; use tokio::{task, time}; use deadpool::unmanaged::{Pool, PoolError}; #[tokio::test] async fn basic() { let pool = Pool::from(vec![(), (), ()]); let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 3); let _val0 = pool.get().await; let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 2); let _val1 = pool.get().await; let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 1); let _val2 = pool.get().await; let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 0); } #[tokio::test] async fn closing() { let pool = Pool::::new(1); pool.try_add(42).unwrap(); let obj = pool.get().await.unwrap(); let join_handle = { let pool = pool.clone(); tokio::spawn(async move { pool.get().await }) }; assert!(!pool.is_closed()); assert_eq!(pool.status().available, 0); task::yield_now().await; pool.close(); assert!(pool.is_closed()); task::yield_now().await; assert_eq!(pool.status().available, 0); assert!(matches!(join_handle.await.unwrap(), Err(PoolError::Closed))); assert!(matches!(pool.get().await, Err(PoolError::Closed))); assert!(matches!(pool.try_get(), Err(PoolError::Closed))); drop(obj); assert!(pool.is_closed()); assert!(matches!(pool.try_get(), Err(PoolError::Closed))); assert_eq!(pool.status().available, 0); } #[tokio::test(flavor = "multi_thread")] async fn concurrent() { let pool = Pool::from(vec![0usize, 0, 0]); // Spawn tasks let futures = (0..100) .map(|_| { let pool = pool.clone(); tokio::spawn(async move { *pool.get().await.unwrap() += 1; }) }) .collect::>(); // Await tasks to finish for future in futures { future.await.unwrap(); } // Verify let status = pool.status(); assert_eq!(status.size, 3); assert_eq!(status.available, 3); let values = [pool.get().await, pool.get().await, pool.get().await]; assert_eq!( values .iter() .map(|obj| **obj.as_ref().unwrap()) .sum::(), 100, ); } #[tokio::test(flavor = "multi_thread")] async fn test_unmanaged_add_remove() { let pool = Pool::new(2); pool.add(1).await.unwrap(); assert_eq!(pool.status().size, 1); pool.add(2).await.unwrap(); assert_eq!(pool.status().size, 2); assert!( time::timeout(Duration::from_millis(10), pool.add(3)) .await .is_err(), "adding a third item should timeout" ); pool.remove().await.unwrap(); assert_eq!(pool.status().size, 1); assert!( time::timeout(Duration::from_millis(10), pool.add(3)) .await .is_ok(), "adding a third item should not timeout" ); pool.remove().await.unwrap(); assert_eq!(pool.status().size, 1); pool.remove().await.unwrap(); assert_eq!(pool.status().size, 0); } #[tokio::test(flavor = "multi_thread")] async fn try_add_try_remove() { let pool = Pool::new(2); pool.try_add(1).unwrap(); assert_eq!(pool.status().size, 1); pool.try_add(2).unwrap(); assert_eq!(pool.status().size, 2); assert!(pool.try_add(3).is_err()); pool.try_remove().unwrap(); assert_eq!(pool.status().size, 1); assert!(pool.try_add(3).is_ok()); pool.try_remove().unwrap(); assert_eq!(pool.status().size, 1); pool.try_remove().unwrap(); assert_eq!(pool.status().size, 0); } #[tokio::test(flavor = "multi_thread")] async fn add_timeout() { let pool = Pool::from(vec![1]); let add = { let pool = pool.clone(); tokio::spawn(async move { pool.add(2).await.unwrap(); }) }; let mut iv = time::interval(Duration::from_millis(10)); iv.tick().await; iv.tick().await; pool.try_remove().unwrap(); assert!( time::timeout(Duration::from_millis(10), add).await.is_ok(), "add should not timeout" ); assert_eq!(pool.status().size, 1); assert_eq!(pool.try_remove().unwrap(), 2); } deadpool-0.12.2/tests/unmanaged_timeout.rs000064400000000000000000000026351046102023000167200ustar 00000000000000#![cfg(feature = "unmanaged")] use std::time::Duration; use deadpool::{ unmanaged::{self, PoolConfig, PoolError}, Runtime, }; type Pool = unmanaged::Pool<()>; #[tokio::test] async fn no_runtime() { let pool = Pool::default(); assert!(matches!( pool.timeout_get(Some(Duration::from_millis(1))).await, Err(PoolError::NoRuntimeSpecified) )); assert!(matches!( pool.timeout_get(Some(Duration::from_millis(0))).await, Err(PoolError::Timeout) )); } async fn _test_get(runtime: Runtime) { let cfg = PoolConfig { max_size: 16, timeout: None, runtime: Some(runtime), }; let pool = Pool::from_config(&cfg); assert!(matches!( pool.timeout_get(Some(Duration::from_millis(1))).await, Err(PoolError::Timeout), )); } async fn _test_config(runtime: Runtime) { let cfg = PoolConfig { max_size: 16, timeout: Some(Duration::from_millis(1)), runtime: Some(runtime), }; let pool = Pool::from_config(&cfg); assert!(matches!(pool.get().await, Err(PoolError::Timeout))); } #[cfg(feature = "rt_tokio_1")] #[tokio::test] async fn rt_tokio_1() { _test_get(Runtime::Tokio1).await; _test_config(Runtime::Tokio1).await; } #[cfg(feature = "rt_async-std_1")] #[async_std::test] async fn rt_async_std_1() { _test_get(Runtime::AsyncStd1).await; _test_config(Runtime::AsyncStd1).await; }