From f40639c799fd2c9fed34699a88e7272d80135c76 Mon Sep 17 00:00:00 2001 From: YoursFunny Date: Sat, 8 Aug 2026 20:09:04 +0800 Subject: [PATCH] queue: back off 1s when a lease fails A lease error (e.g. persistent SQLITE_BUSY) while rows are due made the worker spin with sleep(0), hammering SQLite and flooding the log. lease_next now returns the error and the loop sleeps 1s before retrying. --- crates/xmedia-bot/src/queue.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/xmedia-bot/src/queue.rs b/crates/xmedia-bot/src/queue.rs index 1ebfdff..fa2fc0a 100644 --- a/crates/xmedia-bot/src/queue.rs +++ b/crates/xmedia-bot/src/queue.rs @@ -240,8 +240,8 @@ impl QueueWorker { async fn run_loop(self) { while !self.stop.load(Ordering::Relaxed) { match self.lease_next().await { - Some(row) => self.process(row).await, - None => { + Ok(Some(row)) => self.process(row).await, + Ok(None) => { let wait_until = self.earliest_run_after().await; let notified = self.notify.notified(); tokio::pin!(notified); @@ -258,13 +258,20 @@ impl QueueWorker { } } } + // A lease failure while rows are due would otherwise loop + // with sleep(0) and hammer SQLite; back off briefly. + Err(e) => { + log::error!("queue lease failed: {e}"); + tokio::time::sleep(Duration::from_secs(1)).await; + } } } } /// Leases the oldest due row (sets it `in_progress` with a lock TTL). - async fn lease_next(&self) -> Option { - let result = crate::db::with_conn(&self.db_path, |conn| { + /// Errors are surfaced so the caller can back off instead of spinning. + async fn lease_next(&self) -> Result, rusqlite::Error> { + crate::db::with_conn(&self.db_path, |conn| { // BEGIN IMMEDIATE: with several workers, a deferred transaction // that read before another worker's lease commit would fail with // SQLITE_BUSY_SNAPSHOT. Taking the write lock up front serializes @@ -302,14 +309,7 @@ impl QueueWorker { attempts, })) }) - .await; - match result { - Ok(row) => row, - Err(e) => { - log::error!("queue lease failed: {e}"); - None - } - } + .await } async fn earliest_run_after(&self) -> Option {