{"record":{"id":"dd498e6d85aaea4e","repo":"lencx/ChatGPT","slug":"view-download-failed-to-lock-download-path","errorCode":null,"errorMessage":"[view:download] Failed to lock download path","messagePattern":"\\[view:download\\] Failed to lock download path","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/core/setup.rs","lineNumber":71,"sourceCode":"            // Wrap the window in Arc<Mutex<_>> to manage ownership across threads\n            let window = Arc::new(Mutex::new(core_window));\n\n            let main_view =\n                WebviewBuilder::new(\"main\", WebviewUrl::App(\"https://chatgpt.com\".into()))\n                    .auto_resize()\n                    .on_download({\n                        let app_handle = handle.clone();\n                        let download_path = Arc::new(Mutex::new(PathBuf::new()));\n                        move |_, event| {\n                            match event {\n                                DownloadEvent::Requested { destination, .. } => {\n                                    let download_dir = app_handle\n                                        .path()\n                                        .download_dir()\n                                        .expect(\"[view:download] Failed to get download directory\");\n                                    let mut locked_path = download_path\n                                        .lock()\n                                        .expect(\"[view:download] Failed to lock download path\");\n                                    *locked_path = download_dir.join(&destination);\n                                    *destination = locked_path.clone();\n                                }\n                                DownloadEvent::Finished { success, .. } => {\n                                    let final_path = download_path\n                                        .lock()\n                                        .expect(\"[view:download] Failed to lock download path\")\n                                        .clone();\n\n                                    if success {\n                                        app_handle\n                                            .shell()\n                                            .open(final_path.to_string_lossy(), None)\n                                            .expect(\"[view:download] Failed to open file\");\n                                    }\n                                }\n                                _ => (),\n                            }","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/lencx/ChatGPT/blob/a6de9a8b61077fa63ad5c3ecbe2bc12e0cb4b4b9/src-tauri/src/core/setup.rs#L53-L89","documentation":"Panic from download_path.lock() returning Err(PoisonError) in the DownloadEvent::Requested arm. A std::sync::Mutex is poisoned when any thread panics while holding the guard; every later lock() then returns Err until recovered. The shared Arc<Mutex<PathBuf>> tracks the in-flight download path between the Requested and Finished events, and the other arms contain their own .expect() calls that can panic while state is being updated.","triggerScenarios":"A panic anywhere while the download_path guard is held (e.g. a later event handler in the same closure panicking mid-update) poisons the mutex; then any subsequent download (next Requested event) hits lock().expect() and panics again, so downloads permanently fail until app restart.","commonSituations":"A first download triggers the Failed-to-open-file or Failed-to-get-download-directory panic; the user retries the download and immediately hits the poisoned lock; long-running desktop sessions where one poisoned shared mutex quietly disables the whole download feature.","solutions":["Recover from poisoning with match on lock() and poisoned.into_inner(), since a PathBuf is always safe to reuse even after a panic","Eliminate the panics in the other arms (download_dir, shell open) so the mutex never gets poisoned in the first place","Replace Mutex<PathBuf> with std::sync::atomic/lighter state or std::sync::mpsc if poisoning risk stays","Run download-path updates with lock().unwrap_or_else(|p| p.into_inner()) at every call site"],"exampleFix":"// before\nlet mut locked_path = download_path\n    .lock()\n    .expect(\"[view:download] Failed to lock download path\");\n\n// after\nlet mut locked_path = match download_path.lock() {\n    Ok(guard) => guard,\n    Err(poisoned) => poisoned.into_inner(),\n};","handlingStrategy":"fallback","validationCode":"// health check before mutating shared state\nfn lock_path(m: &Mutex<PathBuf>) -> std::sync::MutexGuard<PathBuf> {\n    m.lock().unwrap_or_else(|poisoned| poisoned.into_inner())\n}","typeGuard":"fn is_healthy(m: &Mutex<PathBuf>) -> bool {\n    !m.is_poisoned()\n}","tryCatchPattern":"let mut locked_path = match download_path.lock() {\n    Ok(guard) => guard,\n    Err(poisoned) => poisoned.into_inner(),\n};","preventionTips":["Remove every .expect()/unwrap that can panic while the guard is held — poison starts with a panic","Keep mutex critical sections short: clone data out, drop the guard, then do fallible work","For simple state like PathBuf, always recover via into_inner() instead of propagating poison"],"tags":["rust","mutex","poisoning","download","concurrency"],"backgroundTag":null,"analyzedSha":"a6de9a8b61077fa63ad5c3ecbe2bc12e0cb4b4b9","analyzedAt":"2026-08-16T08:31:13.240Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}