deadpool
repository·main·Indexed 23 days ago
https://github.com/deadpool-rs/deadpoolAn asynchronous connection pooler for Rust. Version 0.13.0 provides a dead simple async pool with various implementations including deadpool-postgres for PostgreSQL, deadpool-diesel for database backends (SQLite, Postgres, MySQL), deadpool-lapin for RabbitMQ/AMQP, deadpool-libsql for libsql, and deadpool-memcached for Memcached.
What's inside deadpool
- Deadpool is a simple asynchronous pool for managing connections and objects of any type. It provides two primary pool implementations depending on whether you want the pool to handle object creation or if you want to provide the objects yourself.
Key features and design principles of Deadpool
mainDeadpool is designed with several core principles:
- Executor Agnostic: It is compatible with any executor because it uses the
Droptrait to return objects to the pool and checks health upon retrieval rather than using background tasks. - Runtime Behavior: Creating a pool never fails. Errors only occur when calling
Pool::get(). This ensures that temporary database unavailability doesn't cause application crashes during startup. - Performance: Uses minimal locking; a single Mutex is used for returning objects, and a Semaphore is used for retrieval to minimize contention.
- Extensibility: Supports
post_create,pre_recycle, andpost_recyclehooks for custom logic. - Observability: Provides
Metricsfor objects and astatus()method for pool insights. - Dynamic Resizing: The pool can be grown or shrunk at runtime.
- Executor Agnostic: It is compatible with any executor because it uses the
Use Deadpool for SQLite
mainDeadpool for SQLite provides an asynchronous connection pool for
rusqlite. Becauserusqliteis a synchronous library, this crate provides a wrapper that ensures connections are used correctly inside a separate thread via theinteract()method. This allows you to perform blocking SQLite operations within an async context safely.use deadpool_sqlite::{Config, Runtime}; #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let mut cfg = Config::new("db.sqlite3"); let pool = cfg.create_pool(Runtime::Tokio1).unwrap(); let conn = pool.get().await.unwrap(); let result: i64 = conn .interact(|conn| { let mut stmt = conn.prepare("SELECT 1")?; let mut rows = stmt.query([])?; let row = rows.next()?.unwrap(); row.get(0) }) .await??; assert_eq!(result, 1); Ok(()) }What is deadpool-sync and when should I use it?
maindeadpool-syncprovides helpers for creating connection pools for objects that do not support asynchronous operations and must be executed within a thread.Important Usage Note: This crate is primarily intended for library authors developing
deadpool-*adapter crates. If you are building a binary application or using other libraries, you should generally not usedeadpool-syncdirectly; instead, use the re-exports provided by the specificdeadpool-*crate you are using.What is the Deadpool runtime abstraction?
mainThe
deadpool-runtimecrate provides aRuntimeenum designed to target multiple asynchronous runtimes. It is a lightweight abstraction that avoids boxed futures and only implements the specific functionality required by thedeadpool-*ecosystem crates.Note for users: This crate is primarily intended for developers creating new
deadpool-*adapter crates. If you are building a standard library or a binary application, you should generally not use this crate directly; instead, use the re-exports provided by the specificdeadpool-*crate you are using (e.g.,deadpool-postgres).How managed pools (connection pools) work
mainA managed pool is the standard way to use Deadpool for connection pooling. It requires implementing the
deadpool::managed::Managertrait for your resource type. The manager defines how tocreatenew objects and how torecycleexisting ones (e.g., checking if a connection is still alive).Key components of the
Managertrait:type Type: The type of object being pooled.type Error: The error type returned by the manager.async fn create(&self) -> Result<Self::Type, Self::Error>: Logic to instantiate a new resource.async fn recycle(&self, object: &mut Self::Type, metrics: &deadpool::managed::Metrics) -> deadpool::managed::RecycleResult<Self::Error>: Logic to validate a resource before it is reused.
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<Computer, Error> { Ok(Computer {}) } async fn recycle(&self, _: &mut Computer, _: &managed::Metrics) -> managed::RecycleResult<Error> { Ok(()) } } type Pool = managed::Pool<Manager>; #[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); }How to handle connection recycling methods
mainDeadpool uses a recycling method to check if a connection is still valid before returning it from the pool.
RecyclingMethod::Fast(Default): Relies ontokio_postgres::Client::is_closed. In rare cases of unreliable networks, this might return a connection that has actually disconnected but hasn't been noticed by the client yet.RecyclingMethod::Verified: Performs a test query to verify the connection is alive. This is slower but more reliable.
You can set this via
ManagerConfig::recycling_methodor, if using theconfigcrate, via the environment variablePG__MANAGER__RECYCLING_METHOD=Verified.Configure a Memcached pool using deadpool_memcached::Config
mainIn addition to the manual builder pattern, you can usedeadpool_memcached::Configto build a pool. This approach supports deserialization from configuration formats viaserde.How unmanaged pools work
mainAn unmanaged pool is used when you want to pool objects without implementing a
Managertrait. It is slightly faster than a managed pool because it skips thecreateandrecyclelifecycle steps, leaving object management entirely to the user. You initialize an unmanaged pool by providing a collection of pre-existing objects.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); }Compare managed and unmanaged pools
mainDeadpool offers two distinct pool types:
Managed Pool (
deadpool::managed::Pool)- Behavior: Automatically creates and recycles objects as needed.
- Use Case: Ideal for database connection pools where the pool manages the lifecycle of connections.
- Requirement: Must enable the
managedfeature inCargo.toml.
Unmanaged Pool (
deadpool::unmanaged::Pool)- Behavior: Objects must be created by the user and added to the pool manually, or the pool can be initialized from an existing collection of objects.
- Use Case: Useful when you already have a set of objects you want to distribute or manage.
- Requirement: Must enable the
unmanagedfeature inCargo.toml.
Perform SQLite operations with interact()
mainTo execute SQLite queries using a connection from the pool, use the
interact()method. This method takes a closure containing your synchronousrusqlitecode and executes it on a separate thread to avoid blocking the async runtime. The result of the closure is returned when you.awaittheinteract()call.let result: i64 = conn .interact(|conn| { let mut stmt = conn.prepare("SELECT 1")?; let mut rows = stmt.query([])?; let row = rows.next()?.unwrap(); row.get(0) }) .await??;Configure Deadpool for Redis
mainTo use
deadpool-redis, you can create a connection pool using aConfigobject. The configuration can be initialized from a single URL or via environment variables. Once the pool is created, you can retrieve connections using.get().awaitand execute commands using the re-exportedrediscrate API.use std::env; use deadpool_redis::{redis::{cmd, FromRedisValue}, Config, Runtime}; #[tokio::main] async fn main() { let mut cfg = Config::from_url(env::var("REDIS__URL").unwrap()); let pool = cfg.create_pool(Some(Runtime::Tokio1)).unwrap(); { let mut conn = pool.get().await.unwrap(); cmd("SET") .arg(&["deadpool/test_key", "42"]) .query_async::<()>(&mut conn) .await.unwrap(); } { let mut conn = pool.get().await.unwrap(); let value: String = cmd("GET") .arg(&["deadpool/test_key"]) .query_async(&mut conn) .await.unwrap(); assert_eq!(value, "42".to_string()); } }