2

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?

smitop
  • 4,770
  • 2
  • 20
  • 53
  • 2
    Nitpick: don't use `lazy_static`, use `once_cell`, its API is going to be integrated into std. – Chayim Friedman Jun 20 '22 at 11:58
  • If something is not `Send`, it can often be fixed by wrapping it in an `Arc`. What if you replace `MYSQL_DB: AsyncOnce>` with `MYSQL_DB: Arc>>`? – Jeremy Meadows Jun 20 '22 at 12:48
  • @JeremyMeadows The Arc wrapper give me the same error!! – Abdelaziz Said Jun 21 '22 at 06:28
  • @ChayimFriedman Can you give me an example please. – Abdelaziz Said Jun 21 '22 at 06:28
  • @JeremyMeadows `Send`ness cannot be fixed. If something is not `Send`, you cannot workaround it. You can add `Sync` however by `Mutex`. `Arc` is for shared ownership, that's not related at all. – Chayim Friedman Jun 21 '22 at 06:31
  • @ChayimFriedman Can you try it with into your laptop and give me an example please. I need to inject it into the actix into the main to call back later into the APIs – Abdelaziz Said Jun 21 '22 at 06:42
  • @ChayimFriedman I tried this code but it still return the same error. static DB_CLIENT: SyncLazy>> = SyncLazy::new( ||async_once::AsyncOnce::new( async { Arc::new(DBClient::new().await.db_client()) })); #[async_trait] trait AdapterPattern: Send + Sync { async fn db_client(&self) -> DBClient; } #[async_trait] impl AdapterPattern for DBClient { async fn db_client(&self) -> Self { Self } } #[derive(Clone)] pub struct PersistenceDB { pub mysql_pool: Pool, } – Abdelaziz Said Jun 21 '22 at 10:11
  • @AbdelazizSaid I didn't mean this will solve your error (that's why I said "Nitpick"), just a good general advice. – Chayim Friedman Jun 21 '22 at 10:14
  • @ChayimFriedman I tried lazy_static, SyncLazy, also I tried to create a trait which implement (Send + Sync) but doesn't work. So I don't know how to solve the problem. In general Thank you brother for your help. – Abdelaziz Said Jun 21 '22 at 11:32
  • @ChayimFriedman oh, I didn't realize that. what effect then does [`impl Send for Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html#impl-Send) have? – Jeremy Meadows Jun 21 '22 at 12:32
  • 1
    @JeremyMeadows `where T: Send + Sync`. – Chayim Friedman Jun 22 '22 at 00:38

1 Answers1

0

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.

Jeremy Meadows
  • 2,314
  • 1
  • 6
  • 22
  • same error error[E0277]:`UnsafeCell – Abdelaziz Said Jun 21 '22 at 14:07
  • @AbdelazizSaid are you using `once_cell::sync::OnceCell`? that error message matches the one I get for `once_cell::unsync::OnceCell` (notice sync vs unsync). I copy-pasted it back into my editor and it still worked for me – Jeremy Meadows Jun 21 '22 at 14:12
  • When I need other database, I find that the server throws this error!! thread 'main' panicked at 'Error in Establishing the MySql Login Connection!!: Pool { size: 1, num_idle: 1, is_closed: false, options: PoolOptions { max_connections: 10, min_connections: 0, connect_timeout: 30s, max_lifetime: Some(1800s), idle_timeout: Some(600s), test_before_acquire: true } }', – Abdelaziz Said Jul 04 '22 at 13:38
  • I added this function in the main as what you did and thrown an exception. LoginDBClient::one_cell_mysql_pool().set({ dotenv::dotenv().expect("Failed to read .env file"); let uri = env::var("LOGIN_DATABASE_URL").unwrap(); println!("login_uri: {}", uri); MysqlPool::connect(uri.as_str()).await.unwrap() }).expect("Error in Establishing the MySql Login Connection!!"); – Abdelaziz Said Jul 04 '22 at 13:39