Cannot be shared between threads safely

This is a follow-up from this question. There I successfully made one global variable so I can access my database-connection in my whole code.

Now I switched the crate to Diesel, and this doesn't work anymore.
Code:

use actix_cors::Cors;
use actix_web::{web, get, middleware, Result, Responder, HttpResponse, HttpServer, App};
use actix_web::http::StatusCode;
use serde::Deserialize;


#[macro_use]
extern crate lazy_static;
extern crate regex;
#[macro_use]
extern crate diesel;

use regex::Regex;

use diesel::insert_into;
use diesel::mysql::Mysql;
use diesel::prelude::*;
use diesel::debug_query;

mod schema {
    table! {
        users (id) {
            id -> Integer,
            username -> VarChar,
            email -> VarChar,
            password -> VarChar,
            birthdate -> Timestamp,
        }
    }
}

use schema::users;

lazy_static! {
    static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor"));
}

// Module for MySQL
pub mod mysqldata {


use crate::RegistrationForm;
use diesel::mysql::MysqlConnection;
use diesel::insert_into;
use diesel::mysql::Mysql;
use diesel::prelude::*;
use diesel::debug_query;

    pub struct MySQLData {
        pub conn: MysqlConnection
    }



    impl MySQLData {
        //Return MySQLData object with conn field
        pub fn init_connection(database_name : &String, database_user : &String, database_pass : &String) -> MySQLData {
            let conn_str : String = format!("mysql://{user}:{pass}@localhost/{name}", user=database_user, pass=database_pass, name=database_name);
            let conn = MysqlConnection::establish(&conn_str);

            match conn {
                Ok(_) => {

                    println!("Connection to {} successful!", database_name);

                    MySQLData {
                        conn: conn.unwrap()
                    }
                },
                Err(e) => {
                    eprintln!("Connection to {} failed: {}", database_name, e.to_string());
                    std::process::exit(-1);
                }
            }
        }


        pub fn add_user(&self, data: RegistrationForm) -> bool {

            use crate::schema::users::dsl::*;
            let new_user = (username.eq(data.username), email.eq(data.email), password.eq(data.password));
            let query = insert_into(users)
            .values(&new_user)
            .execute(&self.conn);


            match query {
                Ok(_) => {
                    println!("User {} added!", data.username);
                    true
                }
                Err(e) => {
                    eprintln!("Failed to add user {}: {}", data.username, e.to_string());
                    false
                }
            }
        }

        pub fn read_user(&mut self, id: u8) -> RegistrationForm {
            unimplemented!()
        }
    }


}

#[derive(Deserialize)]
pub struct RegistrationForm {
    username: String,
    email: String,
    birthdate: String,
    password: String,
    rp_password: String
}
impl RegistrationForm {
    pub fn is_valid(&self) -> bool {
        if self.username.is_empty() ||
        self.email.is_empty() ||
        self.birthdate.is_empty() ||
        self.password.is_empty() ||
        self.rp_password.is_empty() {
            println!("User {} did not fill in all inputs!", self.username);
            return false
        }

        if self.password != self.rp_password {
            println!("User {} did not repeat the password correctly!", self.username);
            return false
        }

        let re_date = Regex::new(r"^\d{2}-\d{2}-\d{4}$").unwrap();
        if !re_date.is_match(&*&self.birthdate) {
            println!("{} for user {} is not a valid date", self.birthdate, self.username);
            return false
        }

        let re_email = Regex::new(r"[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,3}").unwrap();
        if !re_email.is_match(&*&self.email) {
            println!("{} for user {} is not a valid mail-address", self.email, self.username);
            return false
        }

        true
    }
}

//Function which gets executed with correct route
async fn register(form: web::Form<RegistrationForm>) -> String {

    let registration_form = form.into_inner();

    if !registration_form.is_valid() {
        return String::from("error");
    }




}



#[actix_rt::main]
async fn main() -> std::io::Result<()> {



    HttpServer::new(|| App::new()
        .wrap(
            Cors::new()
            .allowed_origin("http://rustsite.local")
            .finish()
        )
        .service(
            web::resource("/register").route(web::post().to(register))
        )
    )
    .bind("127.0.0.1:8088")?
    .run()
    .await
}

Error:

error[E0277]: `std::ptr::NonNull<mysqlclient_sys::st_mysql>` cannot be shared between threads safely
  --> src/main.rs:34:1
   |
34 | / lazy_static! {
35 | |     static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &Strin...
36 | | }
   | |_^ `std::ptr::NonNull<mysqlclient_sys::st_mysql>` cannot be shared between threads safely
   |
  ::: /Users/niel/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 |   pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                      ---- required by this bound in `lazy_static::lazy::Lazy`
   |
   = help: within `mysqldata::MySQLData`, the trait `std::marker::Sync` is not implemented for `std::ptr::NonNull<mysqlclient_sys::st_mysql>`
   = note: required because it appears within the type `diesel::mysql::connection::raw::RawConnection`
   = note: required because it appears within the type `diesel::MysqlConnection`
   = note: required because it appears within the type `mysqldata::MySQLData`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `std::cell::Cell<i32>` cannot be shared between threads safely
  --> src/main.rs:34:1
   |
34 | / lazy_static! {
35 | |     static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &Strin...
36 | | }
   | |_^ `std::cell::Cell<i32>` cannot be shared between threads safely
   |
  ::: /Users/niel/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 |   pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                      ---- required by this bound in `lazy_static::lazy::Lazy`
   |
   = help: within `mysqldata::MySQLData`, the trait `std::marker::Sync` is not implemented for `std::cell::Cell<i32>`
   = note: required because it appears within the type `diesel::connection::AnsiTransactionManager`
   = note: required because it appears within the type `diesel::MysqlConnection`
   = note: required because it appears within the type `mysqldata::MySQLData`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: `std::cell::RefCell<std::collections::HashMap<diesel::connection::StatementCacheKey<diesel::mysql::Mysql>, diesel::mysql::connection::stmt::Statement>>` cannot be shared between threads safely
  --> src/main.rs:34:1
   |
34 | / lazy_static! {
35 | |     static ref db : mysqldata::MySQLData = mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &Strin...
36 | | }
   | |_^ `std::cell::RefCell<std::collections::HashMap<diesel::connection::StatementCacheKey<diesel::mysql::Mysql>, diesel::mysql::connection::stmt::Statement>>` cannot be shared between threads safely
   |
  ::: /Users/niel/.cargo/registry/src/github.com-1ecc6299db9ec823/lazy_static-1.4.0/src/inline_lazy.rs:19:20
   |
19 |   pub struct Lazy<T: Sync>(Cell<Option<T>>, Once);
   |                      ---- required by this bound in `lazy_static::lazy::Lazy`
   |
   = help: within `mysqldata::MySQLData`, the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<std::collections::HashMap<diesel::connection::StatementCacheKey<diesel::mysql::Mysql>, diesel::mysql::connection::stmt::Statement>>`
   = note: required because it appears within the type `diesel::connection::StatementCache<diesel::mysql::Mysql, diesel::mysql::connection::stmt::Statement>`
   = note: required because it appears within the type `diesel::MysqlConnection`
   = note: required because it appears within the type `mysqldata::MySQLData`
   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors; 9 warnings emitted

For more information about this error, try `rustc --explain E0277`.
error: could not compile `rustSite1`.

To learn more, run the command again with --verbose.

The type is Send but not Sync. You need to put it behind a mutex.

1 Like

How? Like this?

    static ref db = Mutex::new(mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor")));

It looks right. Does it work?

No. It generates the following error:

error: no rules expected the token `=`
  --> src/main.rs:36:19
   |
36 |     static ref db = Mutex::new(mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("too...
   |                   ^ no rules expected this token in macro call
lazy_static! {
    static ref db = Mutex::new(mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor")));
}

You still need to specify the type.

2 Likes

This works

lazy_static! {
    static ref db : Mutex<mysqldata::MySQLData> = Mutex::new(mysqldata::MySQLData::init_connection(&String::from("rustsite"), &String::from("root"), &String::from("toor")));
}

How do I access an object in a Mutex in Rust?

db.add_user(registration_form);
no method named `add_user` found for struct `db` in the current scope
method not found in `db`

You need to lock the Mutex to gain exclusive access, and then you can dereference or call methods on the returned MutexGuard object:

db.lock().unwrap().add_user(registration_form);
2 Likes

Instead of using a Mutex you probably want to use a connection pool here. Diesel supports r2d2. Have a look at the corresponding documentation.

1 Like

I don't mean to have a downer on Diesel or r2d2 but that documentation is typical of what I find impossible to make any sense out of. I cannot even see where to get started with it. Not even an example of usage to go on.

How is one supposed to stared reading such things?

I ask in seriousness because I am at a similar road block in using postgres with Rocket.

1 Like

I agree that the documentation can be better, but as mostly for such things: It's a question of time, so if someone puts up a PR improving that I'm certain that it will get better.

r2d2's documentation has a generic example. You basically can copy this and replace r2d2_foodb::FooConnectionManager with your concrete connection manager. For diesel that would be diesel::r2d2::ConnectionManager. Also note that diesel does reexport r2d2 inside of diesel::r2d2, so there is no need to depend on r2d2 directly.

2 Likes

Thanks. That looks like it might help a lot.

Shouldn't he be using an async Mutex here?