Transition a task to Failed status, setting `completed_at` to now and `error_message`. Guarded: only updates tasks with status RUNNING or QUEUED. If the task exists but is already in a terminal state (COMPLETE, FAILED, or CANCELLED), returns the current task as-is (no-op).
(
&self,
id: &str,
error_message: &str,
)
| 441 | /// but is already in a terminal state (COMPLETE, FAILED, or CANCELLED), returns |
| 442 | /// the current task as-is (no-op). |
| 443 | pub async fn mark_failed( |
| 444 | &self, |
| 445 | id: &str, |
| 446 | error_message: &str, |
| 447 | ) -> Result<Task, Box<dyn std::error::Error + Send + Sync>> { |
| 448 | let conn = Arc::clone(&self.conn); |
| 449 | let id = id.to_string(); |
| 450 | let error_message = error_message.to_string(); |
| 451 | tokio::task::spawn_blocking(move || { |
| 452 | let conn = conn.lock().map_err(|e| format!("Lock error: {e}"))?; |
| 453 | let now = chrono::Utc::now().to_rfc3339(); |
| 454 | let rows_affected = conn.execute( |
| 455 | "UPDATE tasks SET status = 'FAILED', completed_at = ?1, error_message = ?2 WHERE id = ?3 AND status IN ('RUNNING', 'QUEUED')", |
| 456 | rusqlite::params![now, error_message, id], |
| 457 | )?; |
| 458 | if rows_affected == 0 { |
| 459 | // Task may not exist, or may already be in a terminal state (e.g. CANCELLED) |
| 460 | return read_task_by_id(&conn, &id); |
| 461 | } |
| 462 | read_task_by_id(&conn, &id) |
| 463 | }) |
| 464 | .await? |
| 465 | } |
| 466 | |
| 467 | /// Transition a task to Cancelled status, setting `completed_at` to now. |
| 468 | /// |