r2d2

repository·master·Indexed 23 days ago

https://github.com/sfackler/r2d2

A generic connection pool for Rust designed to manage database connections efficiently by reusing them and preventing resource exhaustion. It is agnostic to connection types, relying on the ManageConnection trait for connection creation and health checks. r2d2 supports various backends through adaptor crates such as r2d2-postgres, r2d2-sqlite, and r2d2-mysql.

Tokens
4K
Snippets
7
Records
19
Agent score
81%

What's inside r2d2

  1. What is r2d2 and how does it work?

    master

    r2d2 is a generic connection pool for Rust. It prevents the inefficiency and resource exhaustion caused by opening a new database connection for every request by maintaining a set of open connections and handing them out for repeated use.

    Because r2d2 is agnostic to the connection type, it relies on the ManageConnection trait. Implementors of this trait provide the specific logic required to create new connections and check their health. Most common databases have existing adaptor crates (e.g., r2d2-postgres, r2d2-sqlite) that implement this trait for you.

  2. How to use r2d2 to manage a connection pool

    master

    To use r2d2, you typically follow these steps:

    1. Create a connection manager (an implementation of ManageConnection).
    2. Use r2d2::Pool::builder() to configure the pool (e.g., setting max_size).
    3. Build the pool using .build(manager).
    4. Clone the pool handle for different threads or tasks.
    5. Retrieve a connection using .get(). The connection is automatically returned to the pool when it falls out of scope.
    use std::thread;
    
    extern crate r2d2;
    extern crate r2d2_foodb;
    
    fn main() {
        let manager = r2d2_foodb::FooConnectionManager::new("localhost:1234");
        let pool = r2d2::Pool::builder()
            .max_size(15)
            .build(manager)
            .unwrap();
    
        for _ in 0..20 {
            let pool = pool.clone();
            thread::spawn(move || {
                let conn = pool.get().unwrap();
                // use the connection
                // it will be returned to the pool when it falls out of scope.
            })
        }
    }
  3. Configure a connection pool using Builder

    master

    The Builder<M> struct is used to configure and initialize a connection pool for a given connection manager M. You can customize pool size, timeouts, error handling, and connection lifecycle settings.

    To create a pool, use Builder::new() to get a builder with default settings, apply your desired configurations using the builder pattern, and finally call .build(manager) to initialize the pool. Note that .build() will block until the pool has established its configured minimum number of connections.

  4. Create and use a connection pool with `Pool`

    master

    A Pool manages a set of open connections to a database. You can create a pool using Pool::new(manager) for default settings or Pool::builder() for custom configuration.

    To use a connection, call .get() or .get_timeout(duration). This returns a PooledConnection, which acts as a smart pointer to the underlying connection. When the PooledConnection is dropped, the connection is automatically returned to the pool.

    Note that Pool implements Clone, allowing you to share the same pool across multiple threads.

    use std::thread;
    
    extern crate r2d2;
    extern crate r2d2_foodb;
    
    fn main() {
        let manager = r2d2_foodb::FooConnectionManager::new("localhost:1234");
        let pool = r2d2::Pool::builder()
            .max_size(15)
            .build(manager)
            .unwrap();
    
        for _ in 0..20 {
            let pool = pool.clone();
            thread::spawn(move || {
                let conn = pool.get().unwrap();
                // use the connection
                // it will be returned to the pool when it falls out of scope.
            })
        }
    }
  5. Available r2d2 adaptors for different backends

    master

    r2d2 supports many databases through specialized adaptor crates. Below is a list of common backends and their corresponding adaptors:

    BackendAdaptor Crate
    rust-postgresr2d2-postgres
    redis-rsuse r2d2 feature of redis-rs
    rust-memcacher2d2-memcache
    rust-mysql-simpler2d2-mysql
    rusqliter2d2-sqlite
    rsfbclientr2d2-firebird
    rusted-cypherr2d2-cypher
    dieseldiesel::r2d2
    couchdbr2d2-couchdb
    mongodb (archived)r2d2-mongodb (Note: official driver handles pooling internally)
    odbcr2d2-odbc
    jfsr2d2-jfs
    oracler2d2-oracle
    ldap3r2d2-ldap
    duckdb-rsuse r2d2 feature of duckdb-rs
  6. Configure error, event, and connection handlers

    master

    Use these methods on Builder<M> to inject custom logic for pool operations:

    • error_handler(error_handler: Box<dyn HandleError<M::Error>>): Sets the handler for errors reported in the pool. Defaults to LoggingErrorHandler.
    • event_handler(event_handler: Box<dyn HandleEvent>): Sets the handler for events reported by the pool. Defaults to NopEventHandler.
    • connection_customizer(connection_customizer: Box<dyn CustomizeConnection<M::Connection, M::Error>>): Sets the customizer used to configure connections when they are created. Defaults to NopConnectionCustomizer.
  7. Inspect the state of a `Pool`

    master

    Use the state() method on a Pool to get a snapshot of the current pool usage. This returns a State struct containing:

    • connections: The total number of connections currently managed by the pool.
    • idle_connections: The number of connections currently sitting idle in the pool.
    pub struct State {
        pub connections: u32,
        pub idle_connections: u32,
    }
  8. Implement the `ManageConnection` trait for a database adapter

    master

    To use r2d2 with a specific database, you must implement the ManageConnection trait. This trait provides the logic for creating and validating connections.

    Required associated types:

    • type Connection: The type of the connection being managed (e.g., a database client handle).
    • type Error: The error type returned by the connection operations, which must implement std::error::Error.

    Required methods:

    • connect(&self) -> Result<Self::Connection, Self::Error>: Attempts to establish a new connection.
    • is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error>: Performs a health check (e.g., running SELECT 1) to ensure the connection is still active.
    • has_broken(&self, conn: &mut Self::Connection) -> bool: A fast, non-blocking check to see if the connection is unusable (e.g., checking if a TCP socket is closed). If this returns true, the connection is discarded.
    pub trait ManageConnection: Send + Sync + 'static {
        type Connection: Send + 'static;
        type Error: error::Error + 'static;
    
        fn connect(&self) -> Result<Self::Connection, Self::Error>;
        fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error>;
        fn has_broken(&self, conn: &mut Self::Connection) -> bool;
    }
  9. Use Extensions to attach custom data to pooled connections

    master

    The Extensions struct is a type-safe map used to associate arbitrary data with pooled connections. It allows you to cache data (such as prepared statements) alongside a connection by using the type of the data as the lookup key.

    To use it, you can insert values of any type T that implements 'static + Sync + Send. You can then retrieve these values using get for shared references or get_mut for mutable references.

  10. Configure connection pool size and idle limits

    master

    Use these methods on Builder<M> to control the number of connections in the pool:

    • max_size(max_size: u32): Sets the maximum number of connections managed by the pool. Defaults to 10. Panics if max_size is 0.
    • min_idle(min_idle: Option<u32>): Sets the minimum idle connection count maintained by the pool. If set, the pool tries to maintain at least this many idle connections. Defaults to None (which is equivalent to max_size).

    Note: Calling .build() or .build_unchecked() will panic if min_idle is greater than max_size.

  11. Handle connection errors with `HandleError`

    master

    You can provide a custom error handler by implementing the HandleError<E> trait. This is useful for logging or monitoring connection failures.

    Provided implementations:

    • NopErrorHandler: Does nothing.
    • LoggingErrorHandler: Logs errors at the error! level.
    pub trait HandleError<E>: fmt::Debug + Send + Sync + 'static {
        fn handle_error(&self, error: E);
    }
    
    pub struct NopErrorHandler;
    pub struct LoggingErrorHandler;
  12. Configure connection lifecycle and timeouts

    master

    Use these methods on Builder<M> to manage how long connections live and how long they can stay idle:

    • max_lifetime(max_lifetime: Option<Duration>): Sets the maximum lifetime of connections. Connections are closed after existing for at most 30 seconds beyond this duration. If a connection reaches its limit while checked out, it is closed when returned to the pool. Defaults to 30 minutes. Panics if the duration is zero.
    • idle_timeout(idle_timeout: Option<Duration>): Sets the idle timeout. Connections are closed after sitting idle for at most 30 seconds beyond this duration. Defaults to 10 minutes. Panics if the duration is zero.
    • connection_timeout(connection_timeout: Duration): Sets how long calls to Pool::get will wait for a connection to become available before returning an error. Defaults to 30 seconds. Panics if the duration is zero.