Refactor busy flags and worker guards

The `WorkerBusy` type was previously a single `Arc<AtomicBool>` shared
between the analyze and transcribe workers. This commit refactors this
to:

- Introduce `AnalyzeBusy` and `TranscribeBusy` newtype wrappers around
  `WorkerBusy` to distinguish between the two flags. This allows
  `axum::extract::State` to target them individually.
- Move the `BusyGuard` RAII guard into `src/lib.rs` and make it generic
  to work with any `WorkerBusy` instance.
- Update the `main.rs`, `worker.rs`, and `routes/user_web.rs` files to
  use the new types and guards, ensuring correct state management for
  both pipelines.
- Enhance the transcription recovery scan
  (`transcribe::recovery::scan_and_enqueue`) to iterate over user
  directories and call `enqueue_pending_for_user` for each, making it
  more robust and aligned with the per-user self-healing mechanism.
- Add a check for `transcribe_busy` in the `case_detail.html` template
  to correctly display "Transkription läuft…" only when the transcribe
  worker is actually active.
This commit is contained in:
2026-04-16 00:32:45 +02:00
parent 9072d7a833
commit 2f62f11563
8 changed files with 175 additions and 118 deletions
+41 -9
View File
@@ -8,7 +8,7 @@ pub mod routes;
pub mod transcribe;
pub mod web_session;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use axum::extract::FromRef;
@@ -19,12 +19,36 @@ use config::Config;
use transcribe::TranscribeSender;
use web_session::SessionStore;
/// Live "is the analyze worker currently processing a job?" flag.
/// Set true before each `process()` call, false after. UI uses it to
/// distinguish a real in-flight analysis from a stale `analysis_input_v*.json`
/// orphan left over from a crash.
/// Live "is this worker currently processing a job?" flag.
/// Set true before each job, false after. UI uses it to distinguish a real
/// in-flight job from a stale on-disk marker (orphan input, missing
/// transcript) left over from a crash.
pub type WorkerBusy = Arc<AtomicBool>;
/// Newtype wrappers so `FromRef<AppState>` can target the two busy-flags
/// separately even though both are `Arc<AtomicBool>` underneath.
#[derive(Clone)]
pub struct AnalyzeBusy(pub WorkerBusy);
#[derive(Clone)]
pub struct TranscribeBusy(pub WorkerBusy);
/// RAII guard that flips a [`WorkerBusy`] true on construction and false on
/// drop — panic-safe. Shared across analyze + transcribe workers.
pub struct BusyGuard(WorkerBusy);
impl BusyGuard {
pub fn new(busy: WorkerBusy) -> Self {
busy.store(true, Ordering::Release);
Self(busy)
}
}
impl Drop for BusyGuard {
fn drop(&mut self) {
self.0.store(false, Ordering::Release);
}
}
/// Shared application state. Clonable — all fields are cheap to clone
/// (`Arc` and `mpsc::Sender`).
#[derive(Clone)]
@@ -33,7 +57,8 @@ pub struct AppState {
pub transcribe_tx: TranscribeSender,
pub analyze_tx: AnalyzeSender,
pub session_store: SessionStore,
pub worker_busy: WorkerBusy,
pub analyze_busy: AnalyzeBusy,
pub transcribe_busy: TranscribeBusy,
}
impl FromRef<AppState> for Arc<Config> {
@@ -60,9 +85,15 @@ impl FromRef<AppState> for SessionStore {
}
}
impl FromRef<AppState> for WorkerBusy {
impl FromRef<AppState> for AnalyzeBusy {
fn from_ref(state: &AppState) -> Self {
state.worker_busy.clone()
state.analyze_busy.clone()
}
}
impl FromRef<AppState> for TranscribeBusy {
fn from_ref(state: &AppState) -> Self {
state.transcribe_busy.clone()
}
}
@@ -77,7 +108,8 @@ pub fn create_router(config: Arc<Config>) -> Router {
transcribe_tx,
analyze_tx,
session_store: web_session::new_store(),
worker_busy: Arc::new(AtomicBool::new(false)),
analyze_busy: AnalyzeBusy(Arc::new(AtomicBool::new(false))),
transcribe_busy: TranscribeBusy(Arc::new(AtomicBool::new(false))),
})
}