mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 12:34:22 +08:00
fix: 复用本地 SQLite 连接池,修复偶发连接池耗尽
realtime_dual_write_sync 之前每次写命令都新建 sqlx 连接池并跑 migration,高频写入下导致 "pool timed out while waiting for an open connection"。改为在 AppState 缓存启动时建立的本地 SQLite 连接并复用。同时把 execute_rule 里的冷却检查改到事务连接内 执行,避免事务开启时再额外借一条池连接。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -749,13 +749,38 @@ async fn current_remote_and_local_from_state(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// 优先复用启动时缓存的本地 SQLite 连接;缓存未命中时再新建并回填。
|
||||
// 这样 realtime_dual_write_sync 不会在每次写命令后都新建一个 sqlx 连接池,
|
||||
// 避免“pool timed out while waiting for an open connection”式池耗尽。
|
||||
let local_conn = {
|
||||
// 先把 Arc 克隆出来,让 state_guard 尽早释放,再 read 缓存。
|
||||
let local_sqlite = {
|
||||
let state_guard = app_state.read();
|
||||
state_guard.local_sqlite.clone()
|
||||
};
|
||||
let cloned = local_sqlite.read().clone();
|
||||
cloned
|
||||
};
|
||||
|
||||
let local_conn = match local_conn {
|
||||
Some(conn) => conn,
|
||||
None => {
|
||||
let local_path = sqlite_db_path(app_handle)?;
|
||||
let local_conn = create_sqlite_connection(&local_path)
|
||||
let conn = create_sqlite_connection(&local_path)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
run_migration(&local_conn, DatabaseType::SQLite)
|
||||
run_migration(&conn, DatabaseType::SQLite)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
// 回填缓存(best-effort,竞争时仅最后写入者生效,两者都有效)。
|
||||
let state_guard = app_state.read();
|
||||
let mut local_guard = state_guard.local_sqlite.write();
|
||||
if local_guard.is_none() {
|
||||
*local_guard = Some(conn.clone());
|
||||
}
|
||||
conn
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some((local_conn, remote_conn)))
|
||||
}
|
||||
|
||||
@@ -225,6 +225,9 @@ fn setup_database(app: &mut App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let state = handle.state::<crate::state::SafeAppState>();
|
||||
let state_guard = state.write();
|
||||
let mut active_conn = sqlite_conn.clone();
|
||||
// 缓存本地 SQLite 连接,供 realtime_dual_write_sync 复用,
|
||||
// 避免每次写命令都新建连接池。
|
||||
*state_guard.local_sqlite.write() = Some(sqlite_conn.clone());
|
||||
|
||||
{
|
||||
let mut settings = state_guard.settings.write();
|
||||
|
||||
@@ -1858,7 +1858,7 @@ async fn execute_rule(
|
||||
for mut student in target_students {
|
||||
if mode == ExecutionMode::Normal {
|
||||
if !is_student_pass_cooldown(
|
||||
conn,
|
||||
&txn,
|
||||
execution_batches,
|
||||
rule,
|
||||
student.id,
|
||||
@@ -2368,8 +2368,8 @@ async fn resolve_target_students(
|
||||
Ok(matched)
|
||||
}
|
||||
|
||||
async fn is_student_pass_cooldown(
|
||||
conn: &DatabaseConnection,
|
||||
async fn is_student_pass_cooldown<C: ConnectionTrait>(
|
||||
conn: &C,
|
||||
execution_batches: &[AutoScoreExecutionBatch],
|
||||
rule: &AutoScoreRule,
|
||||
student_id: i32,
|
||||
|
||||
@@ -12,6 +12,9 @@ use crate::services::{
|
||||
|
||||
pub struct AppState {
|
||||
pub db: Arc<RwLock<Option<DatabaseConnection>>>,
|
||||
/// 缓存的本地 SQLite 连接(启动时建立),供 `realtime_dual_write_sync` 等复用,
|
||||
/// 避免每次写操作都新建 sqlx 连接池导致池耗尽 / 文件句柄残留。
|
||||
pub local_sqlite: Arc<RwLock<Option<DatabaseConnection>>>,
|
||||
pub settings: Arc<RwLock<SettingsService>>,
|
||||
pub security: Arc<RwLock<SecurityService>>,
|
||||
pub permissions: Arc<RwLock<PermissionService>>,
|
||||
@@ -37,6 +40,7 @@ impl AppState {
|
||||
let data = Arc::new(RwLock::new(DataService::new()));
|
||||
let plugins = Arc::new(RwLock::new(PluginService::new()));
|
||||
let db = Arc::new(RwLock::new(None));
|
||||
let local_sqlite = Arc::new(RwLock::new(None));
|
||||
|
||||
let http_client = Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
@@ -45,6 +49,7 @@ impl AppState {
|
||||
|
||||
Self {
|
||||
db,
|
||||
local_sqlite,
|
||||
settings,
|
||||
security,
|
||||
permissions,
|
||||
|
||||
Reference in New Issue
Block a user