fix: 修复并发 sub-agent 持久化时 SQLite database is locked 错误

将 7 个写事务从 BEGIN DEFERRED 改为 BEGIN IMMEDIATE,在事务开始即获取写锁,消除多 sub-agent 并发写入时的死锁路径。同时将 busy_timeout 从 5s 提升至 30s,为并发写者排队提供 100 倍余量。

根因:BEGIN DEFERRED 下多个事务可同时读 MAX(seq) 不持写锁,提交时互相阻塞,5s timeout 耗尽后返回 SQLITE_BUSY。BEGIN IMMEDIATE 强制写者串行排队,顺带消除 MAX(seq)+1 竞态导致的 UNIQUE 约束冲突。
This commit is contained in:
oudecheng 2026-08-03 22:15:13 +08:00
parent 2a5a0277c0
commit c7ee6bb519

View File

@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
use r2d2::Pool; use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::{Connection, OptionalExtension, params}; use rusqlite::{Connection, OptionalExtension, TransactionBehavior, params};
use crate::bus::ChatMessage; use crate::bus::ChatMessage;
@ -62,7 +62,7 @@ impl SessionStore {
/// The connection is used for schema initialization only; the pool /// The connection is used for schema initialization only; the pool
/// manages subsequent connections using the same file path. /// manages subsequent connections using the same file path.
fn from_connection(conn: Connection, db_uri: &str) -> Result<Self, StorageError> { fn from_connection(conn: Connection, db_uri: &str) -> Result<Self, StorageError> {
conn.busy_timeout(std::time::Duration::from_secs(5))?; conn.busy_timeout(std::time::Duration::from_secs(30))?;
conn.execute_batch( conn.execute_batch(
" "
PRAGMA journal_mode = WAL; PRAGMA journal_mode = WAL;
@ -230,7 +230,7 @@ impl SessionStore {
let manager = SqliteConnectionManager::file(db_uri) let manager = SqliteConnectionManager::file(db_uri)
.with_init(|c| { .with_init(|c| {
c.busy_timeout(std::time::Duration::from_secs(5))?; c.busy_timeout(std::time::Duration::from_secs(30))?;
Ok(()) Ok(())
}); });
let pool = Pool::builder() let pool = Pool::builder()
@ -575,8 +575,8 @@ impl SessionStore {
topic_id: Option<&str>, topic_id: Option<&str>,
message: &ChatMessage, message: &ChatMessage,
) -> Result<(), StorageError> { ) -> Result<(), StorageError> {
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let seq: i64 = tx.query_row( let seq: i64 = tx.query_row(
"SELECT COALESCE(MAX(seq), 0) + 1 FROM messages WHERE session_id = ?1", "SELECT COALESCE(MAX(seq), 0) + 1 FROM messages WHERE session_id = ?1",
@ -651,8 +651,8 @@ impl SessionStore {
return Ok(()); return Ok(());
} }
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let mut seq: i64 = tx.query_row( let mut seq: i64 = tx.query_row(
"SELECT COALESCE(MAX(seq), 0) + 1 FROM messages WHERE session_id = ?1", "SELECT COALESCE(MAX(seq), 0) + 1 FROM messages WHERE session_id = ?1",
@ -736,8 +736,8 @@ impl SessionStore {
summary_message: &ChatMessage, summary_message: &ChatMessage,
preserved_messages: &[ChatMessage], preserved_messages: &[ChatMessage],
) -> Result<bool, StorageError> { ) -> Result<bool, StorageError> {
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let current_max_seq: i64 = tx.query_row( let current_max_seq: i64 = tx.query_row(
"SELECT COALESCE(MAX(seq), 0) FROM messages WHERE session_id = ?1", "SELECT COALESCE(MAX(seq), 0) FROM messages WHERE session_id = ?1",
@ -833,8 +833,8 @@ impl SessionStore {
session_id: &str, session_id: &str,
messages: &[ChatMessage], messages: &[ChatMessage],
) -> Result<(), StorageError> { ) -> Result<(), StorageError> {
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let now = current_timestamp(); let now = current_timestamp();
// Delete all existing messages for this session // Delete all existing messages for this session
@ -892,8 +892,8 @@ impl SessionStore {
topic_id: &str, topic_id: &str,
messages: &[ChatMessage], messages: &[ChatMessage],
) -> Result<(), StorageError> { ) -> Result<(), StorageError> {
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let now = current_timestamp(); let now = current_timestamp();
// Delete only messages belonging to this topic — other topics' // Delete only messages belonging to this topic — other topics'
@ -1023,8 +1023,8 @@ impl SessionStore {
pub fn put_memory(&self, input: &MemoryUpsert) -> Result<MemoryRecord, StorageError> { pub fn put_memory(&self, input: &MemoryUpsert) -> Result<MemoryRecord, StorageError> {
let now = current_timestamp(); let now = current_timestamp();
let conn = self.pool.get()?; let mut conn = self.pool.get()?;
let tx = conn.unchecked_transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let existing: Option<(String, i64)> = tx let existing: Option<(String, i64)> = tx
.query_row( .query_row(
@ -1611,10 +1611,11 @@ impl SessionStore {
items: &[TodoRecord], items: &[TodoRecord],
) -> Result<Vec<TodoRecord>, StorageError> { ) -> Result<Vec<TodoRecord>, StorageError> {
let mut conn = self.pool.get()?; let mut conn = self.pool.get()?;
// 用 transaction()(非 unchecked_transaction保证严格事务语义 // 用 BEGIN IMMEDIATE 事务保证严格语义:写锁在事务开始时获取,
// 避免并发写事务在提交时死锁导致 "database is locked"。
// 用户数据替换需保证原子性——中途失败必须回滚,避免 DELETE 后 INSERT // 用户数据替换需保证原子性——中途失败必须回滚,避免 DELETE 后 INSERT
// 异常导致 todos 列表丢失且无法恢复。 // 异常导致 todos 列表丢失且无法恢复。
let tx = conn.transaction()?; let tx = conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
let now = current_timestamp(); let now = current_timestamp();
// Delete existing todos for this scope_key // Delete existing todos for this scope_key