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);