Recover from mutex poisoning in db_pool
Move the lock() call into db_pool() itself, using unwrap_or_else(|e| e.into_inner()) so a panic in one task cannot cascade into a server-wide crash for all subsequent DB requests. Call sites now receive a MutexGuard directly.
change
commit a494e1185c90eefca20164212f106f4e00f918cf
author Claude <noreply@anthropic.com>
date
parent c1291a16
diff --git a/quire-server/src/quire/mod.rs b/quire-server/src/quire/mod.rs
index 8b7ad3b..2333701 100644
--- a/quire-server/src/quire/mod.rs
+++ b/quire-server/src/quire/mod.rs
@@ -270,11 +270,12 @@ impl Quire {
     ///
     /// Lazily initialises the connection on first call. Once open, the
     /// same connection is reused for all subsequent requests.
-    pub fn db_pool(&self) -> &Mutex<rusqlite::Connection> {
-        self.db_pool.get_or_init(|| {
+    pub fn db_pool(&self) -> std::sync::MutexGuard<'_, rusqlite::Connection> {
+        let mutex = self.db_pool.get_or_init(|| {
             let conn = crate::db::open(&self.db_path()).expect("failed to open database");
             Mutex::new(conn)
-        })
+        });
+        mutex.lock().unwrap_or_else(|e| e.into_inner())
     }
 
     /// Validate a repository name and return its resolved path.
diff --git a/quire-server/src/quire/web/api.rs b/quire-server/src/quire/web/api.rs
index 2dfca7a..b639ce7 100644
--- a/quire-server/src/quire/web/api.rs
+++ b/quire-server/src/quire/web/api.rs
@@ -113,7 +113,7 @@ async fn verify_run_token(
     let token = bearer.token().to_string();
 
     let run_id = tokio::task::spawn_blocking(move || -> Result<String, ApiError> {
-        let db = quire.db_pool().lock().expect("db mutex poisoned");
+        let db = quire.db_pool();
         let result: rusqlite::Result<String> = db.query_row(
             "SELECT id FROM runs WHERE run_token = ?1",
             rusqlite::params![token],
diff --git a/quire-server/src/quire/web/db.rs b/quire-server/src/quire/web/db.rs
index 74682fb..8777b93 100644
--- a/quire-server/src/quire/web/db.rs
+++ b/quire-server/src/quire/web/db.rs
@@ -32,10 +32,7 @@ pub struct ShEvent {
 }
 
 pub fn load_runs(quire: &Quire, repo: &str) -> Result<Vec<RunRow>> {
-    let db = quire
-        .db_pool()
-        .lock()
-        .map_err(|_| crate::error::Error::Io(std::io::Error::other("db mutex poisoned")))?;
+    let db = quire.db_pool();
     let mut stmt = db.prepare(
         "SELECT id, outcome, sha, ref_name, created_at, dispatched_at, resolved_at
          FROM runs WHERE repo = ?1
@@ -68,10 +65,7 @@ pub struct RunDetail {
 }
 
 pub fn load_run_detail(quire: &Quire, repo: &str, run_id: &str) -> Result<RunDetail> {
-    let db = quire
-        .db_pool()
-        .lock()
-        .map_err(|_| crate::error::Error::Io(std::io::Error::other("db mutex poisoned")))?;
+    let db = quire.db_pool();
 
     let run = db.query_row(
         "SELECT id, outcome, sha, ref_name, created_at, dispatched_at, resolved_at
diff --git a/quire-server/src/quire/web/handlers.rs b/quire-server/src/quire/web/handlers.rs
index 2eedbba..c0c2b26 100644
--- a/quire-server/src/quire/web/handlers.rs
+++ b/quire-server/src/quire/web/handlers.rs
@@ -322,8 +322,7 @@ mod tests {
             dispatched: Option<i64>,
             resolved: Option<i64>,
         ) {
-            let pool = self.quire.db_pool();
-            let db = pool.lock().expect("lock");
+            let db = self.quire.db_pool();
             db.execute(
                 "INSERT INTO runs (id, repo, ref_name, sha, pushed_at_ms,
                                   created_at, dispatched_at, resolved_at, outcome)
@@ -342,8 +341,7 @@ mod tests {
             started: Option<i64>,
             finished: Option<i64>,
         ) {
-            let pool = self.quire.db_pool();
-            let db = pool.lock().expect("lock");
+            let db = self.quire.db_pool();
             db.execute(
                 "INSERT INTO jobs (run_id, job_id, state, exit_code, started_at_ms, finished_at_ms)
                  VALUES (?1, ?2, ?3, ?4, ?5, ?6)",