fix(sqlite): DSN 加 busy_timeout/WAL/foreign_keys + 连接池上限,避免 database is locked
=== 背景:这是 opsxsql 的「死锁类」风险 ===
LokiStack 是自研 JsonDB + sync.RWMutex,问题是**锁重入死锁**(已由上游 2e97c04 修复)。
opsxsql 用 SQLite + GORM,风险形式不同但后果相似:
裸 DSN(不带任何 pragma)+ 默认 journal 模式 + 无连接池上限
→ 写事务与读事务互相阻塞,并发写直接返回 "database is locked"(SQLITE_BUSY),
表现为「点保存失败 / 偶发 500」,而且不会挂住所以更难定位。
静态扫描结论:opsxsql 33 个 Go 文件无 RWMutex 重入问题(0 处),
唯一 RWMutex 在 internal/ghost 且用法正确。日志中 0 次 SQLITE_BUSY
(并发压力尚未到临界点,属**潜在**风险)。
=== 修改 1:DSN pragma ===
?_busy_timeout=5000 遇锁等 5 秒而不是立刻失败
&_journal_mode=WAL 读写不互斥,写不阻塞读
&_synchronous=NORMAL WAL 下常规安全级别
&_foreign_keys=on SQLite 默认关闭外键,容易埋脏数据
仅在 DBPath 不含 "?" 时追加,保留运维自定义 DSN 的能力。
=== 修改 2:连接池上限 4 ===
SQLite 同时只允许一个写者,连接开太多只会加剧锁竞争。
=== 修改 3:ghost.go 回调移出锁(防御性)===
原来 cb(taskID, result.Output) 在持有 Manager.mu 写锁时执行;
若调用方在回调里查任务状态(GetTaskStatus 需要 RLock)会永久死锁。
当前所有调用方都传 nil,属潜在问题,一并修掉。
=== 实测(46a97a5c11b7,ns=sql)===
Pod 1/1 Running,重启 0
/app/data 出现 sqlite3.db-wal + sqlite3.db-shm → WAL 已生效
日志 0 次 database is locked / SQLITE_BUSY
This commit is contained in:
1 parent
9dcb39e971
commit
d6f27545ad
2 files changed
+28
-2
No files matched your search
@@ -163,8 +163,9 @@ func RunGhOstAsync(task GhOstTask, ghOstPath string, cb ProgressCallback) string
|
||||
|
||||
err := cmd.Run()
|
||||
|
||||
// 注意:这里不能用 defer Unlock —— 回调要放到锁外执行,
|
||||
// 所以显式在写完 result 后 Unlock(上面已写明原因)。
|
||||
Manager.mu.Lock()
|
||||
defer Manager.mu.Unlock()
|
||||
|
||||
result := &GhOstResult{
|
||||
TaskID: taskID,
|
||||
@@ -182,7 +183,11 @@ func RunGhOstAsync(task GhOstTask, ghOstPath string, cb ProgressCallback) string
|
||||
}
|
||||
|
||||
Manager.tasks[taskID] = result
|
||||
Manager.mu.Unlock()
|
||||
|
||||
// 回调必须放在锁**外面**:调用方如果在回调里查任务状态
|
||||
// (GetTaskStatus 需要 RLock),持有写锁时调用会永久死锁。
|
||||
// 当前所有调用方传的都是 nil,属于防御性修改。
|
||||
if cb != nil {
|
||||
cb(taskID, result.Output)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user