From 3d2507430b739b8dbf62cf2d8b3bfe495065acd0 Mon Sep 17 00:00:00 2001 From: Sline Date: Sat, 11 Oct 2025 16:49:47 +0800 Subject: [PATCH] fix(shutdown): mark shutdown as exiting to stop background tasks (#5024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(shutdown): mark shutdown as exiting to stop background tasks - lib.rs:570 → Flag app as exiting on ExitRequested, notify proxy guard, start cleanup immediately, with fallback in Exit event - tray/mod.rs:190 → Add unified exit checks around tray init/updates to prevent UI recreation during shutdown - event_driven_proxy.rs:252 → Ensure proxy guard skips all restore/re-enable work (including sysproxy.exe calls) once exit flag is set * fix(shutdown): refine exit handling and proxy guard notifications * fix(shutdown): add guard to run shutdown routine only once per lifecycle --- src-tauri/src/core/event_driven_proxy.rs | 49 +++++++++++++++++++++++- src-tauri/src/core/tray/mod.rs | 48 +++++++++++++++++++++++ src-tauri/src/feat/window.rs | 12 +++--- src-tauri/src/lib.rs | 19 ++++++--- 4 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src-tauri/src/core/event_driven_proxy.rs b/src-tauri/src/core/event_driven_proxy.rs index 0ceb077ed..43efc3dac 100644 --- a/src-tauri/src/core/event_driven_proxy.rs +++ b/src-tauri/src/core/event_driven_proxy.rs @@ -5,7 +5,7 @@ use tokio::time::{Duration, sleep, timeout}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use crate::config::{Config, IVerge}; -use crate::core::async_proxy_query::AsyncProxyQuery; +use crate::core::{async_proxy_query::AsyncProxyQuery, handle}; use crate::logging_error; use crate::process::AsyncHandler; use crate::utils::logging::Type; @@ -250,6 +250,12 @@ impl EventDrivenProxyManager { } ProxyEvent::AppStopping => { log::info!(target: "app", "清理代理状态"); + Self::update_state_timestamp(state, |s| { + s.sys_enabled = false; + s.pac_enabled = false; + s.is_healthy = false; + }) + .await; } } } @@ -301,6 +307,10 @@ impl EventDrivenProxyManager { } async fn check_and_restore_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理守卫检查"); + return; + } let (sys_enabled, pac_enabled) = { let s = state.read().await; (s.sys_enabled, s.pac_enabled) @@ -320,6 +330,11 @@ impl EventDrivenProxyManager { } async fn check_and_restore_pac_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过PAC代理恢复检查"); + return; + } + let current = Self::get_auto_proxy_with_timeout().await; let expected = Self::get_expected_pac_config().await; @@ -346,6 +361,11 @@ impl EventDrivenProxyManager { } async fn check_and_restore_sys_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理恢复检查"); + return; + } + let current = Self::get_sys_proxy_with_timeout().await; let expected = Self::get_expected_sys_proxy().await; @@ -374,6 +394,11 @@ impl EventDrivenProxyManager { } async fn enable_system_proxy(state: &Arc>) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过启用系统代理"); + return; + } + log::info!(target: "app", "启用系统代理"); let pac_enabled = state.read().await.pac_enabled; @@ -407,6 +432,11 @@ impl EventDrivenProxyManager { } async fn switch_proxy_mode(state: &Arc>, to_pac: bool) { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过代理模式切换"); + return; + } + log::info!(target: "app", "切换到{}模式", if to_pac { "PAC" } else { "HTTP代理" }); if to_pac { @@ -545,6 +575,10 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn restore_pac_proxy(expected_url: &str) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过PAC代理恢复"); + return Ok(()); + } Self::execute_sysproxy_command(&["pac", expected_url]).await } @@ -565,6 +599,10 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn restore_sys_proxy(expected: &Sysproxy) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过系统代理恢复"); + return Ok(()); + } let address = format!("{}:{}", expected.host, expected.port); Self::execute_sysproxy_command(&["global", &address, &expected.bypass]).await } @@ -582,6 +620,15 @@ impl EventDrivenProxyManager { #[cfg(target_os = "windows")] async fn execute_sysproxy_command(args: &[&str]) -> Result<(), anyhow::Error> { + if handle::Handle::global().is_exiting() { + log::debug!( + target: "app", + "应用正在退出,取消调用 sysproxy.exe,参数: {:?}", + args + ); + return Ok(()); + } + use crate::utils::dirs; #[allow(unused_imports)] // creation_flags必须 use std::os::windows::process::CommandExt; diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 808ebcb1b..8535ba8dd 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -188,6 +188,11 @@ singleton_lazy!(Tray, TRAY, Tray::default); impl Tray { pub async fn init(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘初始化"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); match self.create_tray_from_handle(app_handle).await { @@ -206,6 +211,11 @@ impl Tray { /// 更新托盘点击行为 pub async fn update_click_behavior(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘点击行为更新"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); let tray_event = { Config::verge().await.latest_ref().tray_event.clone() }; let tray_event: String = tray_event.unwrap_or("main_window".into()); @@ -221,6 +231,10 @@ impl Tray { /// 更新托盘菜单 pub async fn update_menu(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘菜单更新"); + return Ok(()); + } // 调整最小更新间隔,确保状态及时刷新 const MIN_UPDATE_INTERVAL: Duration = Duration::from_millis(100); @@ -309,6 +323,11 @@ impl Tray { /// 更新托盘图标 #[cfg(target_os = "macos")] pub async fn update_icon(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘图标更新"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); let tray = match app_handle.tray_by_id("main") { @@ -340,6 +359,11 @@ impl Tray { #[cfg(not(target_os = "macos"))] pub async fn update_icon(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘图标更新"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); let tray = match app_handle.tray_by_id("main") { @@ -367,6 +391,11 @@ impl Tray { /// 更新托盘显示状态的函数 pub async fn update_tray_display(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘显示状态更新"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); let _tray = app_handle .tray_by_id("main") @@ -380,6 +409,11 @@ impl Tray { /// 更新托盘提示 pub async fn update_tooltip(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘提示更新"); + return Ok(()); + } + let app_handle = handle::Handle::app_handle(); let verge = Config::verge().await.latest_ref().clone(); @@ -438,6 +472,10 @@ impl Tray { } pub async fn update_part(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘局部更新"); + return Ok(()); + } // self.update_menu().await?; // 更新轻量模式显示状态 self.update_tray_display().await?; @@ -447,6 +485,11 @@ impl Tray { } pub async fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘创建"); + return Ok(()); + } + log::info!(target: "app", "正在从AppHandle创建系统托盘"); // 获取图标 @@ -524,6 +567,11 @@ impl Tray { // 托盘统一的状态更新函数 pub async fn update_all_states(&self) -> Result<()> { + if handle::Handle::global().is_exiting() { + log::debug!(target: "app", "应用正在退出,跳过托盘状态更新"); + return Ok(()); + } + // 确保所有状态更新完成 self.update_tray_display().await?; // self.update_menu().await?; diff --git a/src-tauri/src/feat/window.rs b/src-tauri/src/feat/window.rs index fa53e2c15..2717e040f 100644 --- a/src-tauri/src/feat/window.rs +++ b/src-tauri/src/feat/window.rs @@ -1,12 +1,9 @@ +use crate::config::Config; +use crate::core::event_driven_proxy::EventDrivenProxyManager; +use crate::core::{CoreManager, handle, sysopt}; use crate::utils; use crate::utils::window_manager::WindowManager; -use crate::{ - config::Config, - core::{CoreManager, handle, sysopt}, - logging, - module::lightweight, - utils::logging::Type, -}; +use crate::{logging, module::lightweight, utils::logging::Type}; /// Public API: open or close the dashboard pub async fn open_or_close_dashboard() { @@ -27,6 +24,7 @@ pub async fn quit() { // 获取应用句柄并设置退出标志 let app_handle = handle::Handle::app_handle(); handle::Handle::global().set_is_exiting(); + EventDrivenProxyManager::global().notify_app_stopping(); // 优先关闭窗口,提供立即反馈 if let Some(window) = handle::Handle::get_window() { diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 728cef030..f50ffe5ee 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -12,7 +12,7 @@ mod utils; #[cfg(target_os = "macos")] use crate::utils::window_manager::WindowManager; use crate::{ - core::{handle, hotkey}, + core::{EventDrivenProxyManager, handle, hotkey}, process::AsyncHandler, utils::{resolve, server}, }; @@ -584,11 +584,20 @@ pub fn run() { } } tauri::RunEvent::Exit => { - // Avoid duplicate cleanup - if core::handle::Handle::global().is_exiting() { - return; + let handle = core::handle::Handle::global(); + + if handle.is_exiting() { + logging!( + debug, + Type::System, + "Exit事件触发,但退出流程已执行,跳过重复清理" + ); + } else { + logging!(debug, Type::System, "Exit事件触发,执行清理流程"); + handle.set_is_exiting(); + EventDrivenProxyManager::global().notify_app_stopping(); + feat::clean(); } - feat::clean(); } tauri::RunEvent::WindowEvent { label, event, .. } => { if label == "main" {