From 3ab5b223a8e83922fb50a979532f794cc210fad9 Mon Sep 17 00:00:00 2001 From: Froidefond Julien Date: Wed, 11 Mar 2026 15:50:11 +0100 Subject: [PATCH] =?UTF-8?q?fix(indexer):=20d=C3=A9tecter=20l'annulation=20?= =?UTF-8?q?de=20job=20pendant=20la=20phase=202=20(analyzer)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'analyzer ne vérifiait jamais le statut cancelled en DB, ce qui faisait continuer le traitement des thumbnails jusqu'au bout, puis écraser le statut 'cancelled' avec 'success'. Ajout d'un poller background toutes les 2s avec AtomicBool partagé pour stopper proprement le stream concurrent. Co-Authored-By: Claude Sonnet 4.6 --- apps/indexer/src/analyzer.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/indexer/src/analyzer.rs b/apps/indexer/src/analyzer.rs index dba467d..ecbfc32 100644 --- a/apps/indexer/src/analyzer.rs +++ b/apps/indexer/src/analyzer.rs @@ -4,12 +4,12 @@ use image::GenericImageView; use parsers::{analyze_book, BookFormat}; use sqlx::Row; use std::path::Path; -use std::sync::atomic::{AtomicI32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; use std::sync::Arc; use tracing::{info, warn}; use uuid::Uuid; -use crate::{utils, AppState}; +use crate::{job::is_job_cancelled, utils, AppState}; #[derive(Clone)] struct ThumbnailConfig { @@ -187,6 +187,24 @@ pub async fn analyze_library_books( .await; let processed_count = Arc::new(AtomicI32::new(0)); + let cancelled_flag = Arc::new(AtomicBool::new(false)); + + // Background task: poll DB every 2s to detect cancellation + let cancel_pool = state.pool.clone(); + let cancel_flag_for_poller = cancelled_flag.clone(); + let cancel_handle = tokio::spawn(async move { + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + match is_job_cancelled(&cancel_pool, job_id).await { + Ok(true) => { + cancel_flag_for_poller.store(true, Ordering::Relaxed); + break; + } + Ok(false) => {} + Err(_) => break, + } + } + }); struct BookTask { book_id: Uuid, @@ -208,8 +226,13 @@ pub async fn analyze_library_books( let processed_count = processed_count.clone(); let pool = state.pool.clone(); let config = config.clone(); + let cancelled = cancelled_flag.clone(); async move { + if cancelled.load(Ordering::Relaxed) { + return; + } + let local_path = utils::remap_libraries_path(&task.abs_path); let path = Path::new(&local_path); @@ -328,6 +351,13 @@ pub async fn analyze_library_books( }) .await; + cancel_handle.abort(); + + if cancelled_flag.load(Ordering::Relaxed) { + info!("[ANALYZER] Job {} cancelled by user, stopping analysis", job_id); + return Err(anyhow::anyhow!("Job cancelled by user")); + } + let final_count = processed_count.load(Ordering::Relaxed); info!( "[ANALYZER] Analysis complete: {}/{} books processed",