fix(sysproxy): retry a transient system proxy write on Windows

This commit is contained in:
wonfen
2026-08-24 22:30:42 +08:00
parent 8df8edb6dd
commit 272c09568c
18 changed files with 83 additions and 18 deletions
+52 -4
View File
@@ -25,6 +25,9 @@ use std::{
};
use tokio::{sync::Mutex, time::timeout};
const CLEAR_ATTEMPTS: u32 = 3;
const CLEAR_RETRY_DELAY: Duration = Duration::from_millis(100);
/// Actionable system-proxy failure attached to an `anyhow` chain.
///
/// Classification remains downcastable while the original error stays available for diagnostics.
@@ -40,6 +43,8 @@ pub enum SysproxyFailure {
GuardStopped { detail: String },
/// The Core was not ready, so enabling the system proxy was refused.
CoreNotReady,
/// A Windows system call refused the write, usually a transient RPC hiccup.
SystemCallFailed,
}
impl SysproxyFailure {
@@ -51,6 +56,7 @@ impl SysproxyFailure {
Self::SidecarWhileServiceReady => "SYSPROXY_SIDECAR_WHILE_SERVICE_READY",
Self::GuardStopped { .. } => "SYSPROXY_GUARD_STOPPED",
Self::CoreNotReady => "SYSPROXY_CORE_NOT_READY",
Self::SystemCallFailed => "SYSPROXY_SYSTEM_CALL_FAILED",
}
}
@@ -74,6 +80,7 @@ impl std::fmt::Display for SysproxyFailure {
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"),
Self::SystemCallFailed => f.write_str("a Windows system call failed while writing the system proxy"),
}
}
}
@@ -121,10 +128,28 @@ pub fn rollback_failure(caused_by: anyhow::Error, rollback: anyhow::Error) -> an
}
fn classify_local_failure(error: anyhow::Error) -> anyhow::Error {
if !was_refused_locally(&error) {
return error;
if was_refused_locally(&error) {
return error.context(SysproxyFailure::PrivilegeRequired);
}
error.context(SysproxyFailure::PrivilegeRequired)
if was_a_failed_system_call(&error) {
return error.context(SysproxyFailure::SystemCallFailed);
}
error
}
#[cfg(target_os = "windows")]
fn was_a_failed_system_call(error: &anyhow::Error) -> bool {
error.chain().any(|cause| {
matches!(
cause.downcast_ref::<sysproxy::Error>(),
Some(sysproxy::Error::SystemCall(_))
)
})
}
#[cfg(not(target_os = "windows"))]
const fn was_a_failed_system_call(_error: &anyhow::Error) -> bool {
false
}
fn was_refused_locally(error: &anyhow::Error) -> bool {
@@ -447,7 +472,7 @@ pub async fn apply() -> Result<()> {
}
pub async fn clear() -> Result<()> {
let result = clear_inner().await;
let result = clear_with_retry().await;
// Clear failures must be filed even when a restart aborts before apply.
settle_table(
clear_table_effect(&result),
@@ -459,6 +484,29 @@ pub async fn clear() -> Result<()> {
result
}
/// A failed system call is usually a transient RPC hiccup, so give it a few tries.
async fn clear_with_retry() -> Result<()> {
for _ in 1..CLEAR_ATTEMPTS {
match clear_inner().await {
Err(error)
if matches!(
SysproxyFailure::from_chain(&error),
Some(SysproxyFailure::SystemCallFailed)
) =>
{
logging!(
warn,
Type::Core,
"clearing the system proxy failed; retrying: {error:#}"
);
tokio::time::sleep(CLEAR_RETRY_DELAY).await;
}
other => return other,
}
}
clear_inner().await
}
async fn clear_inner() -> Result<()> {
let running_mode = CoreManager::global().get_running_mode();
match proxy_backend_route(cfg!(target_os = "macos"), &running_mode) {
+1 -1
View File
@@ -108,7 +108,7 @@ pub async fn save_proxy_ports(settings: ProxyPortSettings) -> Result<SaveProxyPo
);
return Ok(outcome);
}
return Err(activation_error).context("Mihomo rejected the proxy port configuration");
return Err(activation_error).context("failed to activate the proxy port configuration");
}
if let Err(persist_error) = persist_proxy_port_sources().await {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "تعذّر ضبط وكيل النظام؛ تمت إعادة حركة البيانات إلى اتصال مباشر.",
"guardStopped": "توقف حارس وكيل النظام بعد عدة إخفاقات متتالية. لم يعد وكيل النظام يُحفَظ تلقائيًا.",
"sidecarWhileServiceReady": "تعذّر ضبط وكيل النظام: خدمة Clash Verge جاهزة لكن النواة ما زالت تعمل بوضع Sidecar. أعد تشغيل النواة للتبديل إلى وضع الخدمة.",
"coreNotReady": "تعذّر تفعيل وكيل النظام لأن النواة لا تعمل. شغّل النواة (أو ثبّت/أصلح خدمة Clash Verge) ثم أعد المحاولة."
"coreNotReady": "تعذّر تفعيل وكيل النظام لأن النواة لا تعمل. شغّل النواة (أو ثبّت/أصلح خدمة Clash Verge) ثم أعد المحاولة.",
"systemCallFailed": "تعذّرت كتابة وكيل النظام لأن استدعاء نظام Windows أخفق. عادةً ما يكون هذا عارضًا، فأعد المحاولة."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Systemproxy konnte nicht gesetzt werden; der Datenverkehr wurde auf eine direkte Verbindung zurückgesetzt.",
"guardStopped": "Der Systemproxy-Wächter wurde nach mehreren Fehlversuchen in Folge gestoppt. Der Systemproxy wird nicht mehr automatisch aufrechterhalten.",
"sidecarWhileServiceReady": "Der Systemproxy konnte nicht gesetzt werden: Der Clash-Verge-Dienst ist bereit, der Kern läuft aber noch als Sidecar. Starten Sie den Kern neu, um in den Dienstmodus zu wechseln.",
"coreNotReady": "Der Systemproxy konnte nicht aktiviert werden, da der Kern nicht läuft. Starten Sie den Kern (oder installieren/reparieren Sie den Clash-Verge-Dienst) und versuchen Sie es erneut."
"coreNotReady": "Der Systemproxy konnte nicht aktiviert werden, da der Kern nicht läuft. Starten Sie den Kern (oder installieren/reparieren Sie den Clash-Verge-Dienst) und versuchen Sie es erneut.",
"systemCallFailed": "Der Systemproxy konnte nicht geschrieben werden, da ein Windows-Systemaufruf fehlschlug. Das ist meist vorübergehend, versuchen Sie es erneut."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Failed to set the system proxy; traffic has been switched back to a direct connection.",
"guardStopped": "The system proxy guard stopped after failing several times running. The system proxy is no longer being kept in place.",
"sidecarWhileServiceReady": "The system proxy could not be set because the core is still running as a Sidecar, although the Clash Verge service is ready. Restart the core to switch to service mode.",
"coreNotReady": "The system proxy could not be enabled because the core is not running. Start the core (or install/repair the Clash Verge service) and try again."
"coreNotReady": "The system proxy could not be enabled because the core is not running. Start the core (or install/repair the Clash Verge service) and try again.",
"systemCallFailed": "The system proxy could not be written because a Windows system call failed. This is usually transient, so try again."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "No se pudo establecer el proxy del sistema; el tráfico volvió a una conexión directa.",
"guardStopped": "El guardián del proxy del sistema se detuvo tras varios fallos seguidos. El proxy del sistema ya no se mantiene automáticamente.",
"sidecarWhileServiceReady": "No se pudo establecer el proxy del sistema: el servicio de Clash Verge está listo, pero el núcleo sigue ejecutándose como Sidecar. Reinicie el núcleo para cambiar al modo de servicio.",
"coreNotReady": "No se pudo activar el proxy del sistema porque el núcleo no está en ejecución. Inicie el núcleo (o instale/repare el servicio de Clash Verge) e inténtelo de nuevo."
"coreNotReady": "No se pudo activar el proxy del sistema porque el núcleo no está en ejecución. Inicie el núcleo (o instale/repare el servicio de Clash Verge) e inténtelo de nuevo.",
"systemCallFailed": "No se pudo escribir el proxy del sistema porque falló una llamada al sistema de Windows. Suele ser transitorio; inténtelo de nuevo."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "تنظیم پراکسی سیستم ناموفق بود؛ ترافیک به اتصال مستقیم بازگردانده شد.",
"guardStopped": "نگهبان پراکسی سیستم پس از چند شکست پیاپی متوقف شد. پراکسی سیستم دیگر به‌طور خودکار نگه داشته نمی‌شود.",
"sidecarWhileServiceReady": "تنظیم پراکسی سیستم ممکن نشد: سرویس Clash Verge آماده است اما هسته هنوز به‌صورت Sidecar اجرا می‌شود. برای رفتن به حالت سرویس، هسته را دوباره راه‌اندازی کنید.",
"coreNotReady": "فعال‌سازی پراکسی سیستم ممکن نشد: هسته در حال اجرا نیست. ابتدا هسته را اجرا کنید (یا سرویس Clash Verge را نصب/تعمیر کنید) و دوباره تلاش کنید."
"coreNotReady": "فعال‌سازی پراکسی سیستم ممکن نشد: هسته در حال اجرا نیست. ابتدا هسته را اجرا کنید (یا سرویس Clash Verge را نصب/تعمیر کنید) و دوباره تلاش کنید.",
"systemCallFailed": "نوشتن پراکسی سیستم ناموفق بود: یک فراخوانی سیستمی ویندوز شکست خورد. این معمولاً گذراست؛ دوباره تلاش کنید."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Gagal mengatur proxy sistem; lalu lintas dikembalikan ke koneksi langsung.",
"guardStopped": "Penjaga proxy sistem berhenti setelah gagal beberapa kali berturut-turut. Proxy sistem tidak lagi dipertahankan secara otomatis.",
"sidecarWhileServiceReady": "Proxy sistem tidak dapat diatur: layanan Clash Verge sudah siap, tetapi inti masih berjalan sebagai Sidecar. Mulai ulang inti untuk beralih ke mode layanan.",
"coreNotReady": "Proxy sistem tidak dapat diaktifkan karena inti tidak berjalan. Jalankan inti (atau pasang/perbaiki layanan Clash Verge) lalu coba lagi."
"coreNotReady": "Proxy sistem tidak dapat diaktifkan karena inti tidak berjalan. Jalankan inti (atau pasang/perbaiki layanan Clash Verge) lalu coba lagi.",
"systemCallFailed": "Proxy sistem gagal ditulis karena panggilan sistem Windows gagal. Ini biasanya sementara, coba lagi."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "システムプロキシの設定に失敗したため、直接接続に戻しました。",
"guardStopped": "システムプロキシガードが連続して失敗したため停止しました。システムプロキシは自動的に維持されなくなりました。",
"sidecarWhileServiceReady": "システムプロキシを設定できませんでした: Clash Verge サービスは準備できていますが、コアはまだ Sidecar で動作しています。コアを再起動するとサービスモードに切り替わります。",
"coreNotReady": "コアが実行されていないため、システムプロキシを有効にできませんでした。コアを起動する(または Clash Verge サービスをインストール/修復する)してから再試行してください。"
"coreNotReady": "コアが実行されていないため、システムプロキシを有効にできませんでした。コアを起動する(または Clash Verge サービスをインストール/修復する)してから再試行してください。",
"systemCallFailed": "Windows のシステムコールが失敗したため、システムプロキシを書き込めませんでした。多くは一時的な問題です。再試行してください。"
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "시스템 프록시 설정에 실패하여 직접 연결로 되돌렸습니다.",
"guardStopped": "시스템 프록시 가드가 연속으로 실패하여 중지되었습니다. 시스템 프록시가 더 이상 자동으로 유지되지 않습니다.",
"sidecarWhileServiceReady": "시스템 프록시를 설정하지 못했습니다: Clash Verge 서비스는 준비되었지만 코어가 아직 Sidecar로 실행 중입니다. 코어를 재시작하면 서비스 모드로 전환됩니다.",
"coreNotReady": "코어가 실행 중이 아니어서 시스템 프록시를 켤 수 없습니다. 코어를 시작한 뒤(또는 Clash Verge 서비스를 설치/복구한 뒤) 다시 시도하세요."
"coreNotReady": "코어가 실행 중이 아니어서 시스템 프록시를 켤 수 없습니다. 코어를 시작한 뒤(또는 Clash Verge 서비스를 설치/복구한 뒤) 다시 시도하세요.",
"systemCallFailed": "Windows 시스템 호출이 실패하여 시스템 프록시를 기록하지 못했습니다. 대개 일시적인 문제이니 다시 시도하세요."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Не удалось настроить системный прокси; трафик возвращён к прямому подключению.",
"guardStopped": "Страж системного прокси остановился после нескольких неудач подряд. Системный прокси больше не поддерживается автоматически.",
"sidecarWhileServiceReady": "Не удалось настроить системный прокси: служба Clash Verge готова, но ядро всё ещё работает в режиме Sidecar. Перезапустите ядро, чтобы переключиться в режим службы.",
"coreNotReady": "Не удалось включить системный прокси: ядро не запущено. Запустите ядро (или установите/восстановите службу Clash Verge) и повторите попытку."
"coreNotReady": "Не удалось включить системный прокси: ядро не запущено. Запустите ядро (или установите/восстановите службу Clash Verge) и повторите попытку.",
"systemCallFailed": "Не удалось записать системный прокси: системный вызов Windows завершился с ошибкой. Обычно это временный сбой, попробуйте ещё раз."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Sistem vekil sunucusu ayarlanamadı; trafik doğrudan bağlantıya geri döndürüldü.",
"guardStopped": "Sistem vekil sunucusu koruması art arda birkaç başarısızlıktan sonra durdu. Sistem vekil sunucusu artık otomatik olarak korunmuyor.",
"sidecarWhileServiceReady": "Sistem vekil sunucusu ayarlanamadı: Clash Verge hizmeti hazır ancak çekirdek hâlâ Sidecar olarak çalışıyor. Hizmet moduna geçmek için çekirdeği yeniden başlatın.",
"coreNotReady": "Çekirdek çalışmadığı için sistem vekil sunucusu etkinleştirilemedi. Çekirdeği başlatın (veya Clash Verge hizmetini kurun/onarın) ve yeniden deneyin."
"coreNotReady": "Çekirdek çalışmadığı için sistem vekil sunucusu etkinleştirilemedi. Çekirdeği başlatın (veya Clash Verge hizmetini kurun/onarın) ve yeniden deneyin.",
"systemCallFailed": "Bir Windows sistem çağrısı başarısız olduğu için sistem vekil sunucusu yazılamadı. Bu genellikle geçicidir, yeniden deneyin."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "Система проксиен көйләү уңышсыз булды; трафик туры тоташуга кайтарылды.",
"guardStopped": "Система проксие сакчысы берничә тапкыр рәттән уңышсыз булганнан соң туктады. Система проксие бүтән автоматик рәвештә саклап торылмый.",
"sidecarWhileServiceReady": "Система проксиен көйләп булмады: Clash Verge хезмәте әзер, әмма ядро һаман Sidecar буларак эшли. Хезмәт режимына күчәр өчен ядрону яңадан эшләтегез.",
"coreNotReady": "Система проксиен кабызып булмады: ядро эшләми. Ядрону җибәрегез (яки Clash Verge хезмәтен урнаштырыгыз/төзәтегез) һәм тагын бер тапкыр карагыз."
"coreNotReady": "Система проксиен кабызып булмады: ядро эшләми. Ядрону җибәрегез (яки Clash Verge хезмәтен урнаштырыгыз/төзәтегез) һәм тагын бер тапкыр карагыз.",
"systemCallFailed": "Система проксиен язып булмады: Windows система чакыруы уңышсыз тәмамланды. Гадәттә бу вакытлыча, тагын бер тапкыр карагыз."
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "系统代理设置失败,已回退为直连。",
"guardStopped": "系统代理守卫连续多次失败后已停止,系统代理不再被自动维持。",
"sidecarWhileServiceReady": "系统代理设置失败:Clash Verge 服务已就绪,但内核仍以 Sidecar 运行。重启内核即可切换到服务模式。",
"coreNotReady": "无法开启系统代理:内核尚未运行。请先启动内核(或安装/修复 Clash Verge 服务)后重试。"
"coreNotReady": "无法开启系统代理:内核尚未运行。请先启动内核(或安装/修复 Clash Verge 服务)后重试。",
"systemCallFailed": "系统代理写入失败:Windows 系统调用未成功,通常是瞬时故障。请稍后重试。"
}
},
"notifications": {
+2 -1
View File
@@ -661,7 +661,8 @@
"directFallback": "系統代理設定失敗,已回退為直連。",
"guardStopped": "系統代理守衛連續多次失敗後已停止,系統代理不再被自動維持。",
"sidecarWhileServiceReady": "系統代理設定失敗:Clash Verge 服務已就緒,但內核仍以 Sidecar 執行。重新啟動內核即可切換到服務模式。",
"coreNotReady": "無法開啟系統代理:內核尚未執行。請先啟動內核(或安裝/修復 Clash Verge 服務)後重試。"
"coreNotReady": "無法開啟系統代理:內核尚未執行。請先啟動內核(或安裝/修復 Clash Verge 服務)後重試。",
"systemCallFailed": "系統代理寫入失敗:Windows 系統呼叫未成功,通常是瞬時故障。請稍後重試。"
}
},
"notifications": {
+2
View File
@@ -89,6 +89,8 @@ const CODED_ERROR_TRANSLATION_KEYS: Readonly<Record<string, TranslationKey>> = {
'settings.feedback.errors.sysproxy.sidecarWhileServiceReady',
SYSPROXY_PRIVILEGE_REQUIRED:
'settings.feedback.errors.sysproxy.privilegeRequired',
SYSPROXY_SYSTEM_CALL_FAILED:
'settings.feedback.errors.sysproxy.systemCallFailed',
}
let nextId = 0
+1
View File
@@ -780,6 +780,7 @@ export const translationKeys = [
'settings.feedback.errors.sysproxy.guardStopped',
'settings.feedback.errors.sysproxy.sidecarWhileServiceReady',
'settings.feedback.errors.sysproxy.coreNotReady',
'settings.feedback.errors.sysproxy.systemCallFailed',
'settings.feedback.notifications.clash.restartSuccess',
'settings.feedback.notifications.clash.versionUpdated',
'settings.feedback.notifications.clash.alreadyLatestVersion',
+1
View File
@@ -817,6 +817,7 @@ export interface TranslationResources {
guardStopped: string
privilegeRequired: string
sidecarWhileServiceReady: string
systemCallFailed: string
}
}
notifications: {