refactor(async): migrate from sync-blocking async execution to true async with unified AsyncHandler::spawn (#4502)

* feat: replace all tokio::spawn with unified AsyncHandler::spawn

- 🚀 Core Improvements:
  * Replace all tokio::spawn calls with AsyncHandler::spawn for unified Tauri async task management
  * Prioritize converting sync functions to async functions to reduce spawn usage
  * Use .await directly in async contexts instead of spawn

- 🔧 Major Changes:
  * core/hotkey.rs: Use AsyncHandler::spawn for hotkey callback functions
  * module/lightweight.rs: Async lightweight mode switching
  * feat/window.rs: Convert window operation functions to async, use .await internally
  * feat/proxy.rs, feat/clash.rs: Async proxy and mode switching functions
  * lib.rs: Window focus handling with AsyncHandler::spawn
  * core/tray/mod.rs: Complete async tray event handling

-  Technical Advantages:
  * Unified task tracking and debugging capabilities (via tokio-trace feature)
  * Better error handling and task management
  * Consistency with Tauri runtime
  * Reduced async boundaries for better performance

- 🧪 Verification:
  * Compilation successful with 0 errors, 0 warnings
  * Maintains complete original functionality
  * Optimized async execution flow

* feat: complete tokio fs migration and replace tokio::spawn with AsyncHandler

🚀 Major achievements:
- Migrate 8 core modules from std::fs to tokio::fs
- Create 6 Send-safe wrapper functions using spawn_blocking pattern
- Replace all tokio::spawn calls with AsyncHandler::spawn for unified async task management
- Solve all 19 Send trait compilation errors through innovative spawn_blocking architecture

🔧 Core changes:
- config/profiles.rs: Add profiles_*_safe functions to handle Send trait constraints
- cmd/profile.rs: Update all Tauri commands to use Send-safe operations
- config/prfitem.rs: Replace append_item calls with profiles_append_item_safe
- utils/help.rs: Convert YAML operations to async (read_yaml, save_yaml)
- Multiple modules: Replace tokio::task::spawn_blocking with AsyncHandler::spawn_blocking

 Technical innovations:
- spawn_blocking wrapper pattern resolves parking_lot RwLock Send trait conflicts
- Maintain parking_lot performance while achieving Tauri async command compatibility
- Preserve backwards compatibility with gradual migration strategy

🎯 Results:
- Zero compilation errors
- Zero warnings
- All async file operations working correctly
- Complete Send trait compliance for Tauri commands

* feat: refactor app handle and command functions to use async/await for improved performance

* feat: update async handling in profiles and logging functions for improved error handling and performance

* fix: update TRACE_MINI_SIZE constant to improve task logging threshold

* fix(windows): convert service management functions to async for improved performance

* fix: convert service management functions to async for improved responsiveness

* fix(ubuntu): convert install and reinstall service functions to async for improved performance

* fix(linux): convert uninstall_service function to async for improved performance

* fix: convert uninstall_service call to async for improved performance

* fix: convert file and directory creation calls to async for improved performance

* fix: convert hotkey functions to async for improved responsiveness

* chore: update UPDATELOG.md for v2.4.1 with major improvements and performance optimizations
This commit is contained in:
Tunglies
2025-08-26 01:49:51 +08:00
committed by GitHub
parent 4598c805eb
commit 355a18e5eb
47 changed files with 2127 additions and 1809 deletions

View File

@@ -1,3 +1,4 @@
use crate::process::AsyncHandler;
use crate::utils::notification::{notify_event, NotificationEvent};
use crate::{
config::Config, core::handle, feat, logging, logging_error,
@@ -103,83 +104,86 @@ impl Hotkey {
}
/// Execute the function associated with a hotkey function enum
fn execute_function(function: HotkeyFunction, app_handle: Arc<AppHandle>) {
fn execute_function(function: HotkeyFunction, app_handle: &AppHandle) {
let app_handle = app_handle.clone();
match function {
HotkeyFunction::OpenOrCloseDashboard => {
logging!(
debug,
Type::Hotkey,
true,
"=== Hotkey Dashboard Window Operation Start ==="
);
logging!(
info,
Type::Hotkey,
true,
"Using unified WindowManager for hotkey operation (bypass debounce)"
);
crate::feat::open_or_close_dashboard_hotkey();
logging!(
debug,
Type::Hotkey,
"=== Hotkey Dashboard Window Operation End ==="
);
notify_event(app_handle, NotificationEvent::DashboardToggled);
AsyncHandler::spawn(async move || {
crate::feat::open_or_close_dashboard_hotkey().await;
notify_event(app_handle, NotificationEvent::DashboardToggled).await;
});
}
HotkeyFunction::ClashModeRule => {
feat::change_clash_mode("rule".into());
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Rule" },
);
AsyncHandler::spawn(async move || {
feat::change_clash_mode("rule".into()).await;
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Rule" },
)
.await;
});
}
HotkeyFunction::ClashModeGlobal => {
feat::change_clash_mode("global".into());
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Global" },
);
AsyncHandler::spawn(async move || {
feat::change_clash_mode("global".into()).await;
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Global" },
)
.await;
});
}
HotkeyFunction::ClashModeDirect => {
feat::change_clash_mode("direct".into());
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Direct" },
);
AsyncHandler::spawn(async move || {
feat::change_clash_mode("direct".into()).await;
notify_event(
app_handle,
NotificationEvent::ClashModeChanged { mode: "Direct" },
)
.await;
});
}
HotkeyFunction::ToggleSystemProxy => {
feat::toggle_system_proxy();
notify_event(app_handle, NotificationEvent::SystemProxyToggled);
AsyncHandler::spawn(async move || {
feat::toggle_system_proxy().await;
notify_event(app_handle, NotificationEvent::SystemProxyToggled).await;
});
}
HotkeyFunction::ToggleTunMode => {
feat::toggle_tun_mode(None);
notify_event(app_handle, NotificationEvent::TunModeToggled);
AsyncHandler::spawn(async move || {
feat::toggle_tun_mode(None).await;
notify_event(app_handle, NotificationEvent::TunModeToggled).await;
});
}
HotkeyFunction::EntryLightweightMode => {
entry_lightweight_mode();
notify_event(app_handle, NotificationEvent::LightweightModeEntered);
AsyncHandler::spawn(async move || {
entry_lightweight_mode().await;
notify_event(app_handle, NotificationEvent::LightweightModeEntered).await;
});
}
HotkeyFunction::Quit => {
notify_event(app_handle, NotificationEvent::AppQuit);
feat::quit();
AsyncHandler::spawn(async move || {
notify_event(app_handle, NotificationEvent::AppQuit).await;
feat::quit().await;
});
}
#[cfg(target_os = "macos")]
HotkeyFunction::Hide => {
feat::hide();
notify_event(app_handle, NotificationEvent::AppHidden);
AsyncHandler::spawn(async move || {
feat::hide().await;
notify_event(app_handle, NotificationEvent::AppHidden).await;
});
}
}
}
#[cfg(target_os = "macos")]
/// Register a system hotkey using enum
pub fn register_system_hotkey(&self, hotkey: SystemHotkey) -> Result<()> {
pub async fn register_system_hotkey(&self, hotkey: SystemHotkey) -> Result<()> {
let hotkey_str = hotkey.to_string();
let function = hotkey.function();
self.register_hotkey_with_function(&hotkey_str, function)
.await
}
#[cfg(target_os = "macos")]
@@ -190,7 +194,8 @@ impl Hotkey {
}
/// Register a hotkey with function enum
pub fn register_hotkey_with_function(
#[allow(clippy::unused_async)]
pub async fn register_hotkey_with_function(
&self,
hotkey: &str,
function: HotkeyFunction,
@@ -218,41 +223,55 @@ impl Hotkey {
manager.unregister(hotkey)?;
}
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| {
if event.state == ShortcutState::Pressed {
logging!(debug, Type::Hotkey, "Hotkey pressed: {:?}", hotkey_event);
let hotkey_event_owned = *hotkey_event;
let event_owned = event;
let function_owned = function;
let is_quit_owned = is_quit;
if hotkey_event.key == Code::KeyQ && is_quit {
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, Arc::clone(&app_handle_clone));
let app_handle_cloned = app_handle.clone();
AsyncHandler::spawn(move || async move {
if event_owned.state == ShortcutState::Pressed {
logging!(
debug,
Type::Hotkey,
"Hotkey pressed: {:?}",
hotkey_event_owned
);
if hotkey_event_owned.key == Code::KeyQ && is_quit_owned {
if let Some(window) = app_handle_cloned.get_webview_window("main") {
if window.is_focused().unwrap_or(false) {
logging!(debug, Type::Hotkey, "Executing quit function");
Self::execute_function(function_owned, &app_handle_cloned);
}
}
}
} else {
logging!(debug, Type::Hotkey, "Executing function directly");
let is_enable_global_hotkey = Config::verge()
.latest_ref()
.enable_global_hotkey
.unwrap_or(true);
if is_enable_global_hotkey {
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();
logging!(debug, Type::Hotkey, "Executing function directly");
if is_focused && is_visible {
Self::execute_function(function, Arc::clone(&app_handle_clone));
let is_enable_global_hotkey = Config::verge()
.await
.latest_ref()
.enable_global_hotkey
.unwrap_or(true);
if is_enable_global_hotkey {
Self::execute_function(function_owned, &app_handle_cloned);
} 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_owned, &app_handle_cloned);
}
}
}
}
}
});
});
logging!(
@@ -270,8 +289,8 @@ impl Hotkey {
singleton_with_logging!(Hotkey, INSTANCE, "Hotkey");
impl Hotkey {
pub fn init(&self) -> Result<()> {
let verge = Config::verge();
pub async fn init(&self) -> Result<()> {
let verge = Config::verge().await;
let enable_global_hotkey = verge.latest_ref().enable_global_hotkey.unwrap_or(true);
logging!(
@@ -286,7 +305,10 @@ impl Hotkey {
return Ok(());
}
if let Some(hotkeys) = verge.latest_ref().hotkeys.as_ref() {
// Extract hotkeys data before async operations
let hotkeys = verge.latest_ref().hotkeys.as_ref().cloned();
if let Some(hotkeys) = hotkeys {
logging!(
debug,
Type::Hotkey,
@@ -310,7 +332,7 @@ impl Hotkey {
key,
func
);
if let Err(e) = self.register(key, func) {
if let Err(e) = self.register(key, func).await {
logging!(
error,
Type::Hotkey,
@@ -344,7 +366,7 @@ impl Hotkey {
}
}
}
self.current.lock().clone_from(hotkeys);
self.current.lock().clone_from(&hotkeys);
} else {
logging!(debug, Type::Hotkey, "No hotkeys configured");
}
@@ -362,9 +384,9 @@ impl Hotkey {
}
/// Register a hotkey with string-based function (backward compatibility)
pub fn register(&self, hotkey: &str, func: &str) -> Result<()> {
pub async fn register(&self, hotkey: &str, func: &str) -> Result<()> {
let function = HotkeyFunction::from_str(func)?;
self.register_hotkey_with_function(hotkey, function)
self.register_hotkey_with_function(hotkey, function).await
}
pub fn unregister(&self, hotkey: &str) -> Result<()> {
@@ -377,9 +399,10 @@ impl Hotkey {
Ok(())
}
pub fn update(&self, new_hotkeys: Vec<String>) -> Result<()> {
let mut current = self.current.lock();
let old_map = Self::get_map_from_vec(&current);
pub async fn update(&self, new_hotkeys: Vec<String>) -> Result<()> {
// Extract current hotkeys before async operations
let current_hotkeys = self.current.lock().clone();
let old_map = Self::get_map_from_vec(&current_hotkeys);
let new_map = Self::get_map_from_vec(&new_hotkeys);
let (del, add) = Self::get_diff(old_map, new_map);
@@ -388,11 +411,12 @@ impl Hotkey {
let _ = self.unregister(key);
});
add.iter().for_each(|(key, func)| {
logging_error!(Type::Hotkey, self.register(key, func));
});
for (key, func) in add.iter() {
logging_error!(Type::Hotkey, self.register(key, func).await);
}
*current = new_hotkeys;
// Update the current hotkeys after all async operations
*self.current.lock() = new_hotkeys;
Ok(())
}