Notify

The tokio docs suggest that you should use an async mutex to guard resources like a database connection. I still disagree with this, although it is reasonable if you cannot afford any kind of refactor. That said, I would still implore you to try and get a proper connection pool resource.

Writing your own connection pool is quite easy, if you need one. Simply, you might think of a pool as a VecDeque<T>. You can pop_front from the pool to get access to a connection, and then use push_back to re-queue the connection. To have this between threads, you obviously need a Mutex to protect this vec. However, if there are no connections ready in the pool, how do you wait for one?

One less elegant way is to use a semaphore, Make sure the semaphore has the same number of permits as connections. Before you lock the mutex, acquire a permit for it. This works fine, and I would not mind if I saw this in production. That said, I think this is a great opportunity to show off one of my favourite synchronisation primitives in tokio.

If all you need is a notification, how about a primitive called Notify?

#![allow(unused)]
fn main() {
struct Pool<T> {
    conns: Mutex<VecDeque<T>>,
    notifications: Notify,
}

impl<T> Pool<T> {
    pub async fn acquire(&self) -> Conn<'_, T> {
        // register our interest
        self.notifications.notified().await;

        // a value is now ready
        let mut conns = self.pool.lock().unwrap();

        let conn = conns.pop_front().expect("a conn should exist");

        // more connections ready, store a notification
        if !conns.is_empty() {
            self.notifications.notify_one();
        }

        Conn {
            pool: self,
            conn: Some(conn)
        }
    }

    pub fn insert_conn(&self, conn: T) {
        // insert the conn into the pool
        let mut conns = self.pool.lock().unwrap();
        conns.push_back(conn);

        // notify the next task that a connection is now ready
        self.pool.notifications.notify_one();
    }
}

struct Conn<'a, T> {
    pool: &'a Pool<T>,
    // option needed to return it to the pool on drop later
    // it will always be Some
    conn: Option<T>,
}

// return the conn on drop.
impl<T> Drop for Conn<'_, T> {
    fn drop(&mut self) {
        let conn = self.conn.take().unwrap();
        self.pool.insert_conn(conn);
    }
}
}