{"record":{"id":"9728a0009e3f1de7","repo":"Zackriya-Solutions/meetily","slug":"import-already-in-progress","errorCode":null,"errorMessage":"Import already in progress","messagePattern":"Import already in progress","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"frontend/src-tauri/src/audio/import.rs","lineNumber":263,"sourceCode":"    debug!(\n        \"Extracted metadata: {}Hz, {} frames, {:.2}s\",\n        sample_rate, n_frames, duration_seconds\n    );\n\n    Ok(duration_seconds)\n}\n\n/// Start import of an audio file\npub async fn start_import<R: Runtime>(\n    app: AppHandle<R>,\n    source_path: String,\n    title: String,\n    language: Option<String>,\n    model: Option<String>,\n    provider: Option<String>,\n) -> Result<ImportResult> {\n    // Acquire guard - ensures flag is cleared even on panic/early return\n    let _guard = ImportGuard::acquire().map_err(|e| anyhow!(e))?;\n\n    // Reset cancellation flag\n    IMPORT_CANCELLED.store(false, Ordering::SeqCst);\n\n    let use_parakeet = provider.as_deref() == Some(\"parakeet\");\n    let result = run_import(\n        app.clone(),\n        source_path,\n        title,\n        language,\n        model,\n        provider,\n    )\n    .await;\n\n    // Unload the engine after the batch job (success, failure, or cancellation)\n    super::common::unload_engine_after_batch(use_parakeet).await;\n","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/Zackriya-Solutions/meetily/blob/0281737d87d26352fb0adc78c8c0975f691b23d1/frontend/src-tauri/src/audio/import.rs#L245-L281","documentation":"start_import (import.rs:263) tries to acquire the ImportGuard via compare_exchange on the global IMPORT_IN_PROGRESS AtomicBool and loses the race - another import is currently running in this process. The guard is a deliberate single-flight mechanism (RAII Drop clears the flag, so even a panic cannot leave it stuck) preventing two imports from writing meetings/transcripts concurrently and contending for the whisper/parakeet engines.","triggerScenarios":"Double-clicking Import before the first click's async invoke disabled the button; frontend state desync - the button re-enabled after a transient render while the backend import still runs; invoking import_audio_file twice in quick succession (retry logic that does not await the first call); hot-reload re-mounting a component whose effect fires the import again.","commonSituations":"Janky UI where the disable-button effect lags the invoke; users spamming the button on slow disks; dev hot-reload double-firing effects; automated tests invoking import in parallel.","solutions":["Retry after the current import completes - the error is transient by design; subscribe to the import progress events to know when it is safe.","Make the frontend idempotent: disable the Import button synchronously on click and re-enable only on the terminal import-completed/import-error event.","If the UI believes nothing is running but the error persists, an import may still be finishing; wait for its event or restart the app (Drop guarantees the flag clears when the task ends).","For programmatic callers: await the first invocation's future before starting a second; never fire-and-forget import invokes.","Check app logs: 'Starting import for ...' lines show which import currently holds the guard."],"exampleFix":"// frontend: before - button stays enabled, double-click double-invokes\nconst onImport = () => invoke('import_audio_file', payload);\n\n// frontend: after - single-flight guard keyed to the running request\nconst importingRef = useRef(false);\nconst onImport = async () => {\n  if (importingRef.current) return;\n  importingRef.current = true;\n  try { await invoke('import_audio_file', payload); }\n  finally { importingRef.current = false; }\n};","handlingStrategy":"validation","validationCode":"// Frontend: single-flight guard so the second invoke never happens\nconst importingRef = useRef(false);\nasync function importFile(payload) {\n  if (importingRef.current) { setStatus('busy'); return; }\n  importingRef.current = true;\n  try { return await invoke('import_audio_file', payload); }\n  finally { importingRef.current = false; }\n}","typeGuard":null,"tryCatchPattern":"// Treat as transient: wait for the running import's terminal event, then retry once\ntry { await invoke('import_audio_file', payload); }\ncatch (e) {\n  if (String(e).includes('Import already in progress')) {\n    await waitForEvent('import-completed', 'import-error');\n    return invoke('import_audio_file', payload); // single retry after drain\n  }\n  throw e;\n}","preventionTips":["Disable the Import button synchronously on click; re-enable only on terminal import events.","Never fire-and-forget import invokes; always await.","In React, guard effects that auto-start imports with a ref/flag to survive StrictMode double-mounts.","Subscribe to import progress events so the UI always knows an import is running."],"tags":["import","concurrency","single-flight","race-condition","ui"],"backgroundTag":"duplicate-operation-in-progress","analyzedSha":"0281737d87d26352fb0adc78c8c0975f691b23d1","analyzedAt":"2026-08-16T20:57:52.567Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}