Files
clash-verge-rev/src-tauri/src/core/manager/mod.rs
T
cjcandwonfen 6e9288437f Related to #6759 (windows): ensure sidecar process dies with the main app (#6853)
* fix(windows): ensure sidecar process dies with the main app

* chore: fix clippy lints

* chore: fix TS2307 error on commit.

chore: Undo the changes about package.json and pnpm-lock.yaml

* Fix formatting errors caused by merging.

刚刚的合并让原本的函数少了个结尾括号

* fix(windows): close process handle after assigning sidecar to job object

* fix(windows): sidecar Job Object 错误改为向上传播并补充测试

将 job 创建逻辑抽为返回 Result<OwnedHandle> 的独立函数,任一 Win32 调用失败时
终止已 spawn 的子进程并返回 Err,不再静默降级。句柄由裸 isize 改为 OwnedHandle,
借助 RAII 自动关闭。新增 Windows 单元测试,覆盖 KILL_ON_JOB_CLOSE 生命周期
及无效 PID 的失败路径。

---------

Co-authored-by: wonfen <96291150+wonfen@users.noreply.github.com>
2026-07-19 07:30:38 +08:00

148 lines
4.0 KiB
Rust

mod config;
mod lifecycle;
mod state;
use anyhow::Result;
use arc_swap::{ArcSwap, ArcSwapOption};
use clash_verge_logger::AsyncLogger;
use once_cell::sync::Lazy;
use std::{
fmt,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Instant,
};
use tauri_plugin_shell::process::CommandChild;
use crate::singleton;
#[cfg(target_os = "windows")]
use std::os::windows::io::OwnedHandle;
pub(crate) static CLASH_LOGGER: Lazy<Arc<AsyncLogger>> = Lazy::new(|| Arc::new(AsyncLogger::new()));
#[derive(Debug, serde::Serialize, PartialEq, Eq)]
pub enum RunningMode {
Service,
Sidecar,
NotRunning,
}
impl fmt::Display for RunningMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Service => write!(f, "Service"),
Self::Sidecar => write!(f, "Sidecar"),
Self::NotRunning => write!(f, "NotRunning"),
}
}
}
#[derive(Debug)]
pub struct CoreManager {
state: ArcSwap<State>,
last_update: ArcSwapOption<Instant>,
#[cfg(target_os = "windows")]
job_handle: ArcSwapOption<OwnedHandle>,
config_update_in_progress: AtomicBool,
// 串行化 start/stop/restart 和 sidecar→service 交接。
// 锁序固定为 config_update_in_progress → lifecycle_lock。
lifecycle_lock: tokio::sync::Mutex<()>,
// sidecar→service 交接 watcher 单实例标志。
#[cfg(target_os = "windows")]
handoff_watcher_running: AtomicBool,
}
#[derive(Debug)]
struct State {
running_mode: ArcSwap<RunningMode>,
child_sidecar: ArcSwapOption<CommandChild>,
}
impl Default for State {
fn default() -> Self {
Self {
running_mode: ArcSwap::new(Arc::new(RunningMode::NotRunning)),
child_sidecar: ArcSwapOption::new(None),
}
}
}
impl Default for CoreManager {
fn default() -> Self {
Self {
state: ArcSwap::new(Arc::new(State::default())),
last_update: ArcSwapOption::new(None),
#[cfg(target_os = "windows")]
job_handle: ArcSwapOption::new(None),
config_update_in_progress: AtomicBool::new(false),
lifecycle_lock: tokio::sync::Mutex::new(()),
#[cfg(target_os = "windows")]
handoff_watcher_running: AtomicBool::new(false),
}
}
}
impl CoreManager {
fn new() -> Self {
Self::default()
}
pub fn get_running_mode(&self) -> Arc<RunningMode> {
Arc::clone(&self.state.load().running_mode.load())
}
pub fn take_child_sidecar(&self) -> Option<CommandChild> {
self.state
.load()
.child_sidecar
.swap(None)
.and_then(|arc| Arc::try_unwrap(arc).ok())
}
pub fn get_last_update(&self) -> Option<Arc<Instant>> {
self.last_update.load_full()
}
pub fn set_running_mode(&self, mode: RunningMode) {
let state = self.state.load();
state.running_mode.store(Arc::new(mode));
}
pub fn set_running_child_sidecar(&self, child: CommandChild) {
let state = self.state.load();
state.child_sidecar.store(Some(Arc::new(child)));
}
pub fn set_last_update(&self, time: Instant) {
self.last_update.store(Some(Arc::new(time)));
}
/// Replaces the Windows Job Object handle owned by the core manager
///
/// Passing `None` drops the current handle, which closes the Job Object
/// and terminates its assigned processes due to `KILL_ON_JOB_CLOSE`.
///
/// This method is currently only used on Windows.
#[cfg(target_os = "windows")]
fn set_job_handle(&self, handle: Option<OwnedHandle>) {
self.job_handle.store(handle.map(Arc::new));
}
fn try_start_config_update(&self) -> bool {
!self.config_update_in_progress.swap(true, Ordering::AcqRel)
}
fn finish_config_update(&self) {
self.config_update_in_progress.store(false, Ordering::Release);
}
pub async fn init(&self) -> Result<()> {
self.start_core().await?;
Ok(())
}
}
singleton!(CoreManager, CORE_MANAGER);