浏览器下载插件
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user