pool-mod 0.9.0

Generic object and connection pooling. Async-safe with min/max sizing, idle timeouts, max-lifetime enforcement, validation-on-borrow, and health-check callbacks. Works for database connections, HTTP clients, worker threads, or any expensive resource.
Documentation
# pool-mod v0.9.0 — Hardening + Audit

**Feature freeze.** v0.9.0 lands the last feature — an opt-in background reaper —
and then submits the crate to the mandatory pre-1.0 audit. Everything else is
verification: code cleanliness, error hardening, supply-chain checks, and the full
cross-platform matrix on stable and the MSRV. No breaking changes to the existing
surface; one additive config field. The crate is now feature-complete.

## What is pool-mod?

A generic object and connection pool for Rust. Implement one trait describing how
to create, validate, and recycle a resource, and the pool handles sizing, blocking
acquisition with timeouts, validation-on-borrow, and idle/lifetime expiry. It is
runtime-agnostic and has zero runtime dependencies.

## What's new in 0.9.0

### Background reaper (opt-in)

Until now, an idle resource past its `idle_timeout` or `max_lifetime` was only
discarded when something tried to borrow it — fine for a busy pool, but a resource
that is never borrowed again could linger until the pool closed. That matters for
connection pools, where servers drop idle connections on their own schedule.

`PoolConfig::reap_interval` (and `Builder::reap_interval`) opts into a background
thread that prunes expired idle resources on a fixed cadence:

```rust
use std::time::Duration;
use pool_mod::{Manager, Pool};
# use std::convert::Infallible;
# struct M;
# impl Manager for M {
#   type Resource = (); type Error = Infallible;
#   fn create(&self) -> Result<(), Infallible> { Ok(()) }
#   fn recycle(&self, _r: &mut ()) -> Result<(), Infallible> { Ok(()) }
# }
let pool = Pool::builder(M)
    .max_size(16)
    .idle_timeout(Some(Duration::from_secs(300)))
    .reap_interval(Some(Duration::from_secs(30)))   // prune every 30s
    .build()
    .expect("configuration is valid");
# let _ = pool;
```

Design notes:

- **Off by default.** `reap_interval` defaults to `None`; behavior and overhead are
  unchanged unless you opt in. With it `None`, expiry stays lazy (applied on
  checkout), which is a perfectly valid mode and still the default.
- **Prune-only.** The reaper never calls `create` — it only drops expired idle
  resources (outside the pool lock). On-demand growth refills as needed.
- **Clean shutdown.** The reaper holds a *weak* reference to the pool, so it never
  keeps the pool alive. It stops promptly when the pool is closed or its last
  handle is dropped, via a shared shutdown signal. If the OS refuses a thread,
  the pool falls back to lazy expiry rather than failing to build.
- **Shared expiry logic.** Checkout and the reaper now apply `idle_timeout` /
  `max_lifetime` through one helper, so eager and lazy expiry agree exactly.

`reap_interval` is an additive field on the public `PoolConfig`. Code that builds
a config with `..PoolConfig::default()` is unaffected.

## Pre-1.0 audit

The full audit checklist from `.dev/ROADMAP.md`, with findings.

### Feature completeness
- Every advertised feature is implemented: generic resource management, min/max
  sizing, blocking acquisition with timeouts, non-blocking `try_get`,
  validation-on-borrow, idle/lifetime expiry (lazy and eager), and a status
  snapshot. **No outstanding roadmap features.**

### Code cleanliness
- No `unwrap` / `expect` / `todo!` / `unimplemented!` / `unreachable!` / `dbg!` /
  `print*` anywhere in shipping code (verified by scan). The crate-level lints deny
  all of them.
- The single `#[allow]` in the source is `clippy::unwrap_used` / `expect_used` on
  the pool's test module, justified inline — REPS permits `unwrap` in tests.
- No dead or commented-out code; no `TODO` / `FIXME`.

### Error hardening
- One `unsafe` block in the crate — `ManuallyDrop::take` in the guard's `Drop` —
  carries a `// SAFETY:` proof and is exercised by the check-in tests.
- `Error<E>` is `#[non_exhaustive]`. Every variant (`Backend`, `Timeout`,
  `Closed`, `InvalidConfig`) is documented with caller guidance and covered by a
  test. `Display` is actionable; `source()` exposes the backend cause.
- The pool lock guards only counters and a queue, never user code; the lock helper
  recovers from poisoning rather than propagating an unrelated panic as a
  permanent outage.

### Supply chain
- **Zero runtime dependencies.** `cargo tree --edges normal` is just `pool-mod`.
- `cargo audit` — no advisories across the 80-crate dev-dependency tree.
- `cargo deny check` — advisories, bans, licenses, and sources all pass against
  the committed `deny.toml` (permissive licenses only; wildcards and unknown
  registries denied; yanked crates denied).

### Documentation
- Every public item has a rustdoc example; `cargo doc` is clean under
  `RUSTDOCFLAGS=-D warnings`. `docs/API.md` documents the full surface, including
  the new `reap_interval`.

### Tests
- 21 unit + 10 lifecycle + 3 concurrency + 4 property + 1 smoke integration tests,
  plus 17 doctests. Property tests cover the sizing and close invariants; the
  reaper has both deterministic unit coverage and timed integration coverage.

### Cross-platform
- Green on Linux, macOS, and Windows across stable and MSRV 1.75:
  `fmt --check`, `clippy --all-targets --all-features -D warnings`,
  `test --all-features`, and `doc` with `-D warnings`.

### Deferred to 1.x
- Tracked Criterion baselines with a regression gate (benchmarks exist; baseline
  storage and the CI gate are a 1.0 task).
- Wiring `cargo audit` / `cargo deny` into the CI workflow (both pass locally
  against `deny.toml`; the workflow itself is unchanged in this release).

No blocking findings. The API is considered frozen for 1.0.

## Breaking changes

**None to existing call sites.** `reap_interval` is an additive `PoolConfig`
field; the `..PoolConfig::default()` construction pattern and every existing
method are unchanged.

## Verification

Run on Windows x86_64 (Rust stable 1.95.0 and 1.75.0) and on Linux via WSL2
Ubuntu (Rust stable 1.95.0); these mirror the CI matrix.

```bash
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo +1.75 build --all-targets --all-features
cargo +1.75 test --all-features
cargo audit
cargo deny check
```

All green. Counts at this tag: 21 unit + 18 integration (10 lifecycle + 3 pool +
4 property + 1 smoke) + 17 doctests.

## What's next

- **1.0.0 — Stable release.** Final API freeze, tracked benchmark baselines, and
  the 1.0 tag.

## Installation

```toml
[dependencies]
pool-mod = "0.9"
```

MSRV: Rust 1.75. Edition 2021.

## Documentation

- [README]https://github.com/jamesgober/pool-mod/blob/main/README.md
- [API Reference]https://github.com/jamesgober/pool-mod/blob/main/docs/API.md
- [CHANGELOG]https://github.com/jamesgober/pool-mod/blob/main/CHANGELOG.md

---

**Full diff:** [`v0.5.0...v0.9.0`](https://github.com/jamesgober/pool-mod/compare/v0.5.0...v0.9.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/pool-mod/blob/main/CHANGELOG.md#090---2026-05-27).