Files
clash-verge-rev/src-tauri/src/core/manager/lifecycle.rs
T
hankandTunglies 2995f61087 fix: service unexpected status after restart app in unix (#5768)
* fix: 修复macos重启应用后需要重设服务器模式问题

* chore: remove package-lock.json (using pnpm)

* Delete test.sh

* refactor(lifecycle): remove unnecessary conditional compilation for macOS and Windows

* refactor(timing): remove conditional compilation for service wait durations on Windows and macOS

---------

Co-authored-by: Tunglies <77394545+Tunglies@users.noreply.github.com>
2025-12-11 20:09:27 +08:00

152 lines
5.1 KiB
Rust

use super::{CoreManager, RunningMode};
use crate::cmd::StringifyErr as _;
use crate::config::{Config, IVerge};
use crate::constants::timing;
use crate::core::handle::Handle;
use crate::core::manager::CLASH_LOGGER;
use crate::core::service::{SERVICE_MANAGER, ServiceStatus};
use anyhow::Result;
use backoff::{Error as BackoffError, ExponentialBackoff};
use clash_verge_logging::{Type, logging};
use scopeguard::defer;
use smartstring::alias::String;
use tauri_plugin_clash_verge_sysinfo;
impl CoreManager {
pub async fn start_core(&self) -> Result<()> {
self.prepare_startup().await?;
defer! {
self.after_core_process();
}
match *self.get_running_mode() {
RunningMode::Service => self.start_core_by_service().await,
RunningMode::NotRunning | RunningMode::Sidecar => self.start_core_by_sidecar().await,
}
}
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().await.stringify_err()?;
Ok(())
}
async fn prepare_startup(&self) -> Result<()> {
self.wait_for_service_if_needed().await;
let value = SERVICE_MANAGER.lock().await.current();
let mode = match value {
ServiceStatus::Ready => RunningMode::Service,
_ => RunningMode::Sidecar,
};
self.set_running_mode(mode);
Ok(())
}
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());
}
async fn wait_for_service_if_needed(&self) {
#[cfg(target_os = "windows")]
{
let needs_service = Config::verge().await.latest_arc().enable_tun_mode.unwrap_or(false);
if !needs_service {
return;
}
}
// 在 unix 上,如果服务状态是 "Need Checks",尝试初始化服务管理器
// 在 Windows 上,只有在需要 TUN 模式时才等待服务
let mut manager = SERVICE_MANAGER.lock().await;
let current_status = manager.current();
// 如果服务状态是 "Need Checks",尝试初始化服务管理器
if matches!(current_status, ServiceStatus::Unavailable(ref reason) if reason == "Need Checks") {
// 尝试初始化服务管理器,即使 IPC 路径可能暂时不存在
if let Err(e) = manager.init().await {
logging!(debug, Type::Core, "服务管理器初始化失败(可能服务未启动): {}", e);
} else {
// 初始化成功,尝试刷新状态
let _ = manager.refresh().await;
}
}
// 如果服务已经就绪,直接返回
if matches!(manager.current(), ServiceStatus::Ready) {
return;
}
drop(manager);
// 使用退避重试策略等待服务就绪
let backoff = ExponentialBackoff {
initial_interval: timing::SERVICE_WAIT_INTERVAL,
max_interval: timing::SERVICE_WAIT_INTERVAL,
max_elapsed_time: Some(timing::SERVICE_WAIT_MAX),
multiplier: 1.0,
randomization_factor: 0.0,
..Default::default()
};
let operation = || async {
let mut manager = SERVICE_MANAGER.lock().await;
if matches!(manager.current(), ServiceStatus::Ready) {
return Ok(());
}
// 如果服务管理器未初始化,尝试初始化
if matches!(manager.current(), ServiceStatus::Unavailable(ref reason) if reason == "Need Checks") {
manager.init().await.map_err(BackoffError::transient)?;
}
let _ = manager.refresh().await;
if matches!(manager.current(), ServiceStatus::Ready) {
Ok(())
} else {
Err(BackoffError::transient(anyhow::anyhow!("Service not ready")))
}
};
let _ = backoff::future::retry(backoff, operation).await;
}
}