fix(sysproxy): keep the system proxy usable when the core is not ready

This commit is contained in:
wonfen
2026-08-24 19:32:52 +08:00
parent 9085d907cd
commit 8df8edb6dd
21 changed files with 108 additions and 30 deletions
+23 -8
View File
@@ -2,7 +2,7 @@ use super::{CoreManager, RunningMode};
use crate::config::{Config, IVerge};
use crate::core::handle::Handle;
use crate::core::manager::CLASH_LOGGER;
use crate::core::proxy_control;
use crate::core::proxy_control::{self, SysproxyFailure};
use crate::core::service::{SERVICE_MANAGER, ServiceStatus};
use anyhow::{Result, ensure};
use clash_verge_logging::{Type, logging};
@@ -304,7 +304,10 @@ impl CoreManager {
let _life = self.lifecycle_lock.lock().await;
run_core_start_transition(
|| self.start_core_inner(),
|| !matches!(*self.get_running_mode(), RunningMode::NotRunning),
|| {
!matches!(*self.get_running_mode(), RunningMode::NotRunning)
&& self.current_core_readiness_generation().is_some()
},
|| self.apply_proxy_after_start(),
)
.await
@@ -330,7 +333,9 @@ impl CoreManager {
proxy_control::stop_guard().await;
let result = async {
self.start_core_inner().await?;
if !matches!(*self.get_running_mode(), RunningMode::Sidecar) {
if !matches!(*self.get_running_mode(), RunningMode::Sidecar)
|| self.current_core_readiness_generation().is_none()
{
anyhow::bail!("Sidecar did not become ready");
}
self.apply_proxy_after_start().await
@@ -371,7 +376,9 @@ impl CoreManager {
},
|| async {
self.start_core_inner().await?;
if !matches!(*self.get_running_mode(), RunningMode::Sidecar) {
if !matches!(*self.get_running_mode(), RunningMode::Sidecar)
|| self.current_core_readiness_generation().is_none()
{
anyhow::bail!("Sidecar did not become ready after service uninstall");
}
self.apply_proxy_after_start().await
@@ -423,7 +430,9 @@ impl CoreManager {
self.current_core_readiness_generation(),
crate::core::service::owner_monitor_generation(),
)
.ok_or_else(|| anyhow::anyhow!("cannot apply system proxy before core readiness"))?;
.ok_or_else(|| {
anyhow::anyhow!("cannot apply system proxy before core readiness").context(SysproxyFailure::CoreNotReady)
})?;
proxy_control::apply().await?;
if !expectation.is_valid(
self.get_running_mode().as_ref(),
@@ -433,7 +442,8 @@ impl CoreManager {
let clear_result = proxy_control::clear().await;
proxy_control::stop_guard().await;
clear_result?;
anyhow::bail!("core readiness changed while applying system proxy");
return Err(anyhow::anyhow!("core readiness changed while applying system proxy")
.context(SysproxyFailure::CoreNotReady));
}
proxy_control::refresh_guard().await?;
if !expectation.is_valid(
@@ -444,7 +454,10 @@ impl CoreManager {
let clear_result = proxy_control::clear().await;
proxy_control::stop_guard().await;
clear_result?;
anyhow::bail!("core readiness changed while refreshing the system proxy guard");
return Err(
anyhow::anyhow!("core readiness changed while refreshing the system proxy guard")
.context(SysproxyFailure::CoreNotReady),
);
}
Ok(())
}
@@ -553,7 +566,9 @@ impl CoreManager {
|| self.controlled_stop_core_with_intent(proxy_intent),
|| async {
self.start_core_inner().await?;
if matches!(*self.get_running_mode(), RunningMode::NotRunning) {
if matches!(*self.get_running_mode(), RunningMode::NotRunning)
|| self.current_core_readiness_generation().is_none()
{
anyhow::bail!("core did not become ready after restart");
}
Ok(())
+4
View File
@@ -38,6 +38,8 @@ pub enum SysproxyFailure {
SidecarWhileServiceReady,
/// The proxy guard stopped after repeated failures.
GuardStopped { detail: String },
/// The Core was not ready, so enabling the system proxy was refused.
CoreNotReady,
}
impl SysproxyFailure {
@@ -48,6 +50,7 @@ impl SysproxyFailure {
Self::DirectFallback { .. } => "SYSPROXY_DIRECT_FALLBACK",
Self::SidecarWhileServiceReady => "SYSPROXY_SIDECAR_WHILE_SERVICE_READY",
Self::GuardStopped { .. } => "SYSPROXY_GUARD_STOPPED",
Self::CoreNotReady => "SYSPROXY_CORE_NOT_READY",
}
}
@@ -70,6 +73,7 @@ impl std::fmt::Display for SysproxyFailure {
Self::GuardStopped { detail } => {
write!(f, "system proxy guard stopped after repeated failures: {detail}")
}
Self::CoreNotReady => f.write_str("the core is not ready, so the system proxy was not enabled"),
}
}
}
+25 -5
View File
@@ -1,6 +1,6 @@
use crate::{
config::{Config, IClashTemp, IProfiles, IVerge},
core::{backup, proxy_control::SystemProxyStateUnknown},
core::{CoreManager, backup, proxy_control, proxy_control::SystemProxyStateUnknown},
process::AsyncHandler,
utils::{
dirs::{PathBufExec as _, app_home_dir, local_backup_dir, verge_path},
@@ -52,10 +52,30 @@ async fn finalize_restored_verge_config(
profiles_draft.apply();
let verge_draft = Config::verge().await;
verge_draft.edit_draft(|d| {
*d = restored.clone();
});
verge_draft.apply();
{
// Hold the write lock so a concurrent patch cannot stage a draft the proxy write reads.
let _config_write = Config::lock_config_write().await;
verge_draft.edit_draft(|d| {
*d = restored.clone();
});
verge_draft.apply();
// Turn it off here; a failing core side effect below would otherwise skip this step.
if !restored.enable_system_proxy.unwrap_or_default() {
let result = async {
let _lifecycle = CoreManager::global().lifecycle_lock.lock().await;
proxy_control::apply().await?;
proxy_control::refresh_guard().await
}
.await;
if let Err(err) = result {
logging!(error, Type::Backup, "Failed to turn the system proxy off: {err:#?}");
if SystemProxyStateUnknown::is_in(&err) {
return Err(err);
}
}
}
}
// Run configuration side effects without rewriting the already-restored file.
if let Err(err) = super::patch_verge(&restored, true).await {
+16 -2
View File
@@ -1,6 +1,9 @@
use crate::{
config::{Config, IVerge},
core::{CoreManager, autostart, handle, hotkey, logger::Logger, proxy_control::SystemProxyStateUnknown, tray},
core::{
CoreManager, autostart, handle, hotkey, logger::Logger, proxy_control, proxy_control::SystemProxyStateUnknown,
tray,
},
module::{auto_backup::AutoBackupManager, lightweight},
};
use anyhow::Result;
@@ -223,7 +226,18 @@ async fn process_terminated_flags(update_flags: UpdateFlags, patch: &IVerge) ->
if update_flags.contains(UpdateFlags::SYS_PROXY) {
let manager = CoreManager::global();
let _lifecycle = manager.lifecycle_lock.lock().await;
manager.apply_proxy_after_start().await?;
// Turning it off only writes OS state, so it must stay available while the Core is down.
if Config::verge()
.await
.latest_arc()
.enable_system_proxy
.unwrap_or_default()
{
manager.apply_proxy_after_start().await?;
} else {
proxy_control::apply().await?;
proxy_control::refresh_guard().await?;
}
}
if update_flags.contains(UpdateFlags::HOTKEY)
&& let Some(hotkeys) = &patch.hotkeys