From 5b7e876ab0332620e100bf826fc84f20f1658acd Mon Sep 17 00:00:00 2001 From: wonfen Date: Tue, 3 Nov 2026 09:32:17 +0800 Subject: [PATCH] refactor(service): simplify DNS override sync and runtime asset staging --- Changelog.md | 7 ----- src-tauri/src/cmd/clash.rs | 2 -- src-tauri/src/cmd/profile.rs | 5 ---- src-tauri/src/cmd/save_profile.rs | 3 +-- src-tauri/src/config/dns.rs | 8 +----- src-tauri/src/core/manager/lifecycle.rs | 23 ++++------------ src-tauri/src/core/manager/mod.rs | 5 +++- src-tauri/src/core/runtime_bundle.rs | 20 +++++--------- src-tauri/src/core/service.rs | 35 ++++++++++--------------- src-tauri/src/feat/backup.rs | 3 +-- src-tauri/src/feat/executor.rs | 15 ++--------- src-tauri/src/feat/listener.rs | 5 ++-- src-tauri/src/feat/profile.rs | 13 +++------ src-tauri/src/utils/resolve/scheme.rs | 5 +--- 14 files changed, 41 insertions(+), 108 deletions(-) diff --git a/Changelog.md b/Changelog.md index 306310fae..271277cd8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -25,13 +25,6 @@ 🐞 修复问题 - 修复全局模式缺少 `GLOBAL` 策略组,无法单独选择全局出口 -- 修复规则扩展包含空列表项时,「编辑规则」界面报错的问题 -- 修复托盘测速与代理页行为不一致的问题,移除托盘「测试延迟」入口 -- 修复通过 URL 导入订阅时,即使导入的订阅并非当前使用的订阅,也会重新加载当前配置 -- 修复 首页 网站测速失败后卡片一直显示测试中 -- 修复 代理 页展开分组后不显示最新测速结果,需切换节点才刷新 -- 修复 代理 在全局模式下无法展开节点组 -- 修复 自定义 CSS 无法修改编辑器字体和字号的问题 - 修复服务模式下,SSH 节点的 `private-key` 使用私钥文件时内核无法启动的问题 - 修复服务模式下,内核更新的 GEO 数据库不会同步回应用目录,导致使用新分类的规则无法通过校验的问题 - 修复订阅的 DNS 配置变化后,「DNS 覆写」实际已关闭但开关仍显示开启且没有提示的问题 diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 95f6854bb..bd0ba09aa 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -59,7 +59,6 @@ pub async fn change_clash_core(clash_core: std::string::String) -> CmdResult { - logging_error!(Type::Config, Config::sync_dns_override().await); logging_error!(Type::Core, profiles_save_file_safe().await); match CoreManager::global().restart_core().await { @@ -206,7 +205,6 @@ pub async fn apply_dns_config(apply: bool) -> CmdResult { logging!(info, Type::Config, "Config regenerated successfully"); } - logging_error!(Type::Config, Config::sync_dns_override().await); handle::Handle::refresh_clash(); Ok(()) } diff --git a/src-tauri/src/cmd/profile.rs b/src-tauri/src/cmd/profile.rs index 6b36e0fc9..106302b12 100644 --- a/src-tauri/src/cmd/profile.rs +++ b/src-tauri/src/cmd/profile.rs @@ -191,10 +191,6 @@ pub async fn delete_profile(index: std::string::String) -> CmdResult { drop(config_update_guard); drop(profile_write_guard); - if should_update { - logging_error!(Type::Config, Config::sync_dns_override().await); - } - if let Err(e) = Tray::global().update_tooltip().await { logging!(warn, Type::Cmd, "异步更新托盘提示失败: {e:#}"); } @@ -264,7 +260,6 @@ async fn handle_success(current_value: Option<&String>) -> CmdResult { - logging_error!(Type::Config, Config::sync_dns_override().await); handle::Handle::refresh_clash(); Ok(ValidationOutcome::Valid) } diff --git a/src-tauri/src/config/dns.rs b/src-tauri/src/config/dns.rs index 369eea5b6..86727ab76 100644 --- a/src-tauri/src/config/dns.rs +++ b/src-tauri/src/config/dns.rs @@ -6,7 +6,6 @@ use serde_yaml_ng::{Mapping, Value}; use sha2::{Digest as _, Sha256}; use smartstring::alias::String; use std::sync::atomic::{AtomicBool, Ordering}; -use tokio::sync::MutexGuard; use super::{Config, IVerge}; use crate::core::handle::Handle; @@ -129,12 +128,7 @@ impl IVerge { impl Config { pub(crate) async fn sync_dns_override() -> Result<()> { - let config_write = Self::lock_config_write().await; - Self::sync_dns_override_locked(&config_write).await - } - - /// Callers must have ended their own draft transaction, which claims the same Verge layer. - pub(crate) async fn sync_dns_override_locked(_config_write: &MutexGuard<'_, ()>) -> Result<()> { + let _config_write = Self::lock_config_write().await; let state = Self::runtime().await.data_arc().dns_override.clone(); let Some(state) = state else { return Ok(()); diff --git a/src-tauri/src/core/manager/lifecycle.rs b/src-tauri/src/core/manager/lifecycle.rs index 7694ddf4b..62a8776b4 100644 --- a/src-tauri/src/core/manager/lifecycle.rs +++ b/src-tauri/src/core/manager/lifecycle.rs @@ -5,7 +5,7 @@ use crate::core::manager::CLASH_LOGGER; use crate::core::proxy_control::{self, SysproxyFailure}; use crate::core::service::{SERVICE_MANAGER, ServiceStatus}; use anyhow::Result; -use clash_verge_logging::{Type, logging, logging_error}; +use clash_verge_logging::{Type, logging}; use scopeguard::defer; use smartstring::alias::String; use std::path::Path; @@ -428,7 +428,6 @@ impl CoreManager { } // Drop the guard last so an earlier failure cannot leave a running Sidecar unguarded. proxy_control::stop_guard().await; - let starts_core = matches!(*self.get_running_mode(), RunningMode::NotRunning); let result = async { self.start_core_inner().await?; if !matches!(*self.get_running_mode(), RunningMode::Sidecar) @@ -437,7 +436,7 @@ impl CoreManager { anyhow::bail!("Sidecar did not become ready"); } // A Sidecar that was already running never loaded this generation. - if starts_core { + if matches!(*mode, RunningMode::NotRunning) { Config::runtime().await.apply(); } tracing::Span::current().record("readiness_generation", self.current_core_readiness_generation()); @@ -456,12 +455,7 @@ impl CoreManager { Ok(()) } .await; - let result = result.map_err(|error| report_sidecar_failure(error, service_rejection)); - // Lock order is config then lifecycle. - drop(_life); - // A failure after the commit still leaves that runtime for the Sidecar or the next start. - logging_error!(Type::Config, Config::sync_dns_override().await); - result + result.map_err(|error| report_sidecar_failure(error, service_rejection)) } #[tracing::instrument(skip_all, level = "info", fields(readiness_generation = tracing::field::Empty))] @@ -473,7 +467,7 @@ impl CoreManager { self.finish_config_update(); } // Lock order is config then lifecycle, for the unclaimed draft write below. - let config_write = Config::lock_config_write().await; + let _config_write = Config::lock_config_write().await; let _life = self.lifecycle_lock.lock().await; self.controlled_stop_core_inner().await?; @@ -509,15 +503,8 @@ impl CoreManager { ) .await; - let cleanup = if result.is_err() { - self.rollback_failed_sidecar_transition().await - } else { - Ok(()) - }; - // A failure after the commit still leaves that runtime for the Sidecar or the next start. - logging_error!(Type::Config, Config::sync_dns_override_locked(&config_write).await); if let Err(error) = result { - if let Err(cleanup_error) = cleanup { + if let Err(cleanup_error) = self.rollback_failed_sidecar_transition().await { return Err(proxy_control::rollback_failure(error, cleanup_error) .context("failed to clear the proxy before rolling back")); } diff --git a/src-tauri/src/core/manager/mod.rs b/src-tauri/src/core/manager/mod.rs index 004852010..dde3482cf 100644 --- a/src-tauri/src/core/manager/mod.rs +++ b/src-tauri/src/core/manager/mod.rs @@ -274,6 +274,10 @@ impl CoreManager { pub(crate) fn finish_config_update(&self) { self.config_update_in_progress.store(false, Ordering::Release); + // Spawned because the caller may still hold the config write lock this waits for. + crate::process::AsyncHandler::spawn(|| async { + logging_error!(Type::Config, crate::config::Config::sync_dns_override().await); + }); } #[tracing::instrument(skip_all, level = "info", fields(retries = 0))] @@ -303,7 +307,6 @@ impl CoreManager { } match crate::config::Config::resolve_startup_mixed_port().await { Ok(true) => { - logging_error!(Type::Config, crate::config::Config::sync_dns_override().await); let retries = retries + 1; tracing::Span::current().record("retries", retries); logging!( diff --git a/src-tauri/src/core/runtime_bundle.rs b/src-tauri/src/core/runtime_bundle.rs index 67f405c3d..5038a6231 100644 --- a/src-tauri/src/core/runtime_bundle.rs +++ b/src-tauri/src/core/runtime_bundle.rs @@ -3,7 +3,7 @@ use clash_verge_logging::{Type, logging}; use clash_verge_service_ipc::{RemoteProvider, RuntimeAsset, RuntimeBundle}; use serde_yaml_ng::{Mapping, Value}; use sha2::{Digest as _, Sha256}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::path::{Component, Path, PathBuf, Prefix}; pub(crate) const GEO_ASSETS: &[&str] = &[ @@ -297,7 +297,6 @@ fn collect_ssh_private_keys( } } - let mut staged = HashMap::new(); for proxy in proxy_lists.into_iter().flatten().filter_map(Value::as_mapping_mut) { if proxy.get("type").and_then(Value::as_str) != Some("ssh") { continue; @@ -323,17 +322,12 @@ fn collect_ssh_private_keys( continue; }; // A root-level alias keeps the user's path away from service name rules and mihomo's inline-key test. - let destination = staged - .entry(source.to_string_lossy().into_owned()) - .or_insert_with_key(|source| { - let destination = allocate_provider_destination("ssh-private-key", source, destinations); - assets.push(RuntimeAsset { - source: source.clone(), - destination: destination.clone(), - }); - destination - }) - .clone(); + let source = source.to_string_lossy().into_owned(); + let destination = allocate_provider_destination("ssh-private-key", &source, destinations); + assets.push(RuntimeAsset { + source, + destination: destination.clone(), + }); proxy.insert(Value::String("private-key".to_owned()), Value::String(destination)); } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 8835e5ff1..d21331d86 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -1248,36 +1248,29 @@ async fn sync_runtime_geo( let Some((Some(_), _, Some(runtime_mtime_ns))) = read_chunk(credentials, session, asset, u64::MAX).await? else { return Ok(()); }; - let local_mtime_ns = local - .modified()? - .duration_since(UNIX_EPOCH) - .map_or(0, |since_epoch| since_epoch.as_nanos()); // A newer local file is a change the next staging still has to copy into the runtime. - if u128::from(runtime_mtime_ns) <= local_mtime_ns { + if UNIX_EPOCH + Duration::from_nanos(runtime_mtime_ns) <= local.modified()? { return Ok(()); } let (temp, file) = create_sync_temp(target).await?; - let published = async { - let Fetch::Complete { + let result = async { + if let Fetch::Complete { mtime_ns: Some(mtime_ns), .. } = fetch_runtime_file(credentials, session, asset, file).await? - else { - return Ok(false); - }; - // Matching the runtime copy's time lets the service skip copying these bytes straight back. - std::fs::File::options() - .write(true) - .open(&temp)? - .set_modified(UNIX_EPOCH + Duration::from_nanos(mtime_ns))?; - tokio::fs::rename(&temp, target).await?; - anyhow::Ok(true) + { + // Matching the runtime copy's time lets the service skip copying these bytes straight back. + std::fs::File::options() + .write(true) + .open(&temp)? + .set_modified(UNIX_EPOCH + Duration::from_nanos(mtime_ns))?; + tokio::fs::rename(&temp, target).await?; + } + anyhow::Ok(()) } .await; - if !matches!(published, Ok(true)) { - remove_temp(&temp).await; - } - published.map(|_| ()) + remove_temp(&temp).await; + result } // The on-disk YAML may describe a rejected apply; use the committed runtime. diff --git a/src-tauri/src/feat/backup.rs b/src-tauri/src/feat/backup.rs index 7c246d36a..89f9b6d6e 100644 --- a/src-tauri/src/feat/backup.rs +++ b/src-tauri/src/feat/backup.rs @@ -9,7 +9,7 @@ use crate::{ }; use anyhow::{Result, anyhow}; use chrono::Utc; -use clash_verge_logging::{Type, logging, logging_error}; +use clash_verge_logging::{Type, logging}; use reqwest_dav::list_cmd::ListFile; use serde::Serialize; use smartstring::alias::String; @@ -88,7 +88,6 @@ async fn finalize_restored_verge_config( return Err(err); } } - logging_error!(Type::Config, Config::sync_dns_override().await); Ok(()) } diff --git a/src-tauri/src/feat/executor.rs b/src-tauri/src/feat/executor.rs index 3f62e2d03..71f8820da 100644 --- a/src-tauri/src/feat/executor.rs +++ b/src-tauri/src/feat/executor.rs @@ -3,7 +3,6 @@ use crate::core::notify::{Refresh, announce}; use crate::{ config::{ Config, IVerge, - runtime::IRuntime, snapshot::{capture_config_files, restore_files}, }, core::auto_backup::AutoBackupManager, @@ -13,7 +12,7 @@ use crate::{ }, }; use anyhow::Result; -use clash_verge_draft::{DraftTransaction, SharedDraft}; +use clash_verge_draft::DraftTransaction; use clash_verge_logging::{Type, logging_error}; use serde_yaml_ng::Mapping; use std::sync::Arc; @@ -109,12 +108,6 @@ async fn ensure_effect( Ok(()) } -async fn sync_dns_of_replaced_runtime(config_write: &MutexGuard<'_, ()>, original: &SharedDraft) { - if !Arc::ptr_eq(original, &Config::runtime().await.data_arc()) { - logging_error!(Type::Config, Config::sync_dns_override_locked(config_write).await); - } -} - pub(super) async fn apply(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, effects: Effects) -> Result<()> { let manager = CoreManager::global(); let update = manager.claim_config_update(config_write)?; @@ -162,16 +155,12 @@ pub(super) async fn apply(config_write: &MutexGuard<'_, ()>, patch: Patch<'_>, e let runtime_path = crate::utils::dirs::app_home_dir()?.join(crate::constants::files::RUNTIME_CONFIG); snapshots.retain(|snapshot| snapshot.path != runtime_path); } - let restored = restore_files(&snapshots).await; - // After the restore, which would otherwise overwrite the saved decision. - sync_dns_of_replaced_runtime(config_write, &original_runtime).await; - return match restored { + return match restore_files(&snapshots).await { Ok(()) => Err(error), Err(rollback_error) => Err(proxy_control::rollback_failure(error, rollback_error)), }; } transaction.commit(); - sync_dns_of_replaced_runtime(config_write, &original_runtime).await; match patch { Patch::Verge { patch, .. } => { if patch.auto_check_update == Some(false) { diff --git a/src-tauri/src/feat/listener.rs b/src-tauri/src/feat/listener.rs index 78494b9a8..2e23d82a7 100644 --- a/src-tauri/src/feat/listener.rs +++ b/src-tauri/src/feat/listener.rs @@ -18,7 +18,7 @@ use crate::{ }; use anyhow::{Context as _, Result, anyhow, bail}; use clash_verge_draft::DraftTransaction; -use clash_verge_logging::{Type, logging, logging_error}; +use clash_verge_logging::{Type, logging}; use scopeguard::{ScopeGuard, defer, guard}; pub async fn probe_listener(request: ListenerProbe) -> Result { @@ -30,7 +30,7 @@ pub async fn probe_listener(request: ListenerProbe) -> Result Result { settings.validate()?; - let config_write = Config::lock_config_write().await; + let _config_write = Config::lock_config_write().await; let manager = CoreManager::global(); if !manager.try_start_config_update() { bail!("configuration update is already running"); @@ -124,7 +124,6 @@ pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result, is_mannual if should_refresh { logging!(debug, Type::Config, "[订阅更新] 更新内核配置"); match CoreManager::global().update_config_with_force(is_mannual_trigger).await { - Ok(outcome) if outcome.is_valid() => { - logging_error!(Type::Config, Config::sync_dns_override().await); - announce(Refresh::Clash); - } + Ok(outcome) if outcome.is_valid() => announce(Refresh::Clash), Ok(outcome @ (ValidationOutcome::Skipped { .. } | ValidationOutcome::Busy)) if !is_mannual_trigger => { logging!(info, Type::Config, "[订阅更新] 本次配置刷新已跳过: {}", outcome); } @@ -247,9 +244,5 @@ pub async fn update_profile(uid: &String, option: Option<&PrfOption>, is_mannual /// 增强配置 pub async fn enhance_profiles() -> Result { - let outcome = CoreManager::global().update_config_forced().await?; - if outcome.is_valid() { - logging_error!(Type::Config, Config::sync_dns_override().await); - } - Ok(outcome) + CoreManager::global().update_config_forced().await } diff --git a/src-tauri/src/utils/resolve/scheme.rs b/src-tauri/src/utils/resolve/scheme.rs index fbeed0e64..66c4212fe 100644 --- a/src-tauri/src/utils/resolve/scheme.rs +++ b/src-tauri/src/utils/resolve/scheme.rs @@ -146,10 +146,7 @@ async fn refresh_core_config() { "Deep link import set current profile; refreshing core config" ); match CoreManager::global().update_config_forced().await { - Ok(outcome) if outcome.is_valid() => { - logging_error!(Type::Config, Config::sync_dns_override().await); - handle::Handle::refresh_clash(); - } + Ok(outcome) if outcome.is_valid() => handle::Handle::refresh_clash(), Ok(outcome) => { let message = outcome.to_string(); logging!(warn, Type::Config, "Apply config failed: {}", message);