How to use Diesel in an async context

Hello,

I'm new to Rust and need some help
I'm trying to implement a web server which gets data from a database. As a web server I use Axum (which uses Tokio as its async runtime) and for getting data from the database I want to use Diesel.
As Diesel is blocking I researched how to use it in the async context of Axum. I found out about tokio::task::spawn_blocking and the crate deadpool (as well as deadpool_diesel). By using tokio::task::spawn_blocking I already got a small performance increase (in comparison to simply using Diesel in my Axum handler), but using deadpool_diesel I got an even bigger performance boost. Now I'd like to combine both solutions but run into the following issue:

#[tokio::main]
async fn main() {
    let manager = Manager::new("postgres://db_user:db_password@localhost:5432/db", Runtime::Tokio1);
    let pool = Pool::builder(manager)
        .max_size(10)
        .build()
        .unwrap();

    let router = Router::new()
        .route("/", get(get_posts))
        .layer(Extension(pool));

    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(router.into_make_service())
        .await
        .unwrap();
}

#[debug_handler]
async fn get_posts(
    Extension(pool): Extension<Pool>
) -> String {
    // Import
    use self::schema::posts;

    let conn_obj = pool.get().await.unwrap();

    let posts = conn_obj.interact(|conn| {
        let posts = task::spawn_blocking(move || {
            let posts1: Vec<Post> = posts::dsl::posts
                .limit(8)
                .load::<Post>(conn)
                .expect("Error loading posts");
            posts1
        });

        posts
    }).await.expect("error 1").await.expect("error 2");

    // Return
    serde_json::to_string(&posts).expect("error 3")
}

I get the following error:

error[E0521]: borrowed data escapes outside of closure
  --> diesel-deadpool-spawn-blocking/src/main.rs:45:21
   |
44 |       let posts = conn_obj.interact(|conn| {
   |                                      ----
   |                                      |
   |                                      `conn` is a reference that is only valid in the closure body
   |                                      has type `&'1 mut PgConnection`
45 |           let posts = task::spawn_blocking(move || {
   |  _____________________^
46 | |             let posts1: Vec<Post> = posts::dsl::posts
47 | |                 .limit(8)
48 | |                 .load::<Post>(conn)
49 | |                 .expect("Error loading posts");
50 | |             posts1
51 | |         });
   | |          ^
   | |          |
   | |__________`conn` escapes the closure body here
   |            argument requires that `'1` must outlive `'static`

For more information about this error, try `rustc --explain E0521`.

I understand that the lifetime of the conn variable is the problem here, but I don't know how to get the actual PgConnection object in a different way from the connection pool.
Any help is very much appreciated. Thanks in advance :slight_smile:

The interact method is defined as follows:

pub async fn interact<F, R>(&self, f: F) -> Result<R, InteractError>
where
    F: FnOnce(&mut T) -> R + Send + 'static,
    R: Send + 'static,
{
    let arc = self.obj.clone();
    self.runtime
        .spawn_blocking(move || {
            let mut guard = arc.lock().unwrap();
            let conn = guard.as_mut().unwrap();
            f(conn)
        })
        .await
        .map_err(|e| match e {
            SpawnBlockingError::Panic(p) => InteractError::Panic(p),
        })
}

Since deadpool already calls spawn_blocking for you, you do not have to do it yourself.

2 Likes

Thank you so much @alice. You helped me a lot.
Is this (deadpool_diesel) also the way you would recommend to use Diesel in an async context or is there a more performant alternative you know of?

Deadpool is a good option. :+1:

1 Like