refactor(signal): migrate signal to handling async in windows and remove windows_sys dependency (#5741)

* refactor(signal): migrate signal to handling async in windows and remove signal-hook dependency

* refactor(signal): enhance Windows signal handling with improved cleanup logic

* refactor(signal): improve exit handling in run function for async compatibility
This commit is contained in:
Tunglies
2025-12-06 11:33:49 +08:00
committed by GitHub
parent 5797bb7f8c
commit 90b1c6e153
8 changed files with 120 additions and 178 deletions

2
Cargo.lock generated
View File

@@ -1285,9 +1285,7 @@ version = "0.1.0"
dependencies = [
"clash-verge-logging",
"log",
"tauri",
"tokio",
"windows-sys 0.61.2",
]
[[package]]

View File

@@ -61,7 +61,7 @@
- 优化应用重启/退出时的资源清理性能, 大幅缩短执行时间
- 优化 WebSocket 连接机制
- 改进旧版 Service 需要重新安装检测流程
- 优化 macOS Linux 信号处理
- 优化 macOS, Linux 和 Windows 信号处理
</details>

View File

@@ -9,15 +9,5 @@ clash-verge-logging = { workspace = true }
log = { workspace = true }
tokio = { workspace = true }
[target.'cfg(windows)'.dependencies]
tauri = { workspace = true }
windows-sys = { version = "0.61.2", features = [
"Win32_Foundation",
"Win32_Graphics_Gdi",
"Win32_System_SystemServices",
"Win32_UI_WindowsAndMessaging",
] }
[lints]
workspace = true

View File

@@ -9,7 +9,7 @@ mod windows;
pub(crate) static RUNTIME: OnceLock<Option<tokio::runtime::Runtime>> = OnceLock::new();
pub fn register<F, Fut>(#[cfg(windows)] app_handle: &tauri::AppHandle, f: F)
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
@@ -31,5 +31,5 @@ where
unix::register(f);
#[cfg(windows)]
windows::register(app_handle, f);
windows::register(f);
}

View File

@@ -1,8 +1,12 @@
use std::sync::atomic::{AtomicBool, Ordering};
use clash_verge_logging::{Type, logging};
use tokio::signal::unix::{SignalKind, signal};
use crate::RUNTIME;
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
@@ -64,6 +68,17 @@ where
}
}
if IS_CLEANING_UP.load(Ordering::SeqCst) {
logging!(
info,
Type::SystemSignal,
"Already shutting down, ignoring repeated signal: {}",
signal_name
);
continue;
}
IS_CLEANING_UP.store(true, Ordering::SeqCst);
logging!(info, Type::SystemSignal, "Caught signal {}", signal_name);
f().await;

View File

@@ -1,170 +1,114 @@
use std::{future::Future, pin::Pin, sync::OnceLock};
use tauri::{AppHandle, Manager as _};
use windows_sys::Win32::{
Foundation::{HWND, LPARAM, LRESULT, WPARAM},
UI::WindowsAndMessaging::{
CW_USEDEFAULT, CreateWindowExW, DefWindowProcW, DestroyWindow, RegisterClassW,
WM_ENDSESSION, WM_QUERYENDSESSION, WNDCLASSW, WS_EX_LAYERED, WS_EX_NOACTIVATE,
WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED,
},
};
use std::sync::atomic::{AtomicBool, Ordering};
use clash_verge_logging::{Type, logging};
use tokio::signal::windows;
use crate::RUNTIME;
// code refer to:
// global-hotkey (https://github.com/tauri-apps/global-hotkey)
// Global Shortcut (https://github.com/tauri-apps/plugins-workspace/tree/v2/plugins/global-shortcut)
static IS_CLEANING_UP: AtomicBool = AtomicBool::new(false);
type ShutdownHandler =
Box<dyn Fn() -> Pin<Box<dyn std::future::Future<Output = ()> + Send>> + Send + Sync>;
static SHUTDOWN_HANDLER: OnceLock<ShutdownHandler> = OnceLock::new();
struct ShutdownState {
hwnd: HWND,
}
unsafe impl Send for ShutdownState {}
unsafe impl Sync for ShutdownState {}
impl Drop for ShutdownState {
fn drop(&mut self) {
// this log not be printed, I don't know why.
logging!(info, Type::SystemSignal, "正在销毁系统关闭监听窗口");
unsafe {
DestroyWindow(self.hwnd);
}
}
}
unsafe extern "system" fn shutdown_proc(
hwnd: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
// refer: https://learn.microsoft.com/zh-cn/windows/win32/shutdown/shutting-down#shutdown-notifications
// only perform reset operations in `WM_ENDSESSION`
match msg {
WM_QUERYENDSESSION => {
logging!(
info,
Type::SystemSignal,
"System is shutting down or user is logging off."
);
}
WM_ENDSESSION => {
if let Some(handler) = SHUTDOWN_HANDLER.get() {
if let Some(Some(rt)) = RUNTIME.get() {
rt.block_on(async {
logging!(
info,
Type::SystemSignal,
"Session ended, system shutting down."
);
handler().await;
logging!(info, Type::SystemSignal, "resolved reset finished");
});
} else {
logging!(
error,
Type::SystemSignal,
"handle shutdown signal failed, RUNTIME is not available"
);
}
} else {
logging!(
error,
Type::SystemSignal,
"WM_ENDSESSION received but no shutdown handler is registered"
);
}
}
_ => {}
};
unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
}
fn encode_wide<S: AsRef<std::ffi::OsStr>>(string: S) -> Vec<u16> {
std::os::windows::prelude::OsStrExt::encode_wide(string.as_ref())
.chain(std::iter::once(0))
.collect::<Vec<u16>>()
}
fn get_instance_handle() -> windows_sys::Win32::Foundation::HMODULE {
// Gets the instance handle by taking the address of the
// pseudo-variable created by the microsoft linker:
// https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483
// This is preferred over GetModuleHandle(NULL) because it also works in DLLs:
// https://stackoverflow.com/questions/21718027/getmodulehandlenull-vs-hinstance
unsafe extern "C" {
static __ImageBase: windows_sys::Win32::System::SystemServices::IMAGE_DOS_HEADER;
}
unsafe { &__ImageBase as *const _ as _ }
}
//? 我们有机会采用类似 tokio 信号,不阻塞信号线程吗?
pub fn register<F, Fut>(app_handle: &AppHandle, f: F)
pub fn register<F, Fut>(f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
{
let _ = SHUTDOWN_HANDLER.set(Box::new(move || {
let fut = (f)();
Box::pin(async move {
fut.await;
}) as Pin<Box<dyn std::future::Future<Output = ()> + Send>>
}));
if let Some(Some(rt)) = RUNTIME.get() {
rt.spawn(async move {
let mut ctrl_c = match windows::ctrl_c() {
Ok(s) => s,
Err(e) => {
logging!(
error,
Type::SystemSignal,
"Failed to register Ctrl+C: {}",
e
);
return;
}
};
let class_name = encode_wide("global_shutdown_app");
unsafe {
let hinstance = get_instance_handle();
let mut ctrl_close = match windows::ctrl_close() {
Ok(s) => s,
Err(e) => {
logging!(
error,
Type::SystemSignal,
"Failed to register Ctrl+Close: {}",
e
);
return;
}
};
let wnd_class = WNDCLASSW {
lpfnWndProc: Some(shutdown_proc),
lpszClassName: class_name.as_ptr(),
hInstance: hinstance,
..std::mem::zeroed()
};
let mut ctrl_shutdown = match windows::ctrl_shutdown() {
Ok(s) => s,
Err(e) => {
logging!(
error,
Type::SystemSignal,
"Failed to register Ctrl+Shutdown: {}",
e
);
return;
}
};
RegisterClassW(&wnd_class);
let mut ctrl_logoff = match windows::ctrl_logoff() {
Ok(s) => s,
Err(e) => {
logging!(
error,
Type::SystemSignal,
"Failed to register Ctrl+Logoff: {}",
e
);
return;
}
};
let hwnd = CreateWindowExW(
WS_EX_NOACTIVATE | WS_EX_TRANSPARENT | WS_EX_LAYERED |
// WS_EX_TOOLWINDOW prevents this window from ever showing up in the taskbar, which
// we want to avoid. If you remove this style, this window won't show up in the
// taskbar *initially*, but it can show up at some later point. This can sometimes
// happen on its own after several hours have passed, although this has proven
// difficult to reproduce. Alternatively, it can be manually triggered by killing
// `explorer.exe` and then starting the process back up.
// It is unclear why the bug is triggered by waiting for several hours.
WS_EX_TOOLWINDOW,
class_name.as_ptr(),
std::ptr::null(),
WS_OVERLAPPED,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
hinstance,
std::ptr::null_mut(),
loop {
let signal_name;
tokio::select! {
_ = ctrl_c.recv() => {
signal_name = "Ctrl+C";
}
_ = ctrl_close.recv() => {
signal_name = "Ctrl+Close";
}
_ = ctrl_shutdown.recv() => {
signal_name = "Ctrl+Shutdown";
}
_ = ctrl_logoff.recv() => {
signal_name = "Ctrl+Logoff";
}
}
if IS_CLEANING_UP.load(Ordering::SeqCst) {
logging!(
info,
Type::SystemSignal,
"Already shutting down, ignoring repeated signal: {}",
signal_name
);
continue;
}
IS_CLEANING_UP.store(true, Ordering::SeqCst);
logging!(
info,
Type::SystemSignal,
"Caught Windows signal: {}",
signal_name
);
f().await;
}
});
} else {
logging!(
error,
Type::SystemSignal,
"register shutdown signal failed, RUNTIME is not available"
);
if hwnd.is_null() {
logging!(
error,
Type::SystemSignal,
"failed to create shutdown window"
);
} else {
app_handle.manage(ShutdownState { hwnd });
}
}
}

View File

@@ -405,21 +405,20 @@ pub fn run() {
event_handlers::handle_reopen(has_visible_windows).await;
});
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Exit => AsyncHandler::block_on(async {
if !handle::Handle::global().is_exiting() {
feat::quit().await;
}
}),
tauri::RunEvent::ExitRequested { api, code, .. } => {
AsyncHandler::block_on(async {
let _ = handle::Handle::mihomo().await.clear_all_ws_connections().await;
});
if core::handle::Handle::global().is_exiting() {
return;
}
AsyncHandler::block_on(async {
let _ = handle::Handle::mihomo().await.clear_all_ws_connections().await;
});
if code.is_none() {
api.prevent_exit();
}

View File

@@ -143,11 +143,7 @@ pub(super) async fn init_auto_backup() {
pub fn init_signal() {
logging!(info, Type::Setup, "Initializing signal handlers...");
clash_verge_signal::register(
#[cfg(windows)]
handle::Handle::app_handle(),
feat::quit,
);
clash_verge_signal::register(feat::quit);
}
pub async fn init_work_config() {