{"record":{"id":"674d328861e1df85","repo":"Zackriya-Solutions/meetily","slug":"download-worker-ended-before-completing-cancellation-cleanup","errorCode":null,"errorMessage":"Download worker ended before completing cancellation cleanup","messagePattern":"Download worker ended before completing cancellation cleanup","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs","lineNumber":1220,"sourceCode":"        &self,\n        model_name: &str,\n        cleanup_timeout: Duration,\n    ) -> Result<CancelDownloadOutcome> {\n        let active_download = {\n            let active_downloads = self.active_downloads.lock().await;\n            let Some(active_download) = active_downloads.downloads.get(model_name).cloned() else {\n                return Ok(CancelDownloadOutcome::Cancelled);\n            };\n            active_download.cancellation.cancel();\n            active_download\n        };\n\n        let mut completion = active_download.completion.subscribe();\n        if !*completion.borrow() {\n            match timeout(cleanup_timeout, completion.changed()).await {\n                Ok(Ok(())) => {}\n                Ok(Err(_)) => {\n                    return Err(anyhow!(\n                        \"Download worker ended before completing cancellation cleanup\"\n                    ));\n                }\n                Err(_) => return Ok(CancelDownloadOutcome::Pending),\n            }\n        }\n\n        Ok(CancelDownloadOutcome::Cancelled)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crossbeam::queue::SegQueue;\n    use tempfile::tempdir;\n    use tokio::io::{AsyncReadExt, AsyncWriteExt};\n    use tokio::net::TcpListener;","sourceCodeStart":1202,"sourceCodeEnd":1238,"githubUrl":"https://github.com/Zackriya-Solutions/meetily/blob/a2cb62e827da7ef59f65064c97233efb2313878e/frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs#L1202-L1238","documentation":"During cancel_download cleanup, the engine subscribes to the download worker's completion watch channel. If completion.changed() returns Err, it means the watch channel's sender was dropped — i.e., the download worker task terminated without ever setting the completion flag, so cancellation cleanup can never be confirmed.","triggerScenarios":"In cancel_download, while waiting on active_download.completion.changed() within cleanup_timeout, the watch sender is dropped because the download worker task panicked, was aborted, or exited early without marking completion — the Ok(Err(_)) branch.","commonSituations":"The download worker panicked mid-download (e.g., a bug or OOM kill of the task); the task holding the watch sender was aborted after cancellation; the worker exited early on an unhandled error path without setting completion=true, leaving the cleanup watcher observing a closed channel.","solutions":["Retry the cancel operation; if the outcome is still broken, restart the app to reset download worker state.","Check Rust logs for a panic or abort in the download worker task to identify the root cause.","Ensure the worker sets completion=true (or drops the sender only after setting it) in all exit paths, including error paths.","Reduce race windows: only trigger cancel after the download has started reporting progress, or make the worker's completion channel a shutdown-safe signal."],"exampleFix":"// before\nOk(Err(_)) => {\n    return Err(anyhow!(\"Download worker ended before completing cancellation cleanup\"));\n}\n// after (treat closed channel as cleanup finished if the worker exited deliberately)\nOk(Err(_)) => {\n    if worker_join_handle.is_finished() {\n        return Ok(CancelDownloadOutcome::Cancelled);\n    }\n    return Err(anyhow!(\"Download worker ended before completing cancellation cleanup\"));\n}","handlingStrategy":"try-catch","validationCode":"// Before cancelling, check the worker is still alive\nif worker_handle.is_finished() {\n    eprintln!(\"worker already exited; skip cleanup wait\");\n}","typeGuard":"// Rust: check the watch channel is still connected before waiting\nif active_download.completion.sender().is_some() {\n    // safe to await completion.changed()\n}","tryCatchPattern":"// Rust\nmatch timeout(cleanup_timeout, completion.changed()).await {\n    Ok(Ok(())) => Ok(CancelDownloadOutcome::Cancelled),\n    Ok(Err(_)) => {\n        log::warn!(\"completion sender dropped; treating as worker exit\");\n        Ok(CancelDownloadOutcome::Cancelled) // or surface to user with retry hint\n    }\n    Err(_) => Ok(CancelDownloadOutcome::Pending),\n}","preventionTips":["Set the completion flag in every exit path of the download worker (success, error, panic hook).","Wrap worker bodies with catch_unwind or spawn a supervision task to mark completion on panic.","Drop the watch sender only after setting completion=true.","Check Rust logs for worker panics/aborts when cancellation behaves unexpectedly."],"tags":["concurrency","async","rust","cancellation"],"backgroundTag":"invalid-state-transition","analyzedSha":"a2cb62e827da7ef59f65064c97233efb2313878e","analyzedAt":"2026-09-12T11:12:14.152Z","contentChangedAt":"2026-09-12T11:12:14.152Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}