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:
JSR
2026-07-05 18:15:40 +08:00
parent f033776dc4
commit 2937043e40
4 changed files with 43 additions and 10 deletions
+27 -2
View File
@@ -749,13 +749,38 @@ async fn current_remote_and_local_from_state(
return Ok(None); 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_path = sqlite_db_path(app_handle)?;
let local_conn = create_sqlite_connection(&local_path) let conn = create_sqlite_connection(&local_path)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
run_migration(&local_conn, DatabaseType::SQLite) run_migration(&conn, DatabaseType::SQLite)
.await .await
.map_err(|e| e.to_string())?; .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))) Ok(Some((local_conn, remote_conn)))
} }
+3
View File
@@ -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 = handle.state::<crate::state::SafeAppState>();
let state_guard = state.write(); let state_guard = state.write();
let mut active_conn = sqlite_conn.clone(); 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(); let mut settings = state_guard.settings.write();
+3 -3
View File
@@ -1858,7 +1858,7 @@ async fn execute_rule(
for mut student in target_students { for mut student in target_students {
if mode == ExecutionMode::Normal { if mode == ExecutionMode::Normal {
if !is_student_pass_cooldown( if !is_student_pass_cooldown(
conn, &txn,
execution_batches, execution_batches,
rule, rule,
student.id, student.id,
@@ -2368,8 +2368,8 @@ async fn resolve_target_students(
Ok(matched) Ok(matched)
} }
async fn is_student_pass_cooldown( async fn is_student_pass_cooldown<C: ConnectionTrait>(
conn: &DatabaseConnection, conn: &C,
execution_batches: &[AutoScoreExecutionBatch], execution_batches: &[AutoScoreExecutionBatch],
rule: &AutoScoreRule, rule: &AutoScoreRule,
student_id: i32, student_id: i32,
+5
View File
@@ -12,6 +12,9 @@ use crate::services::{
pub struct AppState { pub struct AppState {
pub db: Arc<RwLock<Option<DatabaseConnection>>>, 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 settings: Arc<RwLock<SettingsService>>,
pub security: Arc<RwLock<SecurityService>>, pub security: Arc<RwLock<SecurityService>>,
pub permissions: Arc<RwLock<PermissionService>>, pub permissions: Arc<RwLock<PermissionService>>,
@@ -37,6 +40,7 @@ impl AppState {
let data = Arc::new(RwLock::new(DataService::new())); let data = Arc::new(RwLock::new(DataService::new()));
let plugins = Arc::new(RwLock::new(PluginService::new())); let plugins = Arc::new(RwLock::new(PluginService::new()));
let db = Arc::new(RwLock::new(None)); let db = Arc::new(RwLock::new(None));
let local_sqlite = Arc::new(RwLock::new(None));
let http_client = Client::builder() let http_client = Client::builder()
.timeout(std::time::Duration::from_secs(30)) .timeout(std::time::Duration::from_secs(30))
@@ -45,6 +49,7 @@ impl AppState {
Self { Self {
db, db,
local_sqlite,
settings, settings,
security, security,
permissions, permissions,