mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 07:14:40 +08:00
fix: failed to receive shutdown signal on windows (#5533)
* fix: receive shutdown signal failed on windows * docs: update Changelog.md * chore: update * fix: use tokio runtime to handle shutdown signal * docs: update Changelog.md * fix: move tauri dependency to the correct section in Cargo.toml * fix: remove unused exit handling code in run function * fix(clash-verge-signal): use global runtime to avoid tokio runtime panic on unix * chore: update tauri-plugin-mihomo deps * fix: handle macOS exit event to ensure proper application termination --------- Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -1204,6 +1204,7 @@ dependencies = [
|
||||
"log",
|
||||
"signal-hook 0.3.18",
|
||||
"tauri",
|
||||
"tokio",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
@@ -7628,7 +7629,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "tauri-plugin-mihomo"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#1c2fba06b1e51eefd14b9f96310e75b9cdbf58fa"
|
||||
source = "git+https://github.com/clash-verge-rev/tauri-plugin-mihomo#24586eb0721314f88e65460b4ac01933b3376d3c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"futures-util",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- 仪表盘与托盘状态不同步
|
||||
- 修复重启或退出应用,关闭系统时无法记忆用户行为
|
||||
- 彻底修复 macOS 连接页面显示异常
|
||||
- windows 端监听关机信号失败
|
||||
|
||||
<details>
|
||||
<summary><strong> ✨ 新增功能 </strong></summary>
|
||||
@@ -39,6 +40,7 @@
|
||||
- 优化托盘菜单当前订阅检测逻辑
|
||||
- 优化连接页面表格渲染
|
||||
- 优化链式代理 UI 反馈
|
||||
- 优化重启应用的资源清理逻辑
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@ edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
clash-verge-logging = { workspace = true }
|
||||
log = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
signal-hook = "0.3.18"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
tauri = { workspace = true }
|
||||
windows-sys = { version = "0.61.2", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Graphics_Gdi",
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix;
|
||||
#[cfg(windows)]
|
||||
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)
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
RUNTIME.get_or_init(|| match tokio::runtime::Runtime::new() {
|
||||
Ok(rt) => Some(rt),
|
||||
Err(e) => {
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
"register shutdown signal failed, create tokio runtime error: {}",
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
#[cfg(unix)]
|
||||
unix::register(f);
|
||||
|
||||
|
||||
@@ -6,39 +6,49 @@ use signal_hook::{
|
||||
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
|
||||
use crate::RUNTIME;
|
||||
|
||||
pub fn register<F, Fut>(f: F)
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static,
|
||||
Fut: Future + Send + 'static,
|
||||
{
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let signals = [SIGTERM, SIGINT, SIGHUP];
|
||||
if let Some(Some(rt)) = RUNTIME.get() {
|
||||
rt.spawn(async move {
|
||||
let signals = [SIGTERM, SIGINT, SIGHUP];
|
||||
|
||||
let mut sigs = match Signals::new(signals) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "注册信号处理器失败: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
for signal in &mut sigs {
|
||||
let signal_to_str = |signal: i32| match signal {
|
||||
SIGTERM => "SIGTERM",
|
||||
SIGINT => "SIGINT",
|
||||
SIGHUP => "SIGHUP",
|
||||
_ => "UNKNOWN",
|
||||
let mut sigs = match Signals::new(signals) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
logging!(error, Type::System, "注册信号处理器失败: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
logging!(info, Type::System, "捕获到信号 {}", signal_to_str(signal));
|
||||
for signal in &mut sigs {
|
||||
let signal_to_str = |signal| match signal {
|
||||
SIGTERM => "SIGTERM",
|
||||
SIGINT => "SIGINT",
|
||||
SIGHUP => "SIGHUP",
|
||||
_ => "UNKNOWN",
|
||||
};
|
||||
|
||||
f().await;
|
||||
logging!(info, Type::System, "捕获到信号 {}", signal_to_str(signal));
|
||||
|
||||
logging_error!(
|
||||
Type::System,
|
||||
"信号 {:?} 默认处理失败",
|
||||
low_level::emulate_default_handler(signal)
|
||||
);
|
||||
}
|
||||
});
|
||||
f().await;
|
||||
|
||||
logging_error!(
|
||||
Type::System,
|
||||
"信号 {:?} 默认处理失败",
|
||||
low_level::emulate_default_handler(signal)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
Type::System,
|
||||
"register shutdown signal failed, RUNTIME is not available"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ use windows_sys::Win32::{
|
||||
|
||||
use clash_verge_logging::{Type, logging};
|
||||
|
||||
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)
|
||||
@@ -56,11 +58,19 @@ unsafe extern "system" fn shutdown_proc(
|
||||
}
|
||||
WM_ENDSESSION => {
|
||||
if let Some(handler) = SHUTDOWN_HANDLER.get() {
|
||||
tauri::async_runtime::block_on(async {
|
||||
logging!(info, Type::System, "Session ended, system shutting down.");
|
||||
handler().await;
|
||||
logging!(info, Type::System, "resolved reset finished");
|
||||
});
|
||||
if let Some(Some(rt)) = RUNTIME.get() {
|
||||
rt.block_on(async {
|
||||
logging!(info, Type::System, "Session ended, system shutting down.");
|
||||
handler().await;
|
||||
logging!(info, Type::System, "resolved reset finished");
|
||||
});
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
Type::System,
|
||||
"handle shutdown signal failed, RUNTIME is not available"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
logging!(
|
||||
error,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{CoreManager, handle, tray},
|
||||
feat::clean_async,
|
||||
process::AsyncHandler,
|
||||
utils::{self, resolve},
|
||||
utils,
|
||||
};
|
||||
use clash_verge_logging::{Type, logging, logging_error};
|
||||
use serde_yaml_ng::{Mapping, Value};
|
||||
@@ -24,16 +25,22 @@ pub async fn restart_clash_core() {
|
||||
|
||||
/// Restart the application
|
||||
pub async fn restart_app() {
|
||||
logging!(debug, Type::System, "启动重启应用流程");
|
||||
utils::server::shutdown_embedded_server();
|
||||
Config::apply_all_and_save_file().await;
|
||||
if let Err(err) = resolve::resolve_reset_async().await {
|
||||
handle::Handle::notice_message(
|
||||
"restart_app::error",
|
||||
format!("Failed to cleanup resources: {err}"),
|
||||
);
|
||||
logging!(error, Type::Core, "Restart failed during cleanup: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置退出标志
|
||||
handle::Handle::global().set_is_exiting();
|
||||
|
||||
logging!(info, Type::System, "开始异步清理资源");
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
logging!(
|
||||
info,
|
||||
Type::System,
|
||||
"资源清理完成,退出代码: {}",
|
||||
if cleanup_result { 0 } else { 1 }
|
||||
);
|
||||
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
app_handle.restart();
|
||||
|
||||
@@ -23,8 +23,7 @@ pub async fn quit() {
|
||||
utils::server::shutdown_embedded_server();
|
||||
Config::apply_all_and_save_file().await;
|
||||
|
||||
// 获取应用句柄并设置退出标志
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
// 设置退出标志
|
||||
handle::Handle::global().set_is_exiting();
|
||||
|
||||
logging!(info, Type::System, "开始异步清理资源");
|
||||
@@ -36,6 +35,8 @@ pub async fn quit() {
|
||||
"资源清理完成,退出代码: {}",
|
||||
if cleanup_result { 0 } else { 1 }
|
||||
);
|
||||
|
||||
let app_handle = handle::Handle::app_handle();
|
||||
app_handle.exit(if cleanup_result { 0 } else { 1 });
|
||||
}
|
||||
|
||||
@@ -56,6 +57,7 @@ pub async fn clean_async() -> bool {
|
||||
|
||||
let disable_tun = serde_json::json!({ "tun": { "enable": false } });
|
||||
|
||||
logging!(info, Type::System, "send disable tun request to mihomo");
|
||||
match timeout(
|
||||
Duration::from_millis(1000),
|
||||
handle::Handle::mihomo()
|
||||
@@ -210,6 +212,7 @@ pub async fn clean_async() -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let stop_timeout = Duration::from_secs(3);
|
||||
|
||||
logging!(info, Type::System, "stop core");
|
||||
match timeout(stop_timeout, CoreManager::global().stop_core()).await {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, "core已停止");
|
||||
@@ -269,41 +272,6 @@ pub async fn clean_async() -> bool {
|
||||
all_success
|
||||
}
|
||||
|
||||
pub fn clean() -> bool {
|
||||
use crate::process::AsyncHandler;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
AsyncHandler::spawn(move || async move {
|
||||
logging!(info, Type::System, "开始执行关闭操作...");
|
||||
|
||||
// 使用已有的异步清理函数
|
||||
let cleanup_result = clean_async().await;
|
||||
|
||||
let _ = tx.send(cleanup_result);
|
||||
});
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let total_timeout = std::time::Duration::from_secs(5);
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let total_timeout = std::time::Duration::from_secs(8);
|
||||
|
||||
match rx.recv_timeout(total_timeout) {
|
||||
Ok(result) => {
|
||||
logging!(info, Type::System, "关闭操作完成,结果: {}", result);
|
||||
result
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
warn,
|
||||
Type::System,
|
||||
"清理操作超时(可能正在关机),返回成功避免阻塞"
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub async fn hide() {
|
||||
use crate::module::lightweight::add_light_weight_timer;
|
||||
|
||||
@@ -10,9 +10,10 @@ mod feat;
|
||||
mod module;
|
||||
mod process;
|
||||
pub mod utils;
|
||||
use crate::constants::files;
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::utils::linux;
|
||||
use crate::utils::resolve::init_signal;
|
||||
use crate::{constants::files, utils::resolve::prioritize_initialization};
|
||||
use crate::{
|
||||
core::handle,
|
||||
process::AsyncHandler,
|
||||
@@ -237,13 +238,14 @@ pub fn run() {
|
||||
|
||||
let builder = app_init::setup_plugins(tauri::Builder::default())
|
||||
.setup(|app| {
|
||||
logging!(info, Type::Setup, "开始应用初始化...");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
APP_HANDLE
|
||||
.set(app.app_handle().clone())
|
||||
.expect("failed to set global app handle");
|
||||
|
||||
let _handle = AsyncHandler::block_on(async { prioritize_initialization().await });
|
||||
|
||||
logging!(info, Type::Setup, "开始应用初始化...");
|
||||
if let Err(e) = app_init::setup_autostart(app) {
|
||||
logging!(error, Type::Setup, "Failed to setup autostart: {}", e);
|
||||
}
|
||||
@@ -257,6 +259,7 @@ pub fn run() {
|
||||
resolve::resolve_setup_handle();
|
||||
resolve::resolve_setup_async();
|
||||
resolve::resolve_setup_sync();
|
||||
init_signal();
|
||||
|
||||
logging!(info, Type::Setup, "初始化已启动");
|
||||
Ok(())
|
||||
@@ -423,6 +426,10 @@ pub fn run() {
|
||||
event_handlers::handle_reopen(has_visible_windows).await;
|
||||
});
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
tauri::RunEvent::Exit => AsyncHandler::block_on(async {
|
||||
feat::quit().await;
|
||||
}),
|
||||
tauri::RunEvent::ExitRequested { api, code, .. } => {
|
||||
AsyncHandler::block_on(async {
|
||||
let _ = handle::Handle::mihomo()
|
||||
@@ -439,13 +446,6 @@ pub fn run() {
|
||||
api.prevent_exit();
|
||||
}
|
||||
}
|
||||
tauri::RunEvent::Exit => {
|
||||
let handle = core::handle::Handle::global();
|
||||
if !handle.is_exiting() {
|
||||
handle.set_is_exiting();
|
||||
feat::clean();
|
||||
}
|
||||
}
|
||||
tauri::RunEvent::WindowEvent { label, event, .. } if label == "main" => match event {
|
||||
tauri::WindowEvent::CloseRequested { .. } => {
|
||||
event_handlers::handle_window_close(&event);
|
||||
|
||||
@@ -19,7 +19,7 @@ use clash_verge_service_ipc::WriterConfig;
|
||||
use flexi_logger::writers::FileLogWriter;
|
||||
use flexi_logger::{Cleanup, Criterion, FileSpec};
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
use flexi_logger::{Duplicate, LogSpecBuilder, Logger};
|
||||
use flexi_logger::{Duplicate, LogSpecBuilder, Logger, LoggerHandle};
|
||||
use std::{path::PathBuf, str::FromStr as _};
|
||||
use tauri_plugin_shell::ShellExt as _;
|
||||
use tokio::fs;
|
||||
@@ -27,7 +27,7 @@ use tokio::fs::DirEntry;
|
||||
|
||||
/// initialize this instance's log file
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
pub async fn init_logger() -> Result<()> {
|
||||
pub async fn init_logger() -> Result<LoggerHandle> {
|
||||
// TODO 提供 runtime 级别实时修改
|
||||
let (log_level, log_max_size, log_max_count) = {
|
||||
let verge_guard = Config::verge().await;
|
||||
@@ -47,11 +47,9 @@ pub async fn init_logger() -> Result<()> {
|
||||
.unwrap_or(log_level);
|
||||
spec.default(level);
|
||||
#[cfg(feature = "tracing")]
|
||||
spec.module("tauri", log::LevelFilter::Debug);
|
||||
#[cfg(feature = "tracing")]
|
||||
spec.module("wry", log::LevelFilter::Off);
|
||||
#[cfg(feature = "tracing")]
|
||||
spec.module("tauri_plugin_mihomo", log::LevelFilter::Off);
|
||||
spec.module("tauri", log::LevelFilter::Debug)
|
||||
.module("wry", log::LevelFilter::Off)
|
||||
.module("tauri_plugin_mihomo", log::LevelFilter::Off);
|
||||
let spec = spec.build();
|
||||
|
||||
let logger = Logger::with(spec)
|
||||
@@ -83,14 +81,14 @@ pub async fn init_logger() -> Result<()> {
|
||||
"kode_bridge",
|
||||
])));
|
||||
|
||||
let _handle = logger.start()?;
|
||||
let handle = logger.start()?;
|
||||
|
||||
// TODO 全局 logger handle 控制
|
||||
// GlobalLoggerProxy::global().set_inner(handle);
|
||||
// TODO 提供前端设置等级,热更新等级
|
||||
// logger.parse_new_spec(spec)
|
||||
|
||||
Ok(())
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
pub async fn sidecar_writer() -> Result<FileLogWriter> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use flexi_logger::LoggerHandle;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
@@ -23,6 +24,21 @@ pub mod ui;
|
||||
pub mod window;
|
||||
pub mod window_script;
|
||||
|
||||
pub async fn prioritize_initialization() -> Option<LoggerHandle> {
|
||||
init_work_config().await;
|
||||
init_resources().await;
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
{
|
||||
logging!(info, Type::Setup, "Initializing logger");
|
||||
init::init_logger().await.ok()
|
||||
}
|
||||
#[cfg(feature = "tauri-dev")]
|
||||
{
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_setup_handle() {
|
||||
init_handle();
|
||||
}
|
||||
@@ -31,14 +47,11 @@ pub fn resolve_setup_sync() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
AsyncHandler::spawn_blocking(init_scheme);
|
||||
AsyncHandler::spawn_blocking(init_embed_server);
|
||||
AsyncHandler::spawn_blocking(init_signal);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn resolve_setup_async() {
|
||||
AsyncHandler::spawn(|| async {
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
resolve_setup_logger().await;
|
||||
logging!(
|
||||
info,
|
||||
Type::ClashVergeRev,
|
||||
@@ -96,11 +109,6 @@ pub(super) fn init_scheme() {
|
||||
logging_error!(Type::Setup, init::init_scheme());
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tauri-dev"))]
|
||||
pub(super) async fn resolve_setup_logger() {
|
||||
logging_error!(Type::Setup, init::init_logger().await);
|
||||
}
|
||||
|
||||
pub async fn resolve_scheme(param: &str) -> Result<()> {
|
||||
logging_error!(Type::Setup, scheme::resolve_scheme(param).await);
|
||||
Ok(())
|
||||
@@ -134,7 +142,7 @@ pub(super) async fn init_auto_backup() {
|
||||
logging_error!(Type::Setup, AutoBackupManager::global().init().await);
|
||||
}
|
||||
|
||||
pub(super) fn init_signal() {
|
||||
pub fn init_signal() {
|
||||
logging!(info, Type::Setup, "Initializing signal handlers...");
|
||||
clash_verge_signal::register(
|
||||
#[cfg(windows)]
|
||||
|
||||
Reference in New Issue
Block a user