rust操作数据库同步锁,调用异步函数,触发死锁问题


示例代码

use actix_web::{web, Responder};
use crate::handlers::AppState;
use crate::models::auth::Auth;
use crate::utils::password::password_utils;
use crate::utils::api_response::ApiResponse;
use std::error::Error;
pub async fn verify_password(data: web::Data<AppState>, user: web::Json<Auth>) -> impl Responder {
    let mut password = None;
    let conn = data.db.conn.lock().unwrap();
    // 查询用户
    let mut stmt = conn.prepare("SELECT password FROM sys_user WHERE username = ? and enable = 0").unwrap();
    let rows = stmt.query_map(&[&user.username], |row| row.get::<_, String>(0)).unwrap();
    for row in rows {
        password = Some(row.unwrap());
        break;
    }
    // 验证密码
    if let Some(pwd) = password {
        match password_utils::verify_password(&user.password, &pwd) {
            Ok(true) => {
                if is_first_login(data.clone()).await.unwrap() {
                    update_login_status(data.clone()).await.unwrap();
                }
                ApiResponse::success(Some("Password verified successfully".to_string()))
            },
            Ok(false) => ApiResponse::error(None,None,Some("Incorrect password".to_string())),
            Err(_) => ApiResponse::error(None,None,Some("Password verification error".to_string())),
        }
    } else {
        ApiResponse::error(None,None,Some("User not found".to_string()))
    }
}
// 判断系统是否是第一次登录
pub async fn is_first_login(data: web::Data<AppState>) -> Result<bool, Box<dyn Error + Send + Sync>> {
    let conn = data.db.conn.lock().unwrap();
    println!("{:?}",conn);
    // 查询用户
    let mut stmt = conn.prepare("select value from sys_config where key = 'first_login' and value = '0' limit 0,1").unwrap();
    let rows = stmt.query_map([], |row| row.get::<_, String>(0)).unwrap();
    let mut value = None;
    for row in rows{
        value = Some(row.unwrap());
        break;
    }
    println!("{:?}",value);
    // 判断是否第一次登录
    if value == Some("0".to_string()) {
        Ok(true)
    } else {
        Ok(false)
    }
}
// 更新登录状态
pub async fn update_login_status(data: web::Data<AppState>) -> Result<(), Box<dyn Error + Send + Sync>> {
    let conn: std::sync::MutexGuard<'_, rusqlite::Connection> = data.db.conn.lock().unwrap();
    // 更新登录状态
    let mut stmt = conn.prepare("update sys_config set value = '1' where key = 'first_login'").unwrap();
    stmt.execute([]).unwrap();
    Ok(())
}

线程阻塞原因分析

异步函数中嵌套使用同步锁导致的死锁

// 在verify_password函数中
let conn = data.db.conn.lock().unwrap();  // 获取数据库连接锁
// ...
if is_first_login(data.clone()).await.unwrap() {  // 调用异步函数
    update_login_status(data.clone()).await.unwrap();  // 又调用异步函数
}

关键问题点:

  1. 主线程在verify_password中获取了数据库连接的同步锁(std::sync::Mutex
  2. 然后调用了异步函数is_first_loginupdate_login_status
  3. 这两个异步函数内部又尝试获取同一个数据库连接锁
  4. 主线程持有锁并等待异步函数完成,而异步函数又等待主线程释放锁
  5. 形成了经典的死锁场景

解决思路

1. 在调用异步函数前释放锁

pub async fn verify_password(data: web::Data<AppState>, user: web::Json<Auth>) -> impl Responder {
    let mut password = None;
    { // 开始一个新的作用域
        let conn = data.db.conn.lock().unwrap();
        // 查询用户
        let mut stmt = conn.prepare("SELECT password FROM sys_user WHERE username = ? and enable = 0").unwrap();
        let rows = stmt.query_map(&[&user.username], |row| row.get::<_, String>(0)).unwrap();
        for row in rows {
            password = Some(row.unwrap());
            break;
        }
    } // 作用域结束,锁释放
    // 验证密码
    if let Some(pwd) = password {
        match password_utils::verify_password(&user.password, &pwd) {
            Ok(true) => {
                if is_first_login(data.clone()).await.unwrap() {
                    update_login_status(data.clone()).await.unwrap();
                }
                ApiResponse::success(Some("Password verified successfully".to_string()))
            },
            Ok(false) => ApiResponse::error(None,None,Some("Incorrect password".to_string())),
            Err(_) => ApiResponse::error(None,None,Some("Password verification error".to_string())),
        }
    } else {
        ApiResponse::error(None,None,Some("User not found".to_string()))
    }
}

2. 使用异步锁替代同步锁

// 在数据库模型中使用tokio的异步锁
use tokio::sync::Mutex;
// 然后在处理函数中使用await获取锁
let conn = data.db.conn.lock().await;

3. 重构为一次性数据库操作

pub async fn verify_password(data: web::Data<AppState>, user: web::Json<Auth>) -> impl Responder {
    let conn = data.db.conn.lock().unwrap();
    // 验证密码逻辑...
    // 合并查询和更新操作,避免多次获取锁
    let is_first = conn.prepare("select value from sys_config where key = 'first_login' and value = '0'")
        .unwrap()
        .query_map([], |row| row.get::<_, String>(0))
        .unwrap()
        .next()
        .map_or(false, |r| r.unwrap() == "0");
    if is_first {
        conn.execute(
            "update sys_config set value = '1' where key = 'first_login'",
            []
        ).unwrap();
    }
    ApiResponse::success(Some("Password verified successfully".to_string()))
}

4. 使用数据库连接池

// 在AppState中使用连接池而非单个连接
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
// 每个请求获取独立连接,避免锁竞争
let conn = data.db_pool.get().unwrap();

5. 使用非阻塞I/O和futures-aware的数据库驱动

// 使用支持异步的数据库驱动,如sqlx
use sqlx::sqlite::SqlitePool;
// 异步查询和更新
let is_first = sqlx::query_scalar("select value from sys_config where key = 'first_login' and value = '0'")
    .fetch_optional(&data.db_pool)
    .await?;

声明:一代明君的小屋|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - rust操作数据库同步锁,调用异步函数,触发死锁问题


欢迎来到我的小屋