SQLx MySQL connection with lazy_static cannot be sent between threads safely

1.4k Views Asked by At

I use sqlx for initializing mysql connection (asynchronous) using lazy_static but weird errors occurred.

This is the code that I wrote:

use actix_web::middleware::Logger;
use actix_web::web::{Data, JsonConfig};
use actix_web::{App, HttpServer};

lazy_static::lazy_static! {
    static ref MYSQL_DB: async_once::AsyncOnce<sqlx::Pool<sqlx::MySql>> = async_once::AsyncOnce::new(async {
        dotenv::dotenv().expect("Failed to read .env file");
        let uri = std::env::var("DATABASE_URL").unwrap();
        sqlx::MySqlPool::connect(uri.as_str()).await.unwrap()
    });
    static ref DB_CLIENT : DBClient = DBClient{};
}

#[derive(Clone, Copy)]
pub struct DBClient;

impl DBClient {
    pub fn get() -> &'static DBClient {
        &DB_CLIENT
    }
    pub async fn mysql_pool(self) -> &'static sqlx::Pool<sqlx::MySql> {
        MYSQL_DB.get().await
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // start_tracing();
    let db_client = Data::new(DBClient::get());
    dotenv::dotenv().expect("Failed to read .env file");

    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .app_data(Data::new(JsonConfig::default().limit(4096)))
            .app_data(db_client.clone())
    })
    .bind(format!(
        "{}:{}",
        std::env::var("HOST").unwrap(),
        std::env::var("PORT").unwrap()
    ))
    .expect("Server binding exception")
    .run()
    .await
}

This is my Cargo.toml file:

[package]
name = "untitled"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4.0.1"
sqlx = { version = "0.5.*", features = ["mysql", "runtime-async-std-native-tls"] }
actix-rt = "2.*"
juniper = { version = "0.15.9", features = ["chrono"] }
uuid = { version = "=0.8", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
dotenv = "0.15.0"
lazy_static = "1.4.*"
async_once = "0.2.6"
validator = { version = "0.12", features = ["derive"] }

This is my .env file:

DATABASE_URL=mysql://user_name:password@localhost:3306/db_name
HOST=127.0.0.1
PORT=3000

And these are my errors:

error[E0277]: `*const Pool<MySql>` cannot be sent between threads safely
  --> src/main.rs:6:1
   |
6  | / lazy_static::lazy_static! {
7  | |     static ref MYSQL_DB: async_once::AsyncOnce<sqlx::Pool<sqlx::MySql>> = async_once::AsyncOnce::new(async {
8  | |         dotenv::dotenv().expect("Failed to read .env file");
9  | |         let uri = env::var("DATABASE_URL").unwrap();
...  |
12 | |     static ref DB_CLIENT : DBClient = DBClient{};
13 | | }
   | |_^ `*const Pool<MySql>` cannot be sent between threads safely
   |
   = help: the trait `Send` is not implemented for `*const Pool<MySql>`
   = note: required because of the requirements on the impl of `Send` for `Cell<*const Pool<MySql>>`
   = note: required because it appears within the type `AsyncOnce<Pool<MySql>>`
   = note: required because of the requirements on the impl of `Sync` for `spin::once::Once<AsyncOnce<Pool<MySql>>>`
   = note: required because it appears within the type `lazy_static::lazy::Lazy<AsyncOnce<Pool<MySql>>>`
   = note: shared static variables must have a type that implements `Sync`
   = note: this error originates in the macro `__lazy_static_create` (in Nightly builds, run with -Z macro-backtrace for more info)


error[E0277]: `(dyn std::future::Future<Output = Pool<MySql>> + 'static)` cannot be sent between threads safely
  --> src/main.rs:6:1
   |
6  | / lazy_static::lazy_static! {
7  | |     static ref MYSQL_DB: async_once::AsyncOnce<sqlx::Pool<sqlx::MySql>> = async_once::AsyncOnce::new(async {
8  | |         dotenv::dotenv().expect("Failed to read .env file");
9  | |         let uri = env::var("DATABASE_URL").unwrap();
...  |
12 | |     static ref DB_CLIENT : DBClient = DBClient{};
13 | | }
   | |_^ `(dyn std::future::Future<Output = Pool<MySql>> + 'static)` cannot be sent between threads safely
   |
   = help: the trait `Send` is not implemented for `(dyn std::future::Future<Output = Pool<MySql>> + 'static)`
   = note: required because of the requirements on the impl of `Send` for `Unique<(dyn std::future::Future<Output = Pool<MySql>> + 'static)>`
   = note: required because it appears within the type `Box<(dyn std::future::Future<Output = Pool<MySql>> + 'static)>`
   = note: required because it appears within the type `Pin<Box<(dyn std::future::Future<Output = Pool<MySql>> + 'static)>>`
   = note: required because it appears within the type `Result<Pool<MySql>, Pin<Box<(dyn std::future::Future<Output = Pool<MySql>> + 'static)>>>`
   = note: required because of the requirements on the impl of `Send` for `Mutex<Result<Pool<MySql>, Pin<Box<(dyn std::future::Future<Output = Pool<MySql>> + 'static)>>>>`
   = note: required because it appears within the type `AsyncOnce<Pool<MySql>>`
   = note: required because of the requirements on the impl of `Sync` for `spin::once::Once<AsyncOnce<Pool<MySql>>>`
   = note: required because it appears within the type `lazy_static::lazy::Lazy<AsyncOnce<Pool<MySql>>>`
   = note: shared static variables must have a type that implements `Sync`
   = note: this error originates in the macro `__lazy_static_create` (in Nightly builds, run with -Z macro-backtrace for more info)

I don't know the reason of the errors, but when I do the following it builds successfully:

  1. Replace the connection with Pool<Postgres>

  2. Change the mysql to postgres in sqlx's features section in Cargo.toml.

  3. Change DATABASE_URL in the .env file to Postgres URI

I got help from this question in stackoverflow: Rust lazy_static with async/await?

1

There are 1 best solutions below

4
Jeremy Meadows On

I replaced the lazy_static!/AsyncOnce with the (synchronous) OnceCell as was talked about in the comments. With those changes your program was able to compile:

use actix_web::middleware::Logger;
use actix_web::web::{Data, JsonConfig};
use actix_web::{App, HttpServer};
use once_cell::sync::OnceCell;
use sqlx::{Pool, MySql};

static MYSQL_DB: OnceCell<Pool<MySql>> = OnceCell::new();
static DB_CLIENT: DBClient = DBClient {};

#[derive(Clone, Copy)]
pub struct DBClient;

impl DBClient {
    pub fn get() -> &'static DBClient {
        &DB_CLIENT
    }

    pub async fn mysql_pool(self) -> &'static sqlx::Pool<sqlx::MySql> {
        MYSQL_DB.get().unwrap()
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    MYSQL_DB.set({
        dotenv::dotenv().expect("Failed to read .env file");
        let uri = std::env::var("DATABASE_URL").unwrap();
        sqlx::MySqlPool::connect(uri.as_str()).await.unwrap()
    }).unwrap();

    // start_tracing();
    let db_client = Data::new(DBClient::get());
    dotenv::dotenv().expect("Failed to read .env file");

    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .app_data(Data::new(JsonConfig::default().limit(4096)))
            .app_data(db_client.clone())
    })
    .bind(format!(
        "{}:{}",
        std::env::var("HOST").unwrap(),
        std::env::var("PORT").unwrap()
    ))
    .expect("Server binding exception")
    .run()
    .await
}

I don't think I can offer a great explanation on why what you had didn't work though.