Files
clash-verge-rev/src-tauri/src/core/manager/lifecycle.rs
T
wonfen 1112f0730d fix(core): improve service startup and cleanup reliability
Wait longer for slow Windows service startup, then hand off from sidecar
to service when it becomes ready. Retry service core startup during the
handoff window, and clean up root-owned macOS service core/IPC leftovers
before uninstalling the service.
2026-06-28 04:34:01 +08:00

260 lines
8.6 KiB
Rust

use super::{CoreManager, RunningMode};
use crate::cmd::StringifyErr as _;
use crate::config::{Config, IVerge};
use crate::core::handle::Handle;
use crate::core::manager::CLASH_LOGGER;
use crate::core::service::{SERVICE_MANAGER, ServiceStatus};
use anyhow::Result;
use clash_verge_logging::{Type, logging};
use scopeguard::defer;
use smartstring::alias::String;
use tauri_plugin_clash_verge_sysinfo;
/// sidecar→service 交接结果
#[cfg(target_os = "windows")]
enum HandoffOutcome {
/// 服务尚未就绪
NotReady,
/// 已完成或无需交接
Done,
/// 交接失败并已回退
Failed,
}
impl CoreManager {
pub async fn start_core(&self) -> Result<()> {
self.prepare_startup().await;
defer! {
self.after_core_process();
}
let result = match *self.get_running_mode() {
RunningMode::Service => self.start_core_by_service().await,
RunningMode::NotRunning | RunningMode::Sidecar => self.start_core_by_sidecar().await,
};
// 回退 sidecar 后,后台等待服务就绪再交接
#[cfg(target_os = "windows")]
if result.is_ok() && matches!(*self.get_running_mode(), RunningMode::Sidecar) {
self.spawn_service_handoff_watcher().await;
}
result
}
pub async fn stop_core(&self) -> Result<()> {
CLASH_LOGGER.clear_logs().await;
defer! {
self.after_core_process();
}
match *self.get_running_mode() {
RunningMode::Service => self.stop_core_by_service().await,
RunningMode::Sidecar => {
self.stop_core_by_sidecar();
Ok(())
}
RunningMode::NotRunning => Ok(()),
}
}
pub async fn restart_core(&self) -> Result<()> {
logging!(info, Type::Core, "Restarting core");
self.stop_core().await?;
self.start_core().await
}
pub async fn change_core(&self, clash_core: &String) -> Result<(), String> {
if !IVerge::VALID_CLASH_CORES.contains(&clash_core.as_str()) {
return Err(format!("Invalid clash core: {}", clash_core).into());
}
Config::verge().await.edit_draft(|d| {
d.clash_core = Some(clash_core.to_owned());
});
Config::verge().await.apply();
let verge_data = Config::verge().await.latest_arc();
verge_data.save_file().await.map_err(|e| e.to_string())?;
self.update_config_checked().await.stringify_err()?;
Ok(())
}
async fn prepare_startup(&self) {
#[cfg(target_os = "windows")]
self.wait_for_service_if_needed().await;
self.set_running_mode(match SERVICE_MANAGER.current().await {
ServiceStatus::Ready => RunningMode::Service,
_ => RunningMode::Sidecar,
});
}
fn after_core_process(&self) {
let app_handle = Handle::app_handle();
tauri_plugin_clash_verge_sysinfo::set_app_core_mode(app_handle, self.get_running_mode().to_string());
}
#[cfg(target_os = "windows")]
async fn wait_for_service_if_needed(&self) {
use crate::{config::Config, constants::timing, core::service};
use backon::{ConstantBuilder, Retryable as _};
let needs_service = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
if !needs_service {
return;
}
let max_times = timing::SERVICE_WAIT_MAX.as_millis() / timing::SERVICE_WAIT_INTERVAL.as_millis();
let backoff = ConstantBuilder::default()
.with_delay(timing::SERVICE_WAIT_INTERVAL)
.with_max_times(max_times as usize);
let _ = (|| async {
if matches!(SERVICE_MANAGER.current().await, ServiceStatus::Ready) {
return Ok(());
}
// If the service IPC path is not ready yet, treat it as transient and retry.
// Running init/refresh too early can mark service state unavailable and break later config reloads.
if !service::is_service_ipc_path_exists() {
return Err(anyhow::anyhow!("Service IPC not ready"));
}
SERVICE_MANAGER.init().await?;
let _ = SERVICE_MANAGER.refresh().await;
if matches!(SERVICE_MANAGER.current().await, ServiceStatus::Ready) {
Ok(())
} else {
Err(anyhow::anyhow!("Service not ready"))
}
})
.retry(backoff)
.await;
}
/// 在窗口内等待服务就绪,再从 sidecar 交接到 service
#[cfg(target_os = "windows")]
async fn spawn_service_handoff_watcher(&self) {
use crate::constants::timing;
use crate::process::AsyncHandler;
use std::sync::atomic::Ordering;
use std::time::Instant;
// 仅 TUN 模式需要服务交接
let needs_service = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
if !needs_service {
return;
}
// 单实例,避免并发交接
if self.handoff_watcher_running.swap(true, Ordering::AcqRel) {
return;
}
logging!(
info,
Type::Core,
"service not ready at startup; sidecar active, watching for handoff"
);
AsyncHandler::spawn(|| async move {
let manager = Self::global();
let started = Instant::now();
loop {
if started.elapsed() >= timing::SERVICE_HANDOFF_WINDOW {
logging!(
info,
Type::Core,
"service handoff window elapsed; staying in sidecar mode"
);
break;
}
tokio::time::sleep(timing::SERVICE_HANDOFF_INTERVAL).await;
// 模式已变更时退出
if !matches!(*manager.get_running_mode(), RunningMode::Sidecar) {
break;
}
match manager.try_handoff_sidecar_to_service().await {
// 已交接或无需交接
HandoffOutcome::Done => break,
// 已回退 sidecar,停止重试
HandoffOutcome::Failed => {
logging!(warn, Type::Core, "handoff attempt failed; staying in sidecar mode");
break;
}
HandoffOutcome::NotReady => {}
}
}
manager.handoff_watcher_running.store(false, Ordering::Release);
});
}
/// 服务就绪后停止 sidecar,再以 service 重启内核
#[cfg(target_os = "windows")]
async fn try_handoff_sidecar_to_service(&self) -> HandoffOutcome {
use crate::core::service;
// 主动刷新服务状态,避免缓存状态阻止交接
if !service::is_service_ipc_path_exists() {
return HandoffOutcome::NotReady;
}
if SERVICE_MANAGER.init().await.is_err() {
return HandoffOutcome::NotReady;
}
let _ = SERVICE_MANAGER.refresh().await;
if !matches!(SERVICE_MANAGER.current().await, ServiceStatus::Ready) {
return HandoffOutcome::NotReady;
}
// 与配置更新/重启互斥
if !self.try_start_config_update() {
return HandoffOutcome::NotReady;
}
defer! {
self.finish_config_update();
}
// 加锁后复检运行模式和 TUN 状态
if !matches!(*self.get_running_mode(), RunningMode::Sidecar)
|| !Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false)
{
return HandoffOutcome::Done;
}
logging!(
info,
Type::Core,
"service became ready; handing off from sidecar to service"
);
self.stop_core_by_sidecar();
match self.start_core_by_service().await {
Ok(()) => {
logging!(info, Type::Core, "handoff to service mode succeeded");
HandoffOutcome::Done
}
Err(e) => {
logging!(
error,
Type::Core,
"handoff to service failed: {}; restarting sidecar",
e
);
if let Err(e2) = self.start_core_by_sidecar().await {
logging!(
error,
Type::Core,
"failed to restart sidecar after handoff failure: {}",
e2
);
}
HandoffOutcome::Failed
}
}
}
}