Move web-view DB access off the async runtime
Wrap load_runs and load_run_detail in tokio::task::spawn_blocking so
the synchronous rusqlite queries dont block tokio workers. Replace
expect("db mutex poisoned") with propagated errors that surface as
500s instead of panicking the worker.
Assisted-by: GLM-5.1 via pi
diff --git a/src/quire/web/db.rs b/src/quire/web/db.rs
index 647d606..20198f8 100644
--- a/src/quire/web/db.rs
+++ b/src/quire/web/db.rs
@@ -32,7 +32,10 @@ pub struct ShEvent {
}
pub fn load_runs(quire: &Quire, repo: &str) -> Result<Vec<RunRow>> {
- let db = quire.db_pool().lock().expect("db mutex poisoned");
+ let db = quire
+ .db_pool()
+ .lock()
+ .map_err(|_| crate::error::Error::Io(std::io::Error::other("db mutex poisoned")))?;
let mut stmt = db.prepare(
"SELECT id, state, sha, ref_name, queued_at_ms, started_at_ms, finished_at_ms
FROM runs WHERE repo = ?1
@@ -65,7 +68,10 @@ pub struct RunDetail {
}
pub fn load_run_detail(quire: &Quire, repo: &str, run_id: &str) -> Result<RunDetail> {
- let db = quire.db_pool().lock().expect("db mutex poisoned");
+ let db = quire
+ .db_pool()
+ .lock()
+ .map_err(|_| crate::error::Error::Io(std::io::Error::other("db mutex poisoned")))?;
let run = db.query_row(
"SELECT id, state, sha, ref_name, queued_at_ms, started_at_ms, finished_at_ms
diff --git a/src/quire/web/handlers.rs b/src/quire/web/handlers.rs
index 207546c..5ef3658 100644
--- a/src/quire/web/handlers.rs
+++ b/src/quire/web/handlers.rs
@@ -89,9 +89,10 @@ pub async fn run_list(State(quire): State<Quire>, AxumPath(repo): AxumPath<Strin
_ => return StatusCode::NOT_FOUND.into_response(),
};
- let runs = match db::load_runs(&quire, &repo_name) {
- Ok(r) => r,
- Err(e) => {
+ let q = quire.clone();
+ let runs = match tokio::task::spawn_blocking(move || db::load_runs(&q, &repo_name)).await {
+ Ok(Ok(r)) => r,
+ Ok(Err(e)) => {
tracing::error!(repo = %repo, error = %display_chain(&e), "failed to load runs");
return render_error(
repo_display,
@@ -100,6 +101,10 @@ pub async fn run_list(State(quire): State<Quire>, AxumPath(repo): AxumPath<Strin
display_chain(&e).to_string(),
);
}
+ Err(_) => {
+ tracing::error!("spawn_blocking task panicked");
+ return StatusCode::INTERNAL_SERVER_ERROR.into_response();
+ }
};
let template_runs: Vec<RunListRow> = runs
@@ -137,11 +142,14 @@ pub async fn run_detail(
return StatusCode::NOT_FOUND.into_response();
}
- let result = db::load_run_detail(&quire, &repo_name, &run_id);
- let detail = match result {
- Ok(d) => d,
- Err(ref e) if is_no_rows(e) => return StatusCode::NOT_FOUND.into_response(),
- Err(e) => {
+ let q = quire.clone();
+ let rn = repo_name.clone();
+ let ri = run_id.clone();
+ let detail = match tokio::task::spawn_blocking(move || db::load_run_detail(&q, &rn, &ri)).await
+ {
+ Ok(Ok(d)) => d,
+ Ok(Err(ref e)) if is_no_rows(e) => return StatusCode::NOT_FOUND.into_response(),
+ Ok(Err(e)) => {
tracing::error!(repo = %repo, run_id = %run_id, error = %display_chain(&e), "failed to load run detail");
return render_error(
repo_display,
@@ -150,6 +158,10 @@ pub async fn run_detail(
display_chain(&e).to_string(),
);
}
+ Err(_) => {
+ tracing::error!("spawn_blocking task panicked");
+ return StatusCode::INTERNAL_SERVER_ERROR.into_response();
+ }
};
let detail_run = DetailRun {