Make app_date mutable in Actix-web

Hello

I have the following struct:

// Wrapper for Config
#[derive(Clone)]
pub struct MyConfig {
    pub config: std::collections::HashMap<String, String>,
}
impl MyConfig {
    pub async fn get_config(mysql: &web::Data<MySQL>) -> std::collections::HashMap<String, String> {
        let mut result = sqlx::query("SELECT config, value FROM config").fetch(&mysql.conn);

        let mut config = std::collections::HashMap::new();
        while let Some(row) = result.try_next().await.unwrap() {
            config.insert(
                row.try_get("config").unwrap(),
                row.try_get("value").unwrap(),
            );
        }

        config
    }

    pub async fn update(config: String, value: String, mysql: &web::Data<MySQL>) -> bool {
        let result = sqlx::query("UPDATE config SET value=? WHERE config=?")
            .bind(&value)
            .bind(&config)
            .execute(&mysql.conn)
            .await;

        match result {
            Err(e) => {
                println!(
                    "Error [in update() of struct MyConfig, main.rs]: {}",
                    e
                );
                false
            }
            Ok(r) => r.rows_affected() > 0,
        }
    }
}

This struct is used to get the configuration values from a database. I pass this configuration as app_data in Actix-web.

let myconfig = web::Data::new(MyConfig {
        config: MyConfig::get_config(&mysql).await,
});
...
.app_data(web::Data::clone(&myconfig))

This way I can access it everywhere using myconfig: web::Data<MyConfig> in my route functions.

The problem is that the configuration can be updated. And when that happens the app_data needs to be updated without the application needing to be restarted.

I don't know how to solve this. I thought of Mutex. Like this:

let myconfig = web::Data::new(Mutex::new(MyConfig {
        config: MyConfig::get_config(&mysql).await,
    }));

in route functions:

myconfig: web::Data<Mutex<MyConfig>>,

I thought I could keep the configuration mutable. But the problem is that it is impossible to retrieve the value of a Mutex.

I for example have this template:

#[derive(Template)]
#[template(path = "master-dashboard/config.html")]
pub struct Config {
    pub config: web::Data<Mutex<MyConfig>>,
}

I try to pass my config to that using

let mut s = templates::master_dashboard::Config {
        config: myconfig.lock().unwrap(),
    };
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(s.render().unwrap()))

This yields:

mismatched types expected struct `actix_web::web::Data<std::sync::Mutex<MyConfig>>` found struct `std::sync::MutexGuard<'_, MyConfig>`

How would I solve this issue?

EDIT:
This compiles:

let myconfig = web::Data::new(Mutex::new(MyConfig {
        config: MyConfig::get_config(&mysql).await,
    }));
#[derive(Template)]
#[template(path = "master-dashboard/config.html")]
pub struct Config {
    pub config: web::Data<Mutex<MyConfig>>,
}
pub async fn config_update(
    session: Session,
    data: web::Form<UpdateConfigForm>,
    mysql: web::Data<MySQL>,
    myconfig: web::Data<Mutex<MyConfig>>,
) -> Result<HttpResponse> {
   ...

    MyConfig::update(
        "commission".to_string(),
        data.commission.to_string(),
        &mysql,
    )
    .await;

    let mut x = myconfig.clone();
    let mut myconfig = x.lock().unwrap();
    myconfig.config = MyConfig::get_config(&mysql).await;

    let mut s = templates::master_dashboard::Config {
        config: myconfig,
    };
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(s.render().unwrap()))
}

But crashes my application when the update form is sent. My application won't run anymore.

Well, the compiler told you exactly what the problem is. Locking the mutex doesn't give you the mutex, it gives you a mutex guard, which is an RAII scope object that unlocks the mutex when it's dropped. If you want to put that into your config, then the type of the field must be MutexGuard, not Mutex.

By the way, I heavily doubt that's what you actually want, because the lifetime parameter will give you headache there. You should probably just wrap the mutex in an Arc, pass a clone of it around, and then only lock it temporarily whenever you need to access the data.

2 Likes

Isn't app_data an Arc?
And could you take a look at my edit I just made to my post? Maybe that gets closer?

I don't know, I don't use Actix. But if it is, then you can omit any explicit Arc-wrapping.

Re edit: how is your code crashing? With what message?

1 Like

It does not give any error message. It keeps 'working' but it won't load. The browser keeps 'loading' without actually crashing. As if it is in some kind of infinite loop.

You should probably copy the fields you need out of Config and pass those to the template instead of trying to pass the Mutex.

It also looks to me like your code still shouldn't compile after your edit, are you sure you pasted the new definition of the template type?

1 Like

I did that. It compiles, but still gives the problem that my website won't load after executing config_update. I was sure my template in my edit was the latest version.

New code.

Template:

#[derive(Template)]
#[template(path = "master-dashboard/config.html")]
pub struct Config {
    pub commission: String,
}

main.rs

// declaration
let myconfig = web::Data::new(Mutex::new(MyConfig {
    config: MyConfig::get_config(&mysql).await,
}));

//...

// binding in app_data
.app_data(web::Data::clone(&myconfig))

config_update which is a route-function executed when /update/config is posted.

#[derive(Deserialize)]
pub struct UpdateConfigForm {
    pub commission: String,
}
pub async fn config_update(
    session: Session,
    data: web::Form<UpdateConfigForm>,
    mysql: web::Data<MySQL>,
    myconfig: web::Data<Mutex<MyConfig>>,
) -> Result<HttpResponse> {
    if session.get::<MasterUser>("master_user").unwrap().is_none() {
        return Ok(HttpResponse::Found()
            .header(http::header::LOCATION, "/master/login")
            .finish());
    };

    MyConfig::update(
        "commission".to_string(),
        data.commission.to_string(),
        &mysql,
    )
    .await;

    let mut x = myconfig.clone();
    let mut my_myconfig = x.lock().unwrap();
    my_myconfig.config = MyConfig::get_config(&mysql).await;

    let mut s = templates::master_dashboard::Config {
        commission: myconfig
            .lock()
            .unwrap()
            .config
            .get("commission")
            .unwrap()
            .to_string(),
    };
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(s.render().unwrap()))
}

This compiles without errors and my application runs fine. But when I execute config_update my application stalls. No page will load, but no error is yielded (debug mode is on).

You're trying to lock the Mutex while it's already locked. Use my_myconfig to reference the data in the Mutex rather than locking it again.

2 Likes

That works perfectly!! Thanks a lot for the help mate!
Also thank you @H2CO3