fix: decouple oneliner-heal from request handler tasks

heal_orphans_if_idle called regenerate_missing_oneliners_for_user
inline on the request task, blocking the browser for up to N×60s
when orphans existed (observed with 16 cases after manual cleanup).

Now: CAS on new OnelinerHealBusy flag + tokio::spawn + BusyGuard
reset-on-drop. Handler returns immediately; the existing
OnelinerUpdated SSE event delivers results via the usual reload.
Startup recovery shares the flag so a concurrent page-load during
boot cannot fire a parallel regen loop against Ollama.

Regression test uses wiremock + timeout(150ms) + .expect(5) to
verify both non-blocking return and dedup of parallel invocations.
This commit is contained in:
2026-04-21 12:56:02 +02:00
parent e5c8c5e3aa
commit 3d42d1da82
5 changed files with 193 additions and 16 deletions
+11
View File
@@ -116,6 +116,12 @@ async fn main() {
// subscribed browsers.
let events_tx = events::channel();
// Dedup flag shared by the startup oneliner-regen task and every
// request-driven `heal_orphans_if_idle` spawn. One-at-a-time semantics
// keep Ollama from seeing overlapping regen loops.
let oneliner_heal_busy: doctate_server::WorkerBusy =
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
// Transcription pipeline: channel + worker + recovery scan.
let (transcribe_tx, transcribe_rx) = transcribe::channel();
let transcribe_busy: doctate_server::WorkerBusy =
@@ -135,10 +141,14 @@ async fn main() {
let cfg = config.clone();
let vocab = vocab.clone();
let events = events_tx.clone();
let heal_busy = oneliner_heal_busy.clone();
tokio::spawn(async move {
transcribe::recovery::scan_and_enqueue(&data_path, &tx).await;
// Then catch up oneliners for cases that crashed between
// transcript-write and oneliner-write in a previous run.
// Hold the shared heal-busy flag while running so a concurrent
// page-load does not spawn a second parallel regen loop.
let _guard = doctate_server::BusyGuard::new(heal_busy);
transcribe::recovery::regenerate_missing_oneliners(
&data_path, &client, &cfg, &vocab, &events,
)
@@ -174,6 +184,7 @@ async fn main() {
magic_link_store: doctate_server::magic_link::new_store(),
analyze_busy: doctate_server::AnalyzeBusy(analyze_busy),
transcribe_busy: doctate_server::TranscribeBusy(transcribe_busy),
oneliner_heal_busy: doctate_server::OnelinerHealBusy(oneliner_heal_busy),
events_tx,
http_client,
vocab,