From 9b9feb9a39fa9ce7f0a189030898081a783356be Mon Sep 17 00:00:00 2001 From: wonfen Date: Wed, 19 Aug 2026 19:53:32 +0800 Subject: [PATCH] fix: handle proxy cleanup failures during app exit --- Cargo.lock | 50 +++++++++---------- src-tauri/Cargo.toml | 2 +- src-tauri/src/core/sysopt.rs | 23 +++++++-- src-tauri/src/feat/clash.rs | 5 +- src-tauri/src/feat/window.rs | 21 +++++--- .../_layout/utils/notification-handlers.ts | 10 +++- 6 files changed, 70 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d1bfd4f1..bf402e028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1257,8 +1257,8 @@ dependencies = [ [[package]] name = "clash_verge_service_ipc" -version = "2.6.2" -source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc.git?tag=v2.6.2#edf7cb57811d4e572f1e2d607472cb87bd421ef4" +version = "2.6.3" +source = "git+https://github.com/clash-verge-rev/clash-verge-service-ipc.git?tag=v2.6.3#322bc6fad12538195aeaeb3711231d7523c88e6f" dependencies = [ "anyhow", "compact_str", @@ -1363,7 +1363,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -2044,7 +2044,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -2324,7 +2324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -3397,7 +3397,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3417,7 +3417,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core 0.61.2", ] [[package]] @@ -4241,7 +4241,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.19", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4410,7 +4410,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4830,7 +4830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.45.0", ] [[package]] @@ -5504,7 +5504,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.5", + "socket2 0.5.10", "thiserror 2.0.19", "tokio", "tracing", @@ -5543,9 +5543,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.5", + "socket2 0.5.10", "tracing", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6093,7 +6093,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6106,7 +6106,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6165,7 +6165,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -6796,7 +6796,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -7009,8 +7009,8 @@ dependencies = [ [[package]] name = "sysproxy" -version = "0.6.0" -source = "git+https://github.com/clash-verge-rev/sysproxy-rs?branch=main#16500c3e91a4fa8d541108fb603c5ee4258004a6" +version = "0.6.1" +source = "git+https://github.com/clash-verge-rev/sysproxy-rs?branch=main#c63788cf88e3deac1087d759c16543235d66b921" dependencies = [ "iptools", "log", @@ -7683,10 +7683,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -8405,7 +8405,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.19", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -8508,7 +8508,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -9093,7 +9093,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] @@ -9697,7 +9697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10" dependencies = [ "cfg-if", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 10e05ce26..92bb99929 100755 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -96,7 +96,7 @@ tauri-plugin-devtools = { version = "2.1.0" } tauri-plugin-mihomo = { git = "https://github.com/clash-verge-rev/tauri-plugin-mihomo", branch = "refactor-use-reqwest-ipc" } clash_verge_logger = { git = "https://github.com/clash-verge-rev/clash-verge-logger" } async-trait = "0.1.92" -clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.6.2", version = "2.6.2", features = [ +clash_verge_service_ipc = { git = "https://github.com/clash-verge-rev/clash-verge-service-ipc.git", tag = "v2.6.3", version = "2.6.3", features = [ "client", ] } arc-swap = "1.9.2" diff --git a/src-tauri/src/core/sysopt.rs b/src-tauri/src/core/sysopt.rs index fc83d76f8..852805e70 100644 --- a/src-tauri/src/core/sysopt.rs +++ b/src-tauri/src/core/sysopt.rs @@ -170,12 +170,25 @@ where first_failure([("global proxy", global), ("PAC", pac)]) } +/// Treats a missing network service as a completed disable: with nothing to write to, the +/// requested "no proxy" state already holds. Turning a proxy on must still fail. +fn tolerate_missing_network_service(enabling: bool, outcome: sysproxy::Result<()>) -> Result<()> { + match outcome { + #[cfg(target_os = "macos")] + Err(sysproxy::Error::NoActiveNetworkService) if !enabling => { + logging!(warn, Type::Core, "no active network service, nothing to turn off"); + Ok(()) + } + other => other.map_err(anyhow::Error::from), + } +} + /// Force both proxy kinds off, in one blocking hop. async fn disable_all_proxies(sys: Sysproxy, auto: Autoproxy) -> Result<()> { tokio::task::spawn_blocking(move || { disable_both( - || sys.set_system_proxy().map_err(anyhow::Error::from), - || auto.set_auto_proxy().map_err(anyhow::Error::from), + || tolerate_missing_network_service(sys.enable, sys.set_system_proxy()), + || tolerate_missing_network_service(auto.enable, auto.set_auto_proxy()), ) }) .await? @@ -472,11 +485,11 @@ impl Sysopt { let applied = tokio::task::spawn_blocking(move || { for (index, step) in apply_steps.into_iter().enumerate() { let written = match step { - ProxyApplyStep::Autoproxy => auto.set_auto_proxy(), - ProxyApplyStep::Sysproxy => sys.set_system_proxy(), + ProxyApplyStep::Autoproxy => tolerate_missing_network_service(auto.enable, auto.set_auto_proxy()), + ProxyApplyStep::Sysproxy => tolerate_missing_network_service(sys.enable, sys.set_system_proxy()), }; if let Err(error) = written { - return Err((index > 0, anyhow::Error::from(error))); + return Err((index > 0, error)); } } Ok(()) diff --git a/src-tauri/src/feat/clash.rs b/src-tauri/src/feat/clash.rs index b20a04c5c..b9749b077 100644 --- a/src-tauri/src/feat/clash.rs +++ b/src-tauri/src/feat/clash.rs @@ -54,7 +54,10 @@ pub async fn restart_app() { if !cleanup_result.core_stopped { handle::Handle::global().clear_is_exiting(); - handle::Handle::notice_message("app_restart::core_stop_failed", ""); + handle::Handle::notice_message( + "app_restart::core_stop_failed", + cleanup_result.stop_error.unwrap_or_default(), + ); return; } diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index af03ee58d..5190e13f6 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -4,14 +4,17 @@ use crate::module::lightweight; use crate::utils; use crate::utils::window_manager::WindowManager; use clash_verge_logging::{Type, logging}; +use parking_lot::Mutex; use tokio::time::Duration; #[cfg(target_os = "macos")] use tokio::time::timeout; -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Default)] pub struct CleanupResult { pub all_success: bool, pub core_stopped: bool, + /// Why the stop failed, for the cancelled-exit notice. + pub stop_error: Option, } const fn should_abort_exit_after_cleanup(core_stopped: bool) -> bool { @@ -29,14 +32,12 @@ where AncillaryFuture: std::future::Future, { if !stop_core().await { - return CleanupResult { - all_success: false, - core_stopped: false, - }; + return CleanupResult::default(); } CleanupResult { all_success: ancillary_cleanup().await, core_stopped: true, + stop_error: None, } } @@ -128,7 +129,10 @@ pub async fn quit() -> clash_verge_signal::ShutdownOutcome { if should_abort_exit_after_cleanup(cleanup_result.core_stopped) { handle::Handle::global().clear_is_exiting(); - handle::Handle::notice_message("app_quit::core_stop_failed", ""); + handle::Handle::notice_message( + "app_quit::core_stop_failed", + cleanup_result.stop_error.unwrap_or_default(), + ); return clash_verge_signal::ShutdownOutcome::Canceled; } @@ -145,7 +149,8 @@ pub async fn clean_async() -> CleanupResult { "Starting interactive cleanup; controlled core stop will be awaited to completion" ); - let result = run_interactive_cleanup_transition( + let stop_error = Mutex::new(None); + let mut result = run_interactive_cleanup_transition( || async { logging!(info, Type::System, "Stopping core for interactive quit or restart"); match CoreManager::global().stop_core().await { @@ -159,6 +164,7 @@ pub async fn clean_async() -> CleanupResult { Type::Window, "Controlled core stop failed; interactive quit or restart must remain cancelled: {error:#}" ); + *stop_error.lock() = Some(format!("{error:#}")); false } } @@ -166,6 +172,7 @@ pub async fn clean_async() -> CleanupResult { restore_dns_after_core_stop, ) .await; + result.stop_error = stop_error.into_inner(); logging!( info, diff --git a/src/pages/_layout/utils/notification-handlers.ts b/src/pages/_layout/utils/notification-handlers.ts index b8930dc46..95b40831a 100644 --- a/src/pages/_layout/utils/notification-handlers.ts +++ b/src/pages/_layout/utils/notification-handlers.ts @@ -33,9 +33,15 @@ export const handleNoticeMessage = ( 'settings.sections.system.notifications.tunMode.autoDisableFailed', ), 'app_restart::core_stop_failed': () => - showNotice.error('layout.feedback.errors.restartCoreStopFailed'), + showNotice.error( + 'layout.feedback.errors.restartCoreStopFailed', + msg || undefined, + ), 'app_quit::core_stop_failed': () => - showNotice.error('layout.feedback.errors.quitCoreStopFailed'), + showNotice.error( + 'layout.feedback.errors.quitCoreStopFailed', + msg || undefined, + ), update_with_clash_proxy: () => showNotice.success( 'settings.feedback.notifications.updater.withClashProxySuccess',