{"record":{"id":"7bd7754ad627302c","repo":"libnyanpasu/clash-nyanpasu","slug":"failed-to-wait-for-widget-process","errorCode":null,"errorMessage":"Failed to wait for widget process: {}","messagePattern":"Failed to wait for widget process: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"backend/tauri/src/widget.rs","lineNumber":135,"sourceCode":"            .stdout(os_pipe::dup_stdout()?)\n            .stderr(os_pipe::dup_stderr()?)\n            .spawn()\n            .context(\"Failed to spawn widget process\")?;\n        tracing::debug!(\"Waiting for widget process to start...\");\n        let tx = tokio::select! {\n            res = tokio::task::spawn_blocking(move || {\n                ipc_server\n                    .connect()\n                    .context(\"Failed to connect to widget\")?;\n                ipc_server.into_tx().context(\"Failed to get ipc sender\")\n            }) => res.context(\"Failed to get ipc sender\")??,\n            res = child.wait() => {\n                match res {\n                    Ok(status) => {\n                        return Err(anyhow::anyhow!(\"Widget process exited: {}\", status));\n                    }\n                    Err(e) => {\n                        return Err(anyhow::anyhow!(\"Failed to wait for widget process: {}\", e));\n                    }\n                }\n            }\n        };\n        instance.replace(WidgetManagerInstance { tx, process: child });\n        Ok(())\n    }\n\n    pub async fn stop(&self) -> anyhow::Result<()> {\n        let Some(mut instance) = self.instance.lock().await.take() else {\n            tracing::debug!(\"Widget instance is not exists, skipping...\");\n            return Ok(());\n        };\n        if !instance.is_alive() {\n            tracing::debug!(\"Widget instance is not alive, skipping...\");\n            return Ok(());\n        }\n        // first try to stop the process gracefully","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/widget.rs#L117-L153","documentation":"In WidgetManager::start, the `child.wait()` future itself failed (not the child exiting normally) — tokio reported an OS-level error waiting for the spawned widget process (e.g. waitpid failure or the child was already reaped). The manager returns this wrapped error instead of an IPC sender.","triggerScenarios":"tokio::process::Child::wait() returns Err during the select! race in start(), e.g. the child's stdin/stdout handles are invalid, the process was already awaited elsewhere, or an OS waitpid error occurs.","commonSituations":"Calling start() twice concurrently so two wait() calls race on the same Child; platform quirks after fork/exec failure; killing the process externally in a way tokio surfaces as a wait error rather than a status.","solutions":["Ensure start()/stop() are serialized — the manager locks self.instance but avoid calling start concurrently from multiple tasks","Check the inner error `e` for the concrete OS cause (e.g. 'No child processes') indicating double-wait or SIGCHLD handling issues","Verify no other code awaits or takes the same tokio::process::Child (only one owner allowed)","Retry start() once the environment is stable; the instance slot remains empty on failure"],"exampleFix":"// before\nErr(anyhow::anyhow!(\"Failed to wait for widget process: {}\", e))\n// after\nErr(anyhow::anyhow!(\"Failed to wait for widget process: {e:#}\"))","handlingStrategy":"try-catch","validationCode":"// serialize start/stop through a mutex and ensure no concurrent waiter exists\nif start_in_flight.swap(true, Ordering::SeqCst) {\n    anyhow::bail!(\"widget start already in progress\");\n}","typeGuard":null,"tryCatchPattern":"match manager.start(variant).await {\n    Err(e) if e.to_string().contains(\"Failed to wait for widget process\") => {\n        log::error!(\"tokio wait failure: {e:#}; check for double-wait/concurrent start\");\n    }\n    Err(e) => return Err(e),\n    Ok(()) => {}\n}","preventionTips":["Never call start() from multiple tasks concurrently; serialize via the instance lock or a supervisor task","Ensure the tokio::process::Child has exactly one owner awaiting it","Keep tokio runtime versions consistent to avoid waitpid quirks"],"tags":["process","tokio","rust"],"backgroundTag":"process-exited-unexpectedly","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}