From 0d070fb934491b81b8277da57120cb760edf72ae Mon Sep 17 00:00:00 2001 From: Tunglies Date: Sat, 23 Aug 2025 00:20:58 +0800 Subject: [PATCH] refactor: update AppHandle usage to use Arc for improved memory management (#4491) * refactor: update AppHandle usage to use Arc for improved memory management * fix: clippy ci * fix: ensure default_latency_test is safely accessed with non-null assertion --- .github/workflows/lint-clippy.yml | 10 +-- src-tauri/src/cmd/app.rs | 4 +- src-tauri/src/cmd/profile.rs | 9 +-- src-tauri/src/core/handle.rs | 11 +-- src-tauri/src/core/hotkey.rs | 12 +-- src-tauri/src/core/tray/mod.rs | 11 +-- src-tauri/src/feat/profile.rs | 10 +-- src-tauri/src/lib.rs | 107 +++++++++++++------------ src-tauri/src/process/async_handler.rs | 41 ++++++++++ src-tauri/src/utils/help.rs | 2 +- src-tauri/src/utils/notification.rs | 6 +- src-tauri/src/utils/resolve.rs | 11 ++- src/components/proxy/proxy-head.tsx | 2 +- 13 files changed, 140 insertions(+), 96 deletions(-) diff --git a/.github/workflows/lint-clippy.yml b/.github/workflows/lint-clippy.yml index c8fe9cc38..b06151b1f 100644 --- a/.github/workflows/lint-clippy.yml +++ b/.github/workflows/lint-clippy.yml @@ -42,17 +42,17 @@ jobs: sudo apt-get update sudo apt-get install -y libxslt1.1 libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + - name: Install Node uses: actions/setup-node@v4 with: node-version: "22" cache: "pnpm" - - name: Install pnpm - uses: pnpm/action-setup@v4 - with: - run_install: false - - name: Pnpm install and check run: | pnpm i diff --git a/src-tauri/src/cmd/app.rs b/src-tauri/src/cmd/app.rs index cb8fd6a2e..1d2e54c80 100644 --- a/src-tauri/src/cmd/app.rs +++ b/src-tauri/src/cmd/app.rs @@ -4,7 +4,7 @@ use crate::{ utils::{dirs, logging::Type}, wrap_err, }; -use tauri::Manager; +use tauri::{AppHandle, Manager}; /// 打开应用程序所在目录 #[tauri::command] @@ -36,7 +36,7 @@ pub fn open_web_url(url: String) -> CmdResult<()> { /// 打开/关闭开发者工具 #[tauri::command] -pub fn open_devtools(app_handle: tauri::AppHandle) { +pub fn open_devtools(app_handle: AppHandle) { if let Some(window) = app_handle.get_webview_window("main") { if !window.is_devtools_open() { window.open_devtools(); diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index a57b30b6a..497cd604e 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -668,10 +668,7 @@ pub async fn patch_profiles_config(profiles: IProfiles) -> CmdResult { /// 根据profile name修改profiles #[tauri::command] -pub async fn patch_profiles_config_by_profile_index( - _app_handle: tauri::AppHandle, - profile_index: String, -) -> CmdResult { +pub async fn patch_profiles_config_by_profile_index(profile_index: String) -> CmdResult { logging!(info, Type::Cmd, true, "切换配置到: {}", profile_index); let profiles = IProfiles { @@ -718,7 +715,7 @@ pub fn patch_profile(index: String, profile: PrfItem) -> CmdResult { /// 查看配置文件 #[tauri::command] -pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult { +pub fn view_profile(index: String) -> CmdResult { let file = { wrap_err!(Config::profiles().latest_ref().get_item(&index))? .file @@ -731,7 +728,7 @@ pub fn view_profile(app_handle: tauri::AppHandle, index: String) -> CmdResult { ret_err!("the file not found"); } - wrap_err!(help::open_file(app_handle, path)) + wrap_err!(help::open_file(path)) } /// 读取配置文件内容 diff --git a/src-tauri/src/core/handle.rs b/src-tauri/src/core/handle.rs index 820dce991..add32c583 100644 --- a/src-tauri/src/core/handle.rs +++ b/src-tauri/src/core/handle.rs @@ -255,7 +255,7 @@ impl NotificationSystem { #[derive(Debug, Clone)] pub struct Handle { - pub app_handle: Arc>>, + pub app_handle: Arc>>>, pub is_exiting: Arc>, startup_errors: Arc>>, startup_completed: Arc>, @@ -282,10 +282,10 @@ impl Handle { Self::default() } - pub fn init(&self, app_handle: &AppHandle) { + pub fn init(&self, app_handle: Arc) { { let mut handle = self.app_handle.write(); - *handle = Some(app_handle.clone()); + *handle = Some(Arc::clone(&app_handle)); } let mut system_opt = self.notification_system.write(); @@ -294,8 +294,9 @@ impl Handle { } } - pub fn app_handle(&self) -> Option { - self.app_handle.read().clone() + /// 获取 AppHandle + pub fn app_handle(&self) -> Option> { + self.app_handle.read().as_ref().map(Arc::clone) } pub fn get_window(&self) -> Option { diff --git a/src-tauri/src/core/hotkey.rs b/src-tauri/src/core/hotkey.rs index 89ba08be4..a1bb2302c 100755 --- a/src-tauri/src/core/hotkey.rs +++ b/src-tauri/src/core/hotkey.rs @@ -6,7 +6,7 @@ use crate::{ use anyhow::{bail, Result}; use parking_lot::Mutex; use std::{collections::HashMap, fmt, str::FromStr, sync::Arc}; -use tauri::Manager; +use tauri::{AppHandle, Manager}; use tauri_plugin_global_shortcut::{Code, GlobalShortcutExt, ShortcutState}; /// Enum representing all available hotkey functions @@ -103,7 +103,7 @@ impl Hotkey { } /// Execute the function associated with a hotkey function enum - fn execute_function(function: HotkeyFunction, app_handle: &tauri::AppHandle) { + fn execute_function(function: HotkeyFunction, app_handle: Arc) { match function { HotkeyFunction::OpenOrCloseDashboard => { logging!( @@ -218,7 +218,7 @@ impl Hotkey { manager.unregister(hotkey)?; } - let app_handle_clone = app_handle.clone(); + let app_handle_clone = Arc::clone(&app_handle); let is_quit = matches!(function, HotkeyFunction::Quit); let _ = manager.on_shortcut(hotkey, move |app_handle, hotkey_event, event| { @@ -229,7 +229,7 @@ impl Hotkey { if let Some(window) = app_handle.get_webview_window("main") { if window.is_focused().unwrap_or(false) { logging!(debug, Type::Hotkey, "Executing quit function"); - Self::execute_function(function, &app_handle_clone); + Self::execute_function(function, Arc::clone(&app_handle_clone)); } } } else { @@ -241,14 +241,14 @@ impl Hotkey { .unwrap_or(true); if is_enable_global_hotkey { - Self::execute_function(function, &app_handle_clone); + Self::execute_function(function, Arc::clone(&app_handle_clone)); } else { use crate::utils::window_manager::WindowManager; let is_visible = WindowManager::is_main_window_visible(); let is_focused = WindowManager::is_main_window_focused(); if is_focused && is_visible { - Self::execute_function(function, &app_handle_clone); + Self::execute_function(function, Arc::clone(&app_handle_clone)); } } } diff --git a/src-tauri/src/core/tray/mod.rs b/src-tauri/src/core/tray/mod.rs index 74ebf61bf..79afc8ca1 100644 --- a/src-tauri/src/core/tray/mod.rs +++ b/src-tauri/src/core/tray/mod.rs @@ -15,6 +15,7 @@ use crate::{ use anyhow::Result; use parking_lot::Mutex; +use std::sync::Arc; use std::{ fs, sync::atomic::{AtomicBool, Ordering}, @@ -244,7 +245,7 @@ impl Tray { // 设置更新状态 self.menu_updating.store(true, Ordering::Release); - let result = self.update_menu_internal(&app_handle); + let result = self.update_menu_internal(app_handle); { let mut last_update = self.last_menu_update.lock(); @@ -255,7 +256,7 @@ impl Tray { result } - fn update_menu_internal(&self, app_handle: &AppHandle) -> Result<()> { + fn update_menu_internal(&self, app_handle: Arc) -> Result<()> { let verge = Config::verge().latest_ref().clone(); let system_proxy = verge.enable_system_proxy.as_ref().unwrap_or(&false); let tun_mode = verge.enable_tun_mode.as_ref().unwrap_or(&false); @@ -277,7 +278,7 @@ impl Tray { match app_handle.tray_by_id("main") { Some(tray) => { let _ = tray.set_menu(Some(create_tray_menu( - app_handle, + &app_handle, Some(mode.as_str()), *system_proxy, *tun_mode, @@ -451,7 +452,7 @@ impl Tray { #[cfg(target_os = "macos")] pub fn unsubscribe_traffic(&self) {} - pub fn create_tray_from_handle(&self, app_handle: &AppHandle) -> Result<()> { + pub fn create_tray_from_handle(&self, app_handle: Arc) -> Result<()> { log::info!(target: "app", "正在从AppHandle创建系统托盘"); // 获取图标 @@ -477,7 +478,7 @@ impl Tray { } } - let tray = builder.build(app_handle)?; + let tray = builder.build(app_handle.as_ref())?; tray.on_tray_icon_event(|_, event| { let tray_event = { Config::verge().latest_ref().tray_event.clone() }; diff --git a/src-tauri/src/feat/profile.rs b/src-tauri/src/feat/profile.rs index 893aa7e83..99fa1c40f 100644 --- a/src-tauri/src/feat/profile.rs +++ b/src-tauri/src/feat/profile.rs @@ -11,15 +11,7 @@ use anyhow::{bail, Result}; /// Toggle proxy profile pub fn toggle_proxy_profile(profile_index: String) { AsyncHandler::spawn(|| async move { - let Some(app_handle) = handle::Handle::global().app_handle() else { - logging!( - error, - Type::Config, - "Failed to get app handle for profile toggle" - ); - return; - }; - match cmd::patch_profiles_config_by_profile_index(app_handle, profile_index).await { + match cmd::patch_profiles_config_by_profile_index(profile_index).await { Ok(_) => { let _ = tray::Tray::global().update_menu(); } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 92f63ea1e..438fd662e 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -17,6 +17,7 @@ use crate::{ }; use config::Config; use parking_lot::Mutex; +use std::sync::Arc; use tauri::AppHandle; #[cfg(target_os = "macos")] use tauri::Manager; @@ -28,7 +29,7 @@ use utils::logging::Type; /// A global singleton handle to the application. pub struct AppHandleManager { - handle: Mutex>, + handle: Mutex>>, } impl AppHandleManager { @@ -40,7 +41,7 @@ impl AppHandleManager { } /// Initialize the app handle manager with an app handle. - pub fn init(&self, handle: AppHandle) { + pub fn init(&self, handle: Arc) { let mut app_handle = self.handle.lock(); if app_handle.is_none() { *app_handle = Some(handle); @@ -54,12 +55,12 @@ impl AppHandleManager { } /// Get the app handle if it has been initialized. - pub fn get(&self) -> Option { + fn get(&self) -> Option> { self.handle.lock().clone() } /// Get the app handle, panics if it hasn't been initialized. - pub fn get_handle(&self) -> AppHandle { + pub fn get_handle(&self) -> Arc { if let Some(handle) = self.get() { handle } else { @@ -246,12 +247,12 @@ mod app_init { } /// Initialize core components asynchronously - pub fn init_core_async(app_handle: tauri::AppHandle) { + pub fn init_core_async(app_handle: Arc) { AsyncHandler::spawn(move || async move { logging!(info, Type::Setup, true, "异步执行应用设置..."); match timeout( Duration::from_secs(30), - resolve::resolve_setup_async(&app_handle), + resolve::resolve_setup_async(app_handle), ) .await { @@ -271,12 +272,12 @@ mod app_init { } /// Initialize core components synchronously - pub fn init_core_sync(app_handle: &tauri::AppHandle) -> Result<(), Box> { + pub fn init_core_sync(app_handle: Arc) -> Result<(), Box> { logging!(info, Type::Setup, true, "初始化AppHandleManager..."); - AppHandleManager::global().init(app_handle.clone()); + AppHandleManager::global().init(Arc::clone(&app_handle)); logging!(info, Type::Setup, true, "初始化核心句柄..."); - core::handle::Handle::global().init(app_handle); + core::handle::Handle::global().init(Arc::clone(&app_handle)); logging!(info, Type::Setup, true, "初始化配置..."); utils::init::init_config()?; @@ -482,13 +483,16 @@ pub fn run() { ); } + let app_handle = app.handle().clone(); + let app_handle = Arc::new(app_handle); + // Initialize core components asynchronously - app_init::init_core_async(app.handle().clone()); + app_init::init_core_async(Arc::clone(&app_handle)); logging!(info, Type::Setup, true, "执行主要设置操作..."); // Initialize core components synchronously - if let Err(e) = app_init::init_core_sync(app.handle()) { + if let Err(e) = app_init::init_core_sync(Arc::clone(&app_handle)) { logging!( error, Type::Setup, @@ -509,9 +513,9 @@ pub fn run() { use super::*; /// Handle application ready/resumed events - pub fn handle_ready_resumed(app_handle: &tauri::AppHandle) { + pub fn handle_ready_resumed(app_handle: Arc) { logging!(info, Type::System, true, "应用就绪或恢复"); - AppHandleManager::global().init(app_handle.clone()); + AppHandleManager::global().init(Arc::clone(&app_handle)); #[cfg(target_os = "macos")] { @@ -524,7 +528,7 @@ pub fn run() { /// Handle application reopen events (macOS) #[cfg(target_os = "macos")] - pub fn handle_reopen(app_handle: &tauri::AppHandle, has_visible_windows: bool) { + pub fn handle_reopen(app_handle: Arc, has_visible_windows: bool) { logging!( info, Type::System, @@ -533,7 +537,7 @@ pub fn run() { has_visible_windows ); - AppHandleManager::global().init(app_handle.clone()); + AppHandleManager::global().init(Arc::clone(&app_handle)); if !has_visible_windows { // 当没有可见窗口时,设置为 regular 模式并显示主窗口 @@ -685,45 +689,48 @@ pub fn run() { std::process::exit(1); }); - app.run(|app_handle, e| match e { - tauri::RunEvent::Ready | tauri::RunEvent::Resumed => { - event_handlers::handle_ready_resumed(app_handle); - } - #[cfg(target_os = "macos")] - tauri::RunEvent::Reopen { - has_visible_windows, - .. - } => { - event_handlers::handle_reopen(app_handle, has_visible_windows); - } - tauri::RunEvent::ExitRequested { api, code, .. } => { - if code.is_none() { - api.prevent_exit(); + app.run(|app_handle, e| { + let app_handle = Arc::new(app_handle.clone()); + match e { + tauri::RunEvent::Ready | tauri::RunEvent::Resumed => { + event_handlers::handle_ready_resumed(Arc::clone(&app_handle)); } - } - tauri::RunEvent::Exit => { - // Avoid duplicate cleanup - if core::handle::Handle::global().is_exiting() { - return; + #[cfg(target_os = "macos")] + tauri::RunEvent::Reopen { + has_visible_windows, + .. + } => { + event_handlers::handle_reopen(app_handle, has_visible_windows); } - feat::clean(); - } - tauri::RunEvent::WindowEvent { label, event, .. } => { - if label == "main" { - match event { - tauri::WindowEvent::CloseRequested { .. } => { - event_handlers::handle_window_close(&event); - } - tauri::WindowEvent::Focused(focused) => { - event_handlers::handle_window_focus(focused); - } - tauri::WindowEvent::Destroyed => { - event_handlers::handle_window_destroyed(); - } - _ => {} + tauri::RunEvent::ExitRequested { api, code, .. } => { + if code.is_none() { + api.prevent_exit(); } } + tauri::RunEvent::Exit => { + // Avoid duplicate cleanup + if core::handle::Handle::global().is_exiting() { + return; + } + feat::clean(); + } + tauri::RunEvent::WindowEvent { label, event, .. } => { + if label == "main" { + match event { + tauri::WindowEvent::CloseRequested { .. } => { + event_handlers::handle_window_close(&event); + } + tauri::WindowEvent::Focused(focused) => { + event_handlers::handle_window_focus(focused); + } + tauri::WindowEvent::Destroyed => { + event_handlers::handle_window_destroyed(); + } + _ => {} + } + } + } + _ => {} } - _ => {} }); } diff --git a/src-tauri/src/process/async_handler.rs b/src-tauri/src/process/async_handler.rs index a8bad052f..539e3b0b7 100644 --- a/src-tauri/src/process/async_handler.rs +++ b/src-tauri/src/process/async_handler.rs @@ -1,22 +1,63 @@ +#[cfg(feature = "tokio-trace")] +use std::any::type_name; use std::future::Future; +#[cfg(feature = "tokio-trace")] +use std::panic::Location; use tauri::{async_runtime, async_runtime::JoinHandle}; pub struct AsyncHandler; impl AsyncHandler { + #[track_caller] pub fn spawn(f: F) -> JoinHandle<()> where F: FnOnce() -> Fut + Send + 'static, Fut: Future + Send + 'static, { + #[cfg(feature = "tokio-trace")] + Self::log_task_info(&f); async_runtime::spawn(f()) } + #[track_caller] pub fn spawn_blocking(f: F) -> JoinHandle where F: FnOnce() -> T + Send + 'static, T: Send + 'static, { + #[cfg(feature = "tokio-trace")] + Self::log_task_info(&f); async_runtime::spawn_blocking(f) } + + #[cfg(feature = "tokio-trace")] + #[track_caller] + fn log_task_info(f: &F) + where + F: ?Sized, + { + const TRACE_MINI_SIZE: usize = 0; + let size = std::mem::size_of_val(f); + if size <= TRACE_MINI_SIZE { + return; + } + + let location = Location::caller(); + let type_str = type_name::(); + let size_str = format!("{} bytes", size); + let loc_str = format!( + "{}:{}:{}", + location.file(), + location.line(), + location.column() + ); + + println!("┌────────────────────┬─────────────────────────────────────────────────────────────────────────────┐"); + println!("│ {:<18} │ {:<80} │", "Field", "Value"); + println!("├────────────────────┼─────────────────────────────────────────────────────────────────────────────┤"); + println!("│ {:<18} │ {:<80} │", "Type of task", type_str); + println!("│ {:<18} │ {:<80} │", "Size of task", size_str); + println!("│ {:<18} │ {:<80} │", "Called from", loc_str); + println!("└────────────────────┴─────────────────────────────────────────────────────────────────────────────┘"); + } } diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 4c30b0f1a..c3d231801 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -120,7 +120,7 @@ pub fn get_last_part_and_decode(url: &str) -> Option { } /// open file -pub fn open_file(_: tauri::AppHandle, path: PathBuf) -> Result<()> { +pub fn open_file(path: PathBuf) -> Result<()> { open::that_detached(path.as_os_str())?; Ok(()) } diff --git a/src-tauri/src/utils/notification.rs b/src-tauri/src/utils/notification.rs index 305e37e7d..71e32ff63 100644 --- a/src-tauri/src/utils/notification.rs +++ b/src-tauri/src/utils/notification.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use tauri::AppHandle; use tauri_plugin_notification::NotificationExt; @@ -14,7 +16,7 @@ pub enum NotificationEvent<'a> { AppHidden, } -fn notify(app: &AppHandle, title: &str, body: &str) { +fn notify(app: Arc, title: &str, body: &str) { app.notification() .builder() .title(title) @@ -23,7 +25,7 @@ fn notify(app: &AppHandle, title: &str, body: &str) { .ok(); } -pub fn notify_event(app: &AppHandle, event: NotificationEvent) { +pub fn notify_event(app: Arc, event: NotificationEvent) { use crate::utils::i18n::t; match event { NotificationEvent::DashboardToggled => { diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 663f04c71..f50b3a62e 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -14,7 +14,10 @@ use once_cell::sync::OnceCell; use parking_lot::{Mutex, RwLock}; use percent_encoding::percent_decode_str; use scopeguard; -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; use tauri::{AppHandle, Manager}; use tauri::Url; @@ -106,7 +109,7 @@ pub fn reset_ui_ready() { } /// 异步方式处理启动后的额外任务 -pub async fn resolve_setup_async(app_handle: &AppHandle) { +pub async fn resolve_setup_async(app_handle: Arc) { let start_time = std::time::Instant::now(); logging!( info, @@ -162,7 +165,7 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) { if let Some(app_handle) = handle::Handle::global().app_handle() { logging!(info, Type::Tray, true, "创建系统托盘..."); - let result = tray::Tray::global().create_tray_from_handle(&app_handle); + let result = tray::Tray::global().create_tray_from_handle(app_handle); if result.is_ok() { logging!(info, Type::Tray, true, "系统托盘创建成功"); } else if let Err(e) = result { @@ -329,7 +332,7 @@ pub fn create_window(is_show: bool) -> bool { }; match tauri::WebviewWindowBuilder::new( - &app_handle, + &*app_handle, "main", /* the unique window label */ tauri::WebviewUrl::App("index.html".into()), ) diff --git a/src/components/proxy/proxy-head.tsx b/src/components/proxy/proxy-head.tsx index 88e8595f2..205f729d3 100644 --- a/src/components/proxy/proxy-head.tsx +++ b/src/components/proxy/proxy-head.tsx @@ -48,7 +48,7 @@ export const ProxyHead = (props: Props) => { useEffect(() => { delayManager.setUrl( groupName, - testUrl || url || verge?.default_latency_test, + testUrl || url || verge?.default_latency_test!, ); }, [groupName, testUrl, verge?.default_latency_test]);