From 4643f0e49a230c985b489970fdd9ca7c502eb0be Mon Sep 17 00:00:00 2001 From: oomeow Date: Fri, 18 Sep 2026 21:26:31 +0800 Subject: [PATCH] fix(core): drain sidecar events before readiness probe to avoid startup stall --- src-tauri/src/core/manager/state.rs | 78 +++++++++++++++++------------ 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/src-tauri/src/core/manager/state.rs b/src-tauri/src/core/manager/state.rs index 996e062ef..9ce2f2367 100644 --- a/src-tauri/src/core/manager/state.rs +++ b/src-tauri/src/core/manager/state.rs @@ -12,7 +12,13 @@ use anyhow::{Context as _, Result}; use clash_verge_logging::Type; use log::Level; use scopeguard::defer; -use std::path::Path; +use std::{ + path::Path, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; use tauri_plugin_mihomo::MihomoExt as _; use tauri_plugin_shell::ShellExt as _; @@ -208,6 +214,45 @@ impl CoreManager { let pid = child.pid(); tracing::Span::current().record("pid", pid); + // The sidecar has to be drained from the moment it starts. tauri-plugin-shell buffers a + // single event, so an unread channel stalls its reader threads and the core blocks on a + // full stdout pipe before it ever creates the API socket. + let core_readiness_generation = Arc::new(AtomicU64::new(0)); + let generation_slot = Arc::clone(&core_readiness_generation); + AsyncHandler::spawn(move || async move { + while let Some(event) = rx.recv().await { + match event { + tauri_plugin_shell::process::CommandEvent::Stdout(line) + | tauri_plugin_shell::process::CommandEvent::Stderr(line) => { + let message = String::from_utf8_lossy(&line).into_owned(); + Logger::global().writer_sidecar_log(Level::Error, &message); + CLASH_LOGGER.append_log(message); + } + tauri_plugin_shell::process::CommandEvent::Terminated(term) => { + let manager = Self::global(); + // The generation is only published once the core is actually ready, so a + // termination before that must not invalidate a later core's readiness. + let generation = generation_slot.load(Ordering::Acquire); + if generation != 0 { + let _ = manager.invalidate_core_readiness_if(generation); + } + let message = if let Some(code) = term.code { + format!("Process terminated with code: {}", code) + } else if let Some(signal) = term.signal { + format!("Process terminated by signal: {}", signal) + } else { + String::from("Process terminated") + }; + Logger::global().writer_sidecar_log(Level::Info, &message); + CLASH_LOGGER.clear_logs(); + manager.clear_terminated_sidecar(pid).await; + break; + } + _ => {} + } + } + }); + let readiness = poll_sidecar_readiness(SIDECAR_READINESS_ATTEMPTS, SIDECAR_READINESS_INTERVAL, || async { tokio::time::timeout(SIDECAR_READINESS_PROBE_TIMEOUT, async { handle::Handle::mihomo().get_version().await @@ -231,39 +276,10 @@ impl CoreManager { #[cfg(target_os = "windows")] self.set_job_handle(Some(job)); self.set_running_child_sidecar(child); - let core_readiness_generation = self.mark_core_ready(); + core_readiness_generation.store(self.mark_core_ready(), Ordering::Release); self.core_started(RunningMode::Sidecar); self.restore_selected_nodes().await; - AsyncHandler::spawn(move || async move { - while let Some(event) = rx.recv().await { - match event { - tauri_plugin_shell::process::CommandEvent::Stdout(line) - | tauri_plugin_shell::process::CommandEvent::Stderr(line) => { - let message = String::from_utf8_lossy(&line).into_owned(); - Logger::global().writer_sidecar_log(Level::Error, &message); - CLASH_LOGGER.append_log(message); - } - tauri_plugin_shell::process::CommandEvent::Terminated(term) => { - let manager = Self::global(); - let _ = manager.invalidate_core_readiness_if(core_readiness_generation); - let message = if let Some(code) = term.code { - format!("Process terminated with code: {}", code) - } else if let Some(signal) = term.signal { - format!("Process terminated by signal: {}", signal) - } else { - String::from("Process terminated") - }; - Logger::global().writer_sidecar_log(Level::Info, &message); - CLASH_LOGGER.clear_logs(); - manager.clear_terminated_sidecar(pid).await; - break; - } - _ => {} - } - } - }); - Ok(()) }