fix(backend): prevent db lock reentrancy in orchestrator notifications

This commit is contained in:
thibaud-lclr 2026-04-21 14:04:29 +02:00
parent 7f7e066e7c
commit ce37ce9ea0
2 changed files with 40 additions and 20 deletions

View file

@ -24,32 +24,50 @@ pub fn get_ticket_result(
#[tauri::command] #[tauri::command]
pub fn retry_ticket(state: State<'_, AppState>, ticket_id: String) -> Result<(), AppError> { pub fn retry_ticket(state: State<'_, AppState>, ticket_id: String) -> Result<(), AppError> {
let conn = state.db.lock().map_err(|e| AppError::from(e.to_string()))?; let active_worktree_cleanup: Option<(crate::models::worktree::Worktree, String)> = {
let ticket = ProcessedTicket::get_by_id(&conn, &ticket_id)?; let conn = state.db.lock().map_err(|e| AppError::from(e.to_string()))?;
let ticket = ProcessedTicket::get_by_id(&conn, &ticket_id)?;
if ticket.status != "Error" && ticket.status != "Done" && ticket.status != "Cancelled" { if ticket.status != "Error" && ticket.status != "Done" && ticket.status != "Cancelled" {
return Err(AppError::from(format!( return Err(AppError::from(format!(
"Cannot retry ticket with status '{}'", "Cannot retry ticket with status '{}'",
ticket.status ticket.status
))); )));
} }
ProcessedTicket::update_status(&conn, &ticket_id, "Pending")?; ProcessedTicket::update_status(&conn, &ticket_id, "Pending")?;
conn.execute( conn.execute(
"UPDATE processed_tickets SET analyst_report = NULL, developer_report = NULL, \ "UPDATE processed_tickets SET analyst_report = NULL, developer_report = NULL, \
worktree_path = NULL, branch_name = NULL, processed_at = NULL WHERE id = ?1", worktree_path = NULL, branch_name = NULL, processed_at = NULL WHERE id = ?1",
rusqlite::params![ticket_id], rusqlite::params![ticket_id],
)?; )?;
if let Some(wt) = Worktree::get_by_ticket_id(&conn, &ticket_id)? { let cleanup_target = if let Some(wt) = Worktree::get_by_ticket_id(&conn, &ticket_id)? {
if wt.status == "Active" {
let project = crate::models::project::Project::get_by_id(&conn, &ticket.project_id)?;
Some((wt, project.path))
} else {
Some((wt, String::new()))
}
} else {
None
};
cleanup_target
};
if let Some((wt, project_path)) = &active_worktree_cleanup {
if wt.status == "Active" { if wt.status == "Active" {
let project = crate::models::project::Project::get_by_id(&conn, &ticket.project_id)?;
let _ = crate::services::worktree_manager::delete_worktree( let _ = crate::services::worktree_manager::delete_worktree(
&project.path, project_path,
&wt.path, &wt.path,
&wt.branch_name, &wt.branch_name,
); );
} }
}
if let Some((wt, _)) = active_worktree_cleanup {
let conn = state.db.lock().map_err(|e| AppError::from(e.to_string()))?;
Worktree::delete(&conn, &wt.id)?; Worktree::delete(&conn, &wt.id)?;
} }

View file

@ -743,9 +743,11 @@ async fn process_ticket(
return Ok(true); return Ok(true);
} }
let conn = db.lock().map_err(|e| format!("DB lock: {}", e))?; {
ProcessedTicket::update_status(&conn, &ticket.id, "Done") let conn = db.lock().map_err(|e| format!("DB lock: {}", e))?;
.map_err(|e| format!("update_status: {}", e))?; ProcessedTicket::update_status(&conn, &ticket.id, "Done")
.map_err(|e| format!("update_status: {}", e))?;
}
let _ = app_handle.emit( let _ = app_handle.emit(
"ticket-processing-done", "ticket-processing-done",
serde_json::json!({ serde_json::json!({