Async-safe min/max sizing, idle timeouts, max-lifetime enforcement, validation-on-borrow, health-check callbacks. Runtime-agnostic.
pool-mod is a generic pool for any resource that is expensive to build — database connections, HTTP clients, worker handles, parsers, large buffers. You describe the resource's lifecycle once by implementing a single trait; the pool takes care of sizing, blocking acquisition with timeouts, validation-on-borrow, and idle / lifetime expiry.
The pool is runtime-agnostic: it pulls in zero dependencies and carries no async runtime. The borrow guard returned on checkout is Send, so it works in synchronous code directly and in async code by acquiring on a blocking-friendly executor thread (such as tokio::task::spawn_blocking).
- Generic over any resource — pool connections, clients, threads, buffers, or anything else through one
Managertrait. - Min / max sizing —
min_idleresources are created up front and kept ready; the pool grows on demand up tomax_sizeand never beyond it. - Blocking acquisition with timeouts —
getwaits up to a configuredcreate_timeout;get_timeoutoverrides it per call, andtry_getnever blocks. - Validation-on-borrow — an optional
validatehook (a health-check callback) runs on checkout; a resource that fails is discarded and replaced transparently. - Idle & max-lifetime expiry — stale resources are dropped and replaced, bounded by
idle_timeoutandmax_lifetime. Applied lazily on checkout by default, or eagerly by an opt-in background reaper (reap_interval). - RAII return — the
Pooledguard recycles and returns its resource automatically on drop. There is noreleaseto forget and no way to leak a resource. - Thread-safe and cheap to share —
PoolisSend + Syncand clones into another handle onto the same pool. - Runtime-agnostic, zero-dependency — no async runtime, no third-party crates.
no_std-aware — the crate root compiles withoutstd; the pool itself is behind the defaultstdfeature.
[dependencies]
pool-mod = "1.0"MSRV is Rust 1.75. The crate is edition 2021 and builds on Linux, macOS, and Windows.
Implement Manager for your resource, then build a pool and borrow from it:
use pool_mod::{Manager, Pool};
use std::convert::Infallible;
// Describe how to create, reset, and (optionally) validate the resource.
struct Buffers {
capacity: usize,
}
impl Manager for Buffers {
type Resource = Vec<u8>;
type Error = Infallible;
fn create(&self) -> Result<Vec<u8>, Infallible> {
Ok(Vec::with_capacity(self.capacity))
}
fn recycle(&self, buf: &mut Vec<u8>) -> Result<(), Infallible> {
buf.clear(); // reuse the allocation, drop the contents
Ok(())
}
}
let pool = Pool::builder(Buffers { capacity: 4096 })
.max_size(16)
.min_idle(4)
.build()
.expect("configuration is valid");
// Borrow a buffer; it returns to the pool when `buf` is dropped.
let mut buf = pool.get().expect("a buffer is available");
buf.extend_from_slice(b"payload");
assert_eq!(buf.len(), 7);A pool owns up to max_size resources. Each checkout:
- Reuses an idle resource if one is available — after applying
max_lifetime,idle_timeout, and thevalidatehealth check. A resource that is too old, too stale, or invalid is dropped and the pool moves on. - Creates a new resource if none is idle and the pool has not reached
max_size. - Waits if the pool is saturated, until a resource is returned or the timeout elapses.
When a Pooled guard is dropped, its resource is passed to recycle and returned to the idle set. If recycling fails — or the pool has been closed — the resource is dropped instead and its slot is freed for a replacement.
Resource construction, validation, and recycling all run without the pool's internal lock held, so a slow create (opening a socket, say) never blocks other threads from returning resources. The lock guards only a small queue and a couple of counters.
Configure through the Builder, or build a PoolConfig directly (for example, from a settings file).
| Setting | Default | Meaning |
|---|---|---|
max_size |
10 |
Upper bound on resources owned at once (idle + checked out). |
min_idle |
0 |
Resources created up front and kept ready. Must be ≤ max_size. |
create_timeout |
30s |
How long get waits when saturated. None waits indefinitely. |
idle_timeout |
None |
Replace a resource unused for this long, checked on next borrow. |
max_lifetime |
None |
Replace a resource older than this, checked on next borrow. |
reap_interval |
None |
Background prune cadence. None applies expiry lazily on borrow. |
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(32)
.min_idle(4)
.create_timeout(Some(Duration::from_secs(5)))
.idle_timeout(Some(Duration::from_secs(600)))
.max_lifetime(Some(Duration::from_secs(3600)))
.build()
.expect("configuration is valid");
# let _ = pool;The pool has no async dependency and get blocks the calling thread. In an async
context, acquire on a blocking-friendly executor thread; the returned guard is
Send, so it may be held across .await points:
let pool = pool.clone();
let mut conn = tokio::task::spawn_blocking(move || pool.get()).await??;
// `conn` is usable across awaits here.A native non-blocking async acquisition API is on the roadmap (see below).
For the complete reference — every public item, its parameters, return values, error semantics, and runnable examples — see docs/API.md.
Manager— the trait you implement:create,recycle, and the optionalvalidatehealth check.Pool—builder/new,get/get_timeout/try_get,status,close,is_closed.Builder— fluent configuration.PoolConfig— limits and lifecycle policy.Pooled— the RAII guard, deref-coercing to your resource.Status—size,idle,in_use,max_size.Error—Backend,Timeout,Closed,InvalidConfig.
The hot path is borrow-and-return against an available resource. The pool guards only a small queue and a few counters under a mutex; resource construction, validation, and recycling all run outside the lock, and an uncontended check-in/return touches the condition variable only when a thread is actually waiting.
Latest local Criterion means (cargo bench, Windows x86_64, Rust stable,
single-threaded, trivial resource):
| Benchmark | Time/op |
|---|---|
get + return (reuse) |
~98 ns |
try_get + return |
~97 ns |
status snapshot |
~8.5 ns |
These measure the pool machinery itself with a no-op resource; in real use the
checkout cost is dominated by the work the resource does (a query, a request),
which the pool exists to amortize. The steady-state checkout/return path performs
no heap allocation. Numbers vary by CPU and platform — run cargo bench on your
target for figures that matter to you.
- Linux (x86_64, aarch64)
- macOS (x86_64, Apple Silicon)
- Windows (x86_64)
CI runs formatting, lints, tests, and rustdoc with -D warnings on all three
operating systems, across both the stable toolchain and the MSRV (1.75).
The suite covers every lifecycle path with unit tests, an eight-thread
concurrency test, proptest properties for the pool's invariants (the max_size
ceiling, the size == idle + in_use identity, reuse, and close semantics), and
doctests on every public item.
# Full suite (unit, integration, property, and doctests)
cargo test --all-features
# Microbenchmarks for the acquire/return hot path
cargo bench
# Lints and formatting, as enforced in CI
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check1.0.0 is the API freeze. Every public item is stable under semantic
versioning: nothing is removed, renamed, or changed in a breaking way within the
1.x series. New functionality is additive, and Error is #[non_exhaustive]
so it can grow without breaking matches. The MSRV (Rust 1.75) will not rise in a
patch release. See docs/API.md for the full
promise and CHANGELOG.md for the history.
- REPS governs every decision. See REPS.md.
- MSRV: Rust 1.75.
- Edition: 2021.
- Cross-platform: Linux, macOS, Windows.
Dual-licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.