mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
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:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1285,9 +1285,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"clash-verge-logging",
|
||||
"log",
|
||||
"tauri",
|
||||
"tokio",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
- 优化应用重启/退出时的资源清理性能, 大幅缩短执行时间
|
||||
- 优化 WebSocket 连接机制
|
||||
- 改进旧版 Service 需要重新安装检测流程
|
||||
- 优化 macOS 和 Linux 信号处理
|
||||
- 优化 macOS, Linux 和 Windows 信号处理
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user