EsoxSolutions.ObjectPool (Rust Port)
Overview
High-performance, thread-safe object pool for Rust with automatic return of objects, async operations, performance metrics, flexible configuration, and advanced features like eviction, circuit breaker, and health monitoring.
Rust Port of the .NET Library - This is a faithful port of the .NET EsoxSolutions.ObjectPool v4.0.0 library, adapted to Rust's ownership model and idioms.
Features
- Thread-safe object pooling with lock-free concurrent operations using
crossbeam - Automatic return of objects via RAII (Drop trait) - no manual return needed
- Async support with
async/await, timeout, and cancellation viatokio - Queryable pools for finding objects matching predicates
- Dynamic pools with factory methods for on-demand object creation
- Health monitoring with real-time status and utilization metrics
- Prometheus metrics exportable format with labels
- Pool configuration for max size, active objects, validation, and timeouts
- Eviction / TTL support for automatic stale object removal
- Circuit Breaker pattern for protecting against cascading failures
- Pool warm-up for pre-population to eliminate cold-start latency
- Try methods* for non-throwing retrieval patterns
- High-performance with O(1) get/return operations
Installation
Add to your Cargo.toml:
[]
= "1"
= { = "1", = ["full"] }
If you prefer the shorter objectpool:: path in your code, rename the dependency:
[]
= { = "esox_objectpool", = "1" }
= { = "1", = ["full"] }
Quick Start
Basic Usage
use ;
With Configuration
use ;
use Duration;
Async Usage
use ObjectPool;
async
Queryable Pool
use ;
Dynamic Pool with Factory
use ;
Pool Warm-up
use ;
use ;
async
Eviction / TTL
Expired objects are filtered out lazily on each get_object() call. For strict TTL
enforcement, call evict_expired() periodically — for example from a background task:
use ;
use Duration;
use ObjectPool;
use Arc;
use Duration;
async
Circuit Breaker
The circuit breaker tracks consecutive pool failures. A success in the Closed state resets the failure counter to zero, so only an unbroken run of failures opens the circuit.
State transitions:
- Closed → Open: failure count reaches
thresholdwithout an intervening success - Open → Half-Open: after
timeoutelapses, one probe request is allowed through - Half-Open → Closed: 3 consecutive successes close the circuit
- Half-Open → Open: any single failure immediately re-opens the circuit
Note: Pool-empty events are recorded as failures. A legitimately busy pool that exhausts its objects will increment the failure counter. If the pool empties
thresholdtimes in a row (with no successful checkout in between), the circuit will open. Size your pool and threshold accordingly.
use ;
use Duration;
Health Monitoring
The health status now includes the circuit breaker state. A pool with an open circuit breaker is reported as unhealthy.
Metrics Export
use ObjectPool;
use HashMap;
Core Types
PooledObject<T>
A smart pointer that automatically returns objects to the pool when dropped (RAII pattern).
use ObjectPool;
unwrap() — Consumes the PooledObject and returns the inner value without returning it to the pool. The object's active slot is released (so max_active_objects and metrics are updated correctly), but the object itself is permanently removed from pool capacity. Use this only when you intentionally want to take ownership and discard the object.
use ObjectPool;
ObjectPool<T>
Fixed-size pool with pre-allocated objects.
Methods:
new(objects, config)- Create pool with initial objectsget_object()- Get object (non-blocking; returnsErr(PoolError::PoolEmpty)if empty, orErr(PoolError::CircuitBreakerOpen)/Err(PoolError::MaxActiveObjectsReached)for operational guards)try_get_object()- Try to get object; returnsOk(None)only for an empty pool — operational errors (CircuitBreakerOpen,MaxActiveObjectsReached) are still returned asErrget_object_async()- Async get with timeout; non-retryable errors (CircuitBreakerOpen,MaxActiveObjectsReached) are returned immediately without waiting for the timeouttry_get_object_async()- Thin async wrapper aroundtry_get_object(); performs a single non-blocking attempt (no polling loop, no timeout)available_count()- Number of objects currently available in the queueactive_count()- Number of objects currently checked outevict_expired()- Proactively remove expired objects; returns count evicteddrain()- Remove and return all available objects (for graceful shutdown)get_health_status()- Get health status (includes circuit breaker state)export_metrics()- Export metrics as HashMapexport_metrics_prometheus()- Export in Prometheus format
QueryableObjectPool<T>
Pool that supports finding objects by predicate.
Methods:
new(objects, config)- Create queryable poolget_object(predicate)- Find object matching predicate; O(n) worst casetry_get_object(predicate)- ReturnsOk(None)only when no match is found; propagates operational errors asErrget_object_async(predicate)- Async find with timeout; non-retryable errors fail fast
DynamicObjectPool<T>
Pool that creates objects on-demand using a factory function.
Methods:
new(factory, config)- Create with factory functionwith_initial(factory, objects, config)- Create with initial objects and factoryget_object()- Returns an available pooled object if one exists; calls the factory to create a new one only when the pool is empty and the active + available count is below capacity (enforced with aMutexto prevent concurrent over-creation).CircuitBreakerOpenandMaxActiveObjectsReachedare propagated immediately — the factory is not called.available_count()/active_count()- Observe pool stateevict_expired()- Proactively remove expired objectsdrain()- Remove and return all available objectswarmup(count)- Pre-populate pool (capped at pool capacity)warmup_async(count)- Async pre-population
PoolConfiguration<T>
Configuration options for pool behavior.
Builder Methods:
with_max_pool_size(size)- Set maximum pool capacitywith_max_active_objects(count)- Limit concurrent checkoutswith_validation(func)- Enable validation on returnwith_timeout(duration)- Set async operation timeoutwith_ttl(duration)- Set time-to-live for objectswith_idle_timeout(duration)- Set idle timeoutwith_warmup(size)- Set warm-up sizewith_circuit_breaker(threshold, timeout)- Enable circuit breaker
Performance Characteristics
| Operation | Complexity | Implementation |
|---|---|---|
get_object() |
O(1) | Lock-free ArrayQueue pop |
return_object() |
O(1) | Lock-free ArrayQueue push |
get_object(query) |
O(n) worst | Early exit optimization |
try_get_object() |
O(1) | Non-blocking variant with error propagation |
Thread Safety
All operations are thread-safe using:
crossbeam::queue::ArrayQueue- Lock-free MPMC queuedashmap::DashMap- Concurrent hash mapparking_lot::RwLock- Fast reader-writer locksstd::sync::atomic- Atomic operations for counters
Tested under high concurrency with zero data races (verified by Rust's ownership system).
Comparison with .NET Version
| Feature | .NET | Rust | Notes |
|---|---|---|---|
| Thread Safety | ConcurrentStack |
ArrayQueue + DashMap |
Lock-free in both |
| Auto Return | IDisposable |
Drop trait |
RAII pattern |
| Async | Task<T> |
async/await with tokio |
Native async |
| Generics | Full support | Full support | Rust has more constraints |
| Metrics | Built-in | Built-in | Prometheus format |
| DI Integration | ASP.NET Core | N/A | Rust doesn't have built-in DI |
| Performance | Fast | Faster | Zero-cost abstractions |
Examples
Run the examples:
# Basic usage
# Async operations
# Advanced features
Production Use
This library is suitable for:
- High-traffic applications with
tokio - Microservices architectures
- Database connection pooling
- Network client pooling
- Buffer pooling for zero-allocation patterns
- Resource-constrained environments
- Real-time systems requiring predictable latency
Known Limitations:
PooledObject::unwrap()permanently removes the object from pool capacity; the active slot is freed but the object is never returned. Avoid in hot paths where maintaining pool size matters.try_get_object_async()is a thin async wrapper around the synchronoustry_get_object()— it performs one non-blocking attempt and returns immediately. It does not poll or apply a timeout.- TTL/idle-timeout eviction is lazy (expired objects are filtered on checkout). For strict enforcement, call
evict_expired()periodically from a background task. QueryableObjectPool::get_object()drains the entire queue to scan for a matching object, then refills it. Concurrent callers serialise on the lock-free queue, making it unsuitable for high-throughput concurrent use.- When the return-to-pool queue push fails after retries (e.g. under extreme contention with a full queue), the object is discarded and the
queue_push_failuresmetric is incremented. This permanently reduces pool capacity. - No built-in integration with web frameworks (e.g. Actix, Axum, Rocket).
- Health checks and metrics endpoints must be manually wired up.
- Async operations are not cancelable via the pool API (use
tokio::time::timeoutexternally if needed).
Testing
Run tests:
Version History
1.0.0 (Current) - April 2026
- Initial crates.io release (Rust port of .NET EsoxSolutions.ObjectPool v4.0.0)
- Thread-safe pooling with lock-free operations (
crossbeam::ArrayQueue+dashmap) - Async support with tokio (
get_object_async,warmup_async) - Health monitoring with circuit-breaker state in
HealthStatus - Prometheus-format metrics export
- Proactive eviction via
evict_expired()+ lazy eviction on checkout - Graceful-shutdown
drain()on all pool types - Circuit breaker (Closed → Open → Half-Open) with consecutive-failure counting
- Pool warm-up / pre-population
- Queryable and dynamic pools with full observability (
available_count,active_count) DynamicObjectPool: TOCTOU-safe capacity enforcement; CB failure offset on successful creation- Requires Rust 1.88+
Architecture Differences
.NET → Rust Mappings
.NET → Rust
─────────────────────────────────────────────────────────
IDisposable → Drop trait
ConcurrentStack<T> → ArrayQueue<T> + DashMap
Task<T> → Future with tokio
lock { } → Mutex/RwLock (RAII locks)
Interlocked.Increment → AtomicUsize::fetch_add
IServiceCollection (DI) → Manual or crates like `shaku`
IHealthCheck → Custom trait
Nullable<T> → Option<T>
Exception → Result<T, E>
Key Rust Advantages
- Memory Safety - No data races, guaranteed by compiler
- Zero-cost Abstractions - Generics compiled away, no runtime overhead
- Ownership - Automatic resource management without GC
- Performance - Comparable to C, faster than .NET in many cases
- Concurrent - Fearless concurrency with ownership rules
Missing .NET Features (by design)
- ASP.NET Core Integration - Rust doesn't have equivalent built-in framework
- Health Checks Registration - No standard in Rust ecosystem
- OpenTelemetry - Available via separate crates (
opentelemetrycrate) - Dependency Injection - Not idiomatic in Rust (use factories/builders instead)
Contributing
Contributions welcome! Please ensure:
- All tests pass:
cargo test - Code follows Rust idioms:
cargo clippy - Formatted:
cargo fmt - New features include tests
- Documentation updated
License
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025 EsoxSolutions
Credits
Rust port of the .NET EsoxSolutions.ObjectPool library.
Original .NET library: Copyright © EsoxSolutions
For questions or issues: info@esoxsolutions.nl
Disclaimer
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.
This library is provided as-is, without any guarantees or warranty. The authors and contributors are not responsible for any damage, data loss, or other issues that may arise from using this software in any capacity. Users assume all risks associated with the use of this library.
By using this software, you acknowledge and agree that:
- You use this software at your own risk
- The authors are not liable for any damages, losses, or other consequences
- No warranty is provided regarding the software's performance, reliability, or suitability
- You are responsible for testing and validating the software for your specific use case
- This software may not be suitable for mission-critical or safety-critical applications
For production use, please conduct thorough testing and validation in your specific environment.