diff --git a/Changelog.md b/Changelog.md index dd88cac4f..306310fae 100644 --- a/Changelog.md +++ b/Changelog.md @@ -34,6 +34,7 @@ - 修复 自定义 CSS 无法修改编辑器字体和字号的问题 - 修复服务模式下,SSH 节点的 `private-key` 使用私钥文件时内核无法启动的问题 - 修复服务模式下,内核更新的 GEO 数据库不会同步回应用目录,导致使用新分类的规则无法通过校验的问题 +- 修复订阅的 DNS 配置变化后,「DNS 覆写」实际已关闭但开关仍显示开启且没有提示的问题 **🍎 macOS** diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index 6fb9602ad..95f6854bb 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -59,6 +59,7 @@ 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 { diff --git a/src-tauri/src/config/dns.rs b/src-tauri/src/config/dns.rs index 86727ab76..369eea5b6 100644 --- a/src-tauri/src/config/dns.rs +++ b/src-tauri/src/config/dns.rs @@ -6,6 +6,7 @@ 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; @@ -128,7 +129,12 @@ impl IVerge { impl Config { pub(crate) async fn sync_dns_override() -> Result<()> { - let _config_write = Self::lock_config_write().await; + 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 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 ad67b6b0a..7694ddf4b 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}; +use clash_verge_logging::{Type, logging, logging_error}; use scopeguard::defer; use smartstring::alias::String; use std::path::Path; @@ -428,6 +428,7 @@ 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) @@ -435,6 +436,10 @@ impl CoreManager { { anyhow::bail!("Sidecar did not become ready"); } + // A Sidecar that was already running never loaded this generation. + if starts_core { + Config::runtime().await.apply(); + } tracing::Span::current().record("readiness_generation", self.current_core_readiness_generation()); self.apply_proxy_after_start().await } @@ -451,7 +456,12 @@ impl CoreManager { Ok(()) } .await; - result.map_err(|error| report_sidecar_failure(error, service_rejection)) + 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 } #[tracing::instrument(skip_all, level = "info", fields(readiness_generation = tracing::field::Empty))] @@ -463,7 +473,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?; @@ -492,14 +502,22 @@ impl CoreManager { { anyhow::bail!("Sidecar did not become ready after service uninstall"); } + Config::runtime().await.apply(); tracing::Span::current().record("readiness_generation", self.current_core_readiness_generation()); self.apply_proxy_after_start().await }, ) .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) = self.rollback_failed_sidecar_transition().await { + if let Err(cleanup_error) = cleanup { 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 1110540b1..004852010 100644 --- a/src-tauri/src/core/manager/mod.rs +++ b/src-tauri/src/core/manager/mod.rs @@ -5,7 +5,7 @@ mod state; use anyhow::Result; use arc_swap::{ArcSwap, ArcSwapOption}; -use clash_verge_logging::{LogRing, Type, logging}; +use clash_verge_logging::{LogRing, Type, logging, logging_error}; use std::{ fmt, sync::{ @@ -303,6 +303,7 @@ 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/feat/executor.rs b/src-tauri/src/feat/executor.rs index 71f8820da..3f62e2d03 100644 --- a/src-tauri/src/feat/executor.rs +++ b/src-tauri/src/feat/executor.rs @@ -3,6 +3,7 @@ use crate::core::notify::{Refresh, announce}; use crate::{ config::{ Config, IVerge, + runtime::IRuntime, snapshot::{capture_config_files, restore_files}, }, core::auto_backup::AutoBackupManager, @@ -12,7 +13,7 @@ use crate::{ }, }; use anyhow::Result; -use clash_verge_draft::DraftTransaction; +use clash_verge_draft::{DraftTransaction, SharedDraft}; use clash_verge_logging::{Type, logging_error}; use serde_yaml_ng::Mapping; use std::sync::Arc; @@ -108,6 +109,12 @@ 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)?; @@ -155,12 +162,16 @@ 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); } - return match restore_files(&snapshots).await { + 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 { 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 2e23d82a7..78494b9a8 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}; +use clash_verge_logging::{Type, logging, logging_error}; 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,6 +124,7 @@ pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result handle::Handle::refresh_clash(), + Ok(outcome) if outcome.is_valid() => { + logging_error!(Type::Config, Config::sync_dns_override().await); + handle::Handle::refresh_clash(); + } Ok(outcome) => { let message = outcome.to_string(); logging!(warn, Type::Config, "Apply config failed: {}", message);