mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +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);
|
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)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user