Advanced Rust Design Patterns

Resource Management Patterns

Systems applications manage scarce external resources—database connections, network sockets, worker threads, file descriptors, and memory buffers—whose creation is expensive and whose concurrent count must be bounded. Resource management patterns in Rust combine Arc, Mutex, and Drop guards to implement self-returning connection/buffer pools and two-phase graceful shutdown.

Self-Returning Pooled Lease (`PooledLease<'a, T>` / `Arc<PoolInner<T>>`):

  Caller calls `pool.acquire()` ---> Receives `PooledResource<T>` (Implements `DerefMut<Target = T>`)
                                            |
                                     Caller uses `lease.query(...)`
                                            |
                                     `lease` goes out of scope (or early `?` / panic!)
                                            |
                                            v
                                     `Drop::drop` automatically returns `T` back to the Pool's idle queue!

Implementing a Self-Returning Resource Pool with Drop and Option::take

How does a PooledLease<T> move the owned resource T back into the pool's VecDeque<T> inside fn drop(&mut self), given that drop only receives &mut self (not self by value)?

Store the resource inside PooledLease<T> as resource: Option<T> (or ManuallyDrop<T>):

  • In Deref and DerefMut, unwrap self.resource.as_ref().unwrap() (which is guaranteed to be Some while the lease is alive).
  • In Drop::drop(&mut self), call self.resource.take() to extract the owned T in $O(1)$ time and push it back into the pool's idle queue!
use std::collections::VecDeque;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex};

pub struct Pool<T> {
    idle: Arc<Mutex<VecDeque<T>>>,
}

impl<T> Clone for Pool<T> {
    fn clone(&self) -> Self {
        Self {
            idle: Arc::clone(&self.idle),
        }
    }
}

pub struct PooledLease<T> {
    // Stored as `Option<T>` so `Drop::drop(&mut self)` can `.take()` ownership
    // and return `T` to the idle queue!
    resource: Option<T>,
    pool_idle: Arc<Mutex<VecDeque<T>>>,
}

impl<T> Pool<T> {
    pub fn new(initial_items: impl IntoIterator<Item = T>) -> Self {
        Self {
            idle: Arc::new(Mutex::new(initial_items.into_iter().collect())),
        }
    }

    pub fn try_acquire(&self) -> Option<PooledLease<T>> {
        let item = self.idle.lock().unwrap().pop_front()?;
        Some(PooledLease {
            resource: Some(item),
            pool_idle: Arc::clone(&self.idle),
        })
    }

    pub fn available_count(&self) -> usize {
        self.idle.lock().unwrap().len()
    }
}

impl<T> Deref for PooledLease<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        self.resource.as_ref().expect("resource present until drop")
    }
}

impl<T> DerefMut for PooledLease<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.resource.as_mut().expect("resource present until drop")
    }
}

impl<T> Drop for PooledLease<T> {
    fn drop(&mut self) {
        if let Some(mut item) = self.resource.take() {
            // Ignore poison errors on drop so unwinding never double-panics:
            if let Ok(mut queue) = self.pool_idle.lock() {
                let _ = &mut item;
                queue.push_back(item);
            }
        }
    }
}

fn main() {
    let pool = Pool::new([String::from("conn_1"), String::from("conn_2")]);
    assert_eq!(pool.available_count(), 2);

    {
        let lease1 = pool.try_acquire().unwrap();
        let lease2 = pool.try_acquire().unwrap();
        assert_eq!(pool.available_count(), 0);
        assert!(pool.try_acquire().is_none()); // Pool exhausted!

        println!("Leased: '{}' and '{}'", *lease1, *lease2);
    } // Both `lease1` and `lease2` drop here and return their connections to `pool`!

    assert_eq!(pool.available_count(), 2);
    println!("Available connections after scope exit: {}", pool.available_count());
}
Leased: 'conn_1' and 'conn_2'
Available connections after scope exit: 2

Two-Phase Graceful Shutdown (close vs Drop)

Because Drop::drop(&mut self) cannot return a Result<(), Error> and cannot .await asynchronous network flushes, resources that perform fallible or asynchronous shutdown (such as a BufWriter::into_inner, a database connection, or a worker thread pool) should provide both:

  1. An explicit consuming .close(self) -> Result<(), Error> method for normal shutdown where the caller wants to handle flush errors or join threads cleanly.
  2. A best-effort Drop implementation as a safety net in case the resource is dropped early due to ? or panic unwinding.