From fc3b2ef30956f5d2570dc51107936e266bacfa8c Mon Sep 17 00:00:00 2001 From: Valmo Date: Tue, 30 Jun 2026 02:39:24 -0300 Subject: [PATCH] fixed sqlite lock --- src/redis_rtsp.rs | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/redis_rtsp.rs b/src/redis_rtsp.rs index 143d6b0..71e3778 100644 --- a/src/redis_rtsp.rs +++ b/src/redis_rtsp.rs @@ -486,6 +486,9 @@ async fn redis_cli(redis_url: &str, args: &[&str]) -> Result { async fn init_sqlite(sqlite_path: &Path) -> Result<()> { let sql = r#" + PRAGMA journal_mode = WAL; + PRAGMA synchronous = NORMAL; + PRAGMA busy_timeout = 5000; CREATE TABLE IF NOT EXISTS rtsp_feed_results ( id INTEGER PRIMARY KEY AUTOINCREMENT, checked_at INTEGER NOT NULL, @@ -520,13 +523,22 @@ async fn insert_sqlite_result( status = sql_quote(&status), error = sql_nullable_quote(error), ); - sqlite_exec(sqlite_path, &sql).await + sqlite_exec_with_retry(sqlite_path, &sql).await } async fn sqlite_exec(sqlite_path: &Path, sql: &str) -> Result<()> { let output = timeout(Duration::from_secs(20), async { let mut command = Command::new("sqlite3"); - command.kill_on_drop(true).arg(sqlite_path).arg(sql); + command + .kill_on_drop(true) + .arg("-cmd") + .arg("PRAGMA busy_timeout = 5000;") + .arg("-cmd") + .arg("PRAGMA journal_mode = WAL;") + .arg("-cmd") + .arg("PRAGMA synchronous = NORMAL;") + .arg(sqlite_path) + .arg(sql); command.output().await }) .await @@ -547,6 +559,28 @@ async fn sqlite_exec(sqlite_path: &Path, sql: &str) -> Result<()> { Ok(()) } +async fn sqlite_exec_with_retry(sqlite_path: &Path, sql: &str) -> Result<()> { + let mut last_err: Option = None; + + for attempt in 0..5 { + match sqlite_exec(sqlite_path, sql).await { + Ok(()) => return Ok(()), + Err(err) if is_sqlite_locked(&err) && attempt < 4 => { + last_err = Some(err); + sleep(Duration::from_millis(200 * (attempt + 1) as u64)).await; + } + Err(err) => return Err(err), + } + } + + Err(last_err.unwrap_or_else(|| anyhow!("sqlite3 failed after retries"))) +} + +fn is_sqlite_locked(err: &anyhow::Error) -> bool { + let text = err.to_string().to_ascii_lowercase(); + text.contains("database is locked") || text.contains("busy") +} + fn load_ip_list(input: &str) -> Result> { let content = match fs::read_to_string(input) { Ok(content) => content,