浏览器下载插件

This commit is contained in:
zhongluofeng
2026-07-21 17:40:55 +08:00
parent 548b022426
commit 141547acb6
9 changed files with 1322 additions and 129 deletions
+3 -2
View File
@@ -758,9 +758,10 @@ impl Aria2Manager {
Err(_) => return,
};
let _ = rt.block_on(async {
// 给一个短超时,避免退出卡住
// 给 2 秒超时,让 aria2 有足够时间保存 session
// aria2 保存 session 可能涉及磁盘 I/O500ms 太短)
let _ = tokio::time::timeout(
std::time::Duration::from_millis(500),
std::time::Duration::from_millis(2000),
self.rpc_call("aria2.shutdown", vec![]),
)
.await;
+24 -5
View File
@@ -38,14 +38,16 @@ fn quit_app(
state: tauri::State<'_, ProcessManager>,
mihomo: tauri::State<'_, MihomoManager>,
aria2: tauri::State<'_, Aria2Manager>,
app: tauri::AppHandle,
) {
// 退出前清理系统代理,避免遗留导致网络问题
mihomo.cleanup_on_exit();
// 退出前让 aria2 优雅关闭(保存 session
// 退出前让 aria2 优雅关闭(保存 session2s 超时
aria2.cleanup_on_exit();
// 停止所有子进程
// 停止所有子进程(同步 kill + 带超时的 wait,确保进程真正终止)
state.stop_all();
std::process::exit(0);
// 通过 app.exit 触发 RunEvent::ExitRequested,统一退出路径
app.exit(0);
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -154,6 +156,7 @@ pub fn run() {
}
"quit" => {
// 退出前清理系统代理 + 优雅关闭 aria2 + 停止所有子进程
// 直接调用 cleanup + stop_all + exitquit_app 命令是给前端用的)
if let Some(mihomo) = app.try_state::<MihomoManager>() {
mihomo.cleanup_on_exit();
}
@@ -207,6 +210,22 @@ pub fn run() {
api.prevent_close();
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app, event| {
// 退出请求兜底:捕获所有退出路径(app.exit、窗口全部关闭、系统信号等)
// 确保 mihomo/aria2 子进程在任何情况下都被清理
// 注:quit_app 命令和托盘菜单已主动调用 cleanup,这里作为二次保险
if let tauri::RunEvent::ExitRequested { .. } = event {
if let Some(mihomo) = app.try_state::<MihomoManager>() {
mihomo.cleanup_on_exit();
}
if let Some(aria2) = app.try_state::<Aria2Manager>() {
aria2.cleanup_on_exit();
}
if let Some(pm) = app.try_state::<ProcessManager>() {
pm.stop_all();
}
}
});
}
+130 -1
View File
@@ -11,6 +11,101 @@ use tauri::{AppHandle, Emitter, Manager};
#[cfg(windows)]
pub const CREATE_NO_WINDOW: u32 = 0x08000000;
// Windows Job Object 相关常量,用于异常退出时自动清理子进程
#[cfg(windows)]
#[allow(non_snake_case, non_upper_case_globals, non_camel_case_types)]
mod winapi {
pub const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE: u32 = 0x2000;
pub type HANDLE = *mut std::ffi::c_void;
pub type BOOL = i32;
pub type DWORD = u32;
pub type ULONG_PTR = usize;
#[repr(C)]
pub struct IO_COUNTERS {
pub ReadOperationCount: ULONG_PTR,
pub WriteOperationCount: ULONG_PTR,
pub OtherOperationCount: ULONG_PTR,
pub ReadTransferCount: ULONG_PTR,
pub WriteTransferCount: ULONG_PTR,
pub OtherTransferCount: ULONG_PTR,
}
#[repr(C)]
pub struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
pub BasicLimitInformation: JOBOBJECT_BASIC_LIMIT_INFORMATION,
pub IoInfo: IO_COUNTERS,
pub ProcessMemoryLimit: ULONG_PTR,
pub JobMemoryLimit: ULONG_PTR,
pub PeakProcessMemoryUsed: ULONG_PTR,
pub PeakJobMemoryUsed: ULONG_PTR,
}
#[repr(C)]
pub struct JOBOBJECT_BASIC_LIMIT_INFORMATION {
pub PerProcessUserTimeLimit: i64,
pub PerJobUserTimeLimit: i64,
pub LimitFlags: DWORD,
pub MinimumWorkingSetSize: ULONG_PTR,
pub MaximumWorkingSetSize: ULONG_PTR,
pub ActiveProcessLimit: DWORD,
pub Affinity: ULONG_PTR,
pub PriorityClass: DWORD,
pub SchedulingClass: DWORD,
}
pub const JobObjectExtendedLimitInformation: DWORD = 9;
extern "system" {
pub fn CreateJobObjectW(lpJobAttributes: *mut std::ffi::c_void, lpName: *const u16) -> HANDLE;
pub fn SetInformationJobObject(
hJob: HANDLE,
JobObjectInformationClass: DWORD,
lpJobObjectInformation: *mut std::ffi::c_void,
cbJobObjectInformationLength: DWORD,
) -> BOOL;
pub fn AssignProcessToJobObject(hJob: HANDLE, hProcess: HANDLE) -> BOOL;
}
}
/// 全局 Job Object 句柄(lazy 初始化,所有子进程都加入此 job)
/// 当主进程退出(包括崩溃)时,OS 自动终止 job 内所有子进程
/// 用 usize 存储指针以绕过 Send 约束(句柄本身是进程级资源,线程间共享安全)
#[cfg(windows)]
static JOB_HANDLE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
#[cfg(windows)]
fn get_job_handle() -> Option<winapi::HANDLE> {
let addr = *JOB_HANDLE.get_or_init(|| {
unsafe {
let h = winapi::CreateJobObjectW(std::ptr::null_mut(), std::ptr::null());
if h.is_null() {
eprintln!("[ProcessManager] CreateJobObjectW 失败,异常退出时子进程可能残留");
return 0;
}
// 设置 KILL_ON_JOB_CLOSE:主进程退出时自动终止所有子进程
let mut info: winapi::JOBOBJECT_EXTENDED_LIMIT_INFORMATION = std::mem::zeroed();
info.BasicLimitInformation.LimitFlags = winapi::JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
let ok = winapi::SetInformationJobObject(
h,
winapi::JobObjectExtendedLimitInformation,
&mut info as *mut _ as *mut std::ffi::c_void,
std::mem::size_of::<winapi::JOBOBJECT_EXTENDED_LIMIT_INFORMATION>() as u32,
);
if ok == 0 {
eprintln!("[ProcessManager] SetInformationJobObject 失败");
return 0;
}
h as usize
}
});
if addr == 0 {
None
} else {
Some(addr as winapi::HANDLE)
}
}
/// 为 Command 设置平台特定的创建标志(Windows 上隐藏控制台窗口)
/// 公开以便其他模块(如 mihomo_manager 调用 mihomo -v 查询版本)复用
#[cfg(windows)]
@@ -24,6 +119,22 @@ pub fn setup_creation_flags(_cmd: &mut Command) {
// 非 Windows 平台无需处理
}
/// 将已启动的子进程加入 Job Object(异常退出时自动清理)
/// 在 Windows 上调用,非 Windows 平台为空操作
#[cfg(windows)]
fn assign_to_job(child: &Child) {
use std::os::windows::io::AsRawHandle;
if let Some(job) = get_job_handle() {
let child_handle = child.as_raw_handle() as winapi::HANDLE;
unsafe {
let _ = winapi::AssignProcessToJobObject(job, child_handle);
}
}
}
#[cfg(not(windows))]
fn assign_to_job(_child: &Child) {}
/// 进程状态枚举
#[derive(Serialize, Clone, Debug)]
#[serde(rename_all = "lowercase")]
@@ -119,6 +230,8 @@ impl ProcessManager {
.spawn()
.map_err(|e| format!("启动进程 '{}' 失败: {}", params.id, e))?;
let pid = child.id();
// 将子进程加入 Job Object,主进程异常退出时由 OS 自动清理
assign_to_job(&child);
let entry = ProcessEntry {
child,
@@ -165,11 +278,27 @@ impl ProcessManager {
}
/// 停止所有进程(应用退出时调用)
/// 同步 kill + 带超时的 wait,确保子进程在主进程退出前真正终止
pub fn stop_all(&self) {
if let Ok(mut processes) = self.processes.lock() {
for (id, mut entry) in processes.drain() {
let _ = entry.child.kill();
let _ = entry.child.wait();
// 带超时的 wait,避免子进程卡住导致主进程无法退出
// 最长等 3 秒,超时则放弃等待(Job Object 兜底会清理)
let deadline = std::time::Instant::now() + Duration::from_secs(3);
loop {
match entry.child.try_wait() {
Ok(Some(_)) => break,
Ok(None) => {
if std::time::Instant::now() >= deadline {
println!("[ProcessManager] 进程 {} 等待退出超时(3s),放弃等待", id);
break;
}
std::thread::sleep(Duration::from_millis(50));
}
Err(_) => break,
}
}
println!("[ProcessManager] 已停止进程: {}", id);
}
}