From 2937043e4052f527320386f3a12c0187b4389c81 Mon Sep 17 00:00:00 2001 From: JSR Date: Sun, 5 Jul 2026 18:15:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=A4=8D=E7=94=A8=E6=9C=AC=E5=9C=B0=20S?= =?UTF-8?q?QLite=20=E8=BF=9E=E6=8E=A5=E6=B1=A0=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=81=B6=E5=8F=91=E8=BF=9E=E6=8E=A5=E6=B1=A0=E8=80=97=E5=B0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit realtime_dual_write_sync 之前每次写命令都新建 sqlx 连接池并跑 migration,高频写入下导致 "pool timed out while waiting for an open connection"。改为在 AppState 缓存启动时建立的本地 SQLite 连接并复用。同时把 execute_rule 里的冷却检查改到事务连接内 执行,避免事务开启时再额外借一条池连接。 Co-Authored-By: Claude --- src-tauri/src/commands/database.rs | 39 +++++++++++++++++++++++----- src-tauri/src/lib.rs | 3 +++ src-tauri/src/services/auto_score.rs | 6 ++--- src-tauri/src/state.rs | 5 ++++ 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/commands/database.rs b/src-tauri/src/commands/database.rs index ec92403..a502e07 100644 --- a/src-tauri/src/commands/database.rs +++ b/src-tauri/src/commands/database.rs @@ -749,13 +749,38 @@ async fn current_remote_and_local_from_state( return Ok(None); } - let local_path = sqlite_db_path(app_handle)?; - let local_conn = create_sqlite_connection(&local_path) - .await - .map_err(|e| e.to_string())?; - run_migration(&local_conn, DatabaseType::SQLite) - .await - .map_err(|e| e.to_string())?; + // 优先复用启动时缓存的本地 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 conn = create_sqlite_connection(&local_path) + .await + .map_err(|e| e.to_string())?; + 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))) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 0856ff6..ba210d6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -225,6 +225,9 @@ fn setup_database(app: &mut App) -> Result<(), Box> { let state = handle.state::(); 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(); diff --git a/src-tauri/src/services/auto_score.rs b/src-tauri/src/services/auto_score.rs index cecd4af..6c6b51c 100644 --- a/src-tauri/src/services/auto_score.rs +++ b/src-tauri/src/services/auto_score.rs @@ -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( + conn: &C, execution_batches: &[AutoScoreExecutionBatch], rule: &AutoScoreRule, student_id: i32, diff --git a/src-tauri/src/state.rs b/src-tauri/src/state.rs index 76e29db..34fda73 100644 --- a/src-tauri/src/state.rs +++ b/src-tauri/src/state.rs @@ -12,6 +12,9 @@ use crate::services::{ pub struct AppState { pub db: Arc>>, + /// 缓存的本地 SQLite 连接(启动时建立),供 `realtime_dual_write_sync` 等复用, + /// 避免每次写操作都新建 sqlx 连接池导致池耗尽 / 文件句柄残留。 + pub local_sqlite: Arc>>, pub settings: Arc>, pub security: Arc>, pub permissions: Arc>, @@ -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,