PoisonError on lock

I got the following runtime-error:

thread 'actix-rt|system:0|arbiter:1' panicked at 'called `Result::unwrap()` on an `Err` value: PoisonError { .. }', src/route_handlers/api.rs:238:55

At this piece of code:

#[derive(Deserialize, Serialize)]
pub struct HellofoodConfigResponse {
    transaction_fee: f64,
}
pub async fn get_hellofood_config(
    hellofood_config: web::Data<Mutex<HellofoodConfig>>,
) -> Result<HttpResponse> {
    let my_hellofood_config = hellofood_config.lock().unwrap();

    Ok(HttpResponse::Ok().content_type("application/json").body(
        serde_json::to_string(&HellofoodConfigResponse {
            transaction_fee: my_hellofood_config
                .config
                .get("transaction_fee")
                .unwrap()
                .to_string()
                .parse::<f64>()
                .unwrap(),
        })
        .unwrap(),
    ))
}

I think it has something to do with my_hellofood_config.
It is the following struct

// Wrapper for Config
#[derive(Clone)]
pub struct HellofoodConfig {
    pub config: std::collections::HashMap<String, String>,
}
impl HellofoodConfig {
    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 HellofoodConfig, main.rs]: {}",
                    e
                );
                false
            }
            Ok(r) => r.rows_affected() > 0,
        }
    }
}

Passed as app_data in Actix web like this

let hellofood_config = web::Data::new(Mutex::new(HellofoodConfig {
        config: HellofoodConfig::get_config(&mysql).await,
    }));
//...
.app_data(web::Data::clone(&hellofood_config))
//...

I have a global configuration (like transaction-fees, commission-fees,...) for my platform which needs to be dynamic so I can change them at runtime. These configurations are saved in the database by the HellofoodConfig object.

I never had this error before and recompiling the application solved it. How can I prevent this error in the future though?

A Mutex gets poisoned when a thread that currently holds the lock panics:

You can avoid a poison error by not panicing in your code when holding a Mutex. First thing I'd watch for is calling unwrap and rewrite the given section of your code. If the panic happens in a third party library, look for non-panicing alternatives in their interface. If not available write your own logic around the call that avoids panicing conditions or ask the library maintainers to please provide a non-panicing alternative. Return some sort of Result or handle the error in a more graceful way than just to panic. Panicing in a request to a web server is bad practice to begin with as you should rather return a proper error that can be handled by the client instead of dropping the request with the panic.

2 Likes

Mutexes are poisoned by .unwrap(), and you're using a lot of .unwrap(). This is a bad practice outside of tests and throw-away-prototype code.

You should replace these unwraps with proper error handling using the ? operator. See thiserror and anyhow libraries to help with handling of various errors.

2 Likes

Will using try_lock() instead of lock() also fix this?

No. You need to get your error handling done properly as mentioned before.

1 Like

Okay, and if I understand it correctly, this panic error is not necessarily only triggered by this code or this unwrap.

transaction_fee: my_hellofood_config
                .config
                .get("transaction_fee")
                .unwrap()
                .to_string()
                .parse::<f64>()
                .unwrap(),

But can be triggered by a panic error anywhere in my code, right?

Yeah, exactly. If your code panics while you are holding a lock, the lock will be poisoned.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.