Transition a task to Cancelled status, setting `completed_at` to now. Works for both RUNNING and QUEUED tasks. Returns an error if the task is not found or is already in a terminal state.
(
&self,
id: &str,
)
| 469 | /// Works for both RUNNING and QUEUED tasks. Returns an error if the task |
| 470 | /// is not found or is already in a terminal state. |
| 471 | pub async fn mark_cancelled( |
| 472 | &self, |
| 473 | id: &str, |
| 474 | ) -> Result<Task, Box<dyn std::error::Error + Send + Sync>> { |
| 475 | let conn = Arc::clone(&self.conn); |
| 476 | let id = id.to_string(); |
| 477 | tokio::task::spawn_blocking(move || { |
| 478 | let conn = conn.lock().map_err(|e| format!("Lock error: {e}"))?; |
| 479 | let now = chrono::Utc::now().to_rfc3339(); |
| 480 | let rows_affected = conn.execute( |
| 481 | "UPDATE tasks SET status = 'CANCELLED', completed_at = ?1 WHERE id = ?2 AND status IN ('RUNNING', 'QUEUED')", |
| 482 | rusqlite::params![now, id], |
| 483 | )?; |
| 484 | if rows_affected == 0 { |
| 485 | // Check if task exists — if so, it's already terminal |
| 486 | let task = read_task_by_id(&conn, &id)?; |
| 487 | return Err(format!( |
| 488 | "Task {} is already in terminal state: {}", |
| 489 | id, |
| 490 | task_status_to_str(&task.status) |
| 491 | ).into()); |
| 492 | } |
| 493 | read_task_by_id(&conn, &id) |
| 494 | }) |
| 495 | .await? |
| 496 | } |
| 497 | |
| 498 | /// Reset a task to Queued status, clearing `started_at`, `completed_at`, and `error_message`. |
| 499 | pub async fn reset_to_queued( |