mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 05:20:28 +08:00
* fix(proxy): resolve system proxy toggle stuck and state desync (#6614) Backend: replace hand-rolled AtomicBool lock in update_sysproxy() with tokio::sync::Mutex so concurrent calls wait instead of being silently dropped, ensuring the latest config is always applied. Move blocking OS calls (networksetup on macOS) to spawn_blocking so they no longer stall the tokio worker thread pool. Frontend: release SwitchRow pendingRef in .finally() so the UI always re-syncs with the actual OS proxy state, and rollback checked on error. Closes #6614 * fix(changelog): add note for macOS proxy toggle freeze issue
This commit is contained in:
parent
607ef5a8a9
commit
ca8e350694
@ -6,6 +6,7 @@
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复系统代理关闭后在 PAC 模式下未完全关闭
|
||||
- 修复 macOS 开关代理时可能的卡死
|
||||
|
||||
### ✨ 新增功能
|
||||
|
||||
|
||||
@ -15,9 +15,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
use sysproxy::{Autoproxy, GuardMonitor, GuardType, Sysproxy};
|
||||
use tokio::sync::Mutex as TokioMutex;
|
||||
|
||||
pub struct Sysopt {
|
||||
update_sysproxy: AtomicBool,
|
||||
update_lock: TokioMutex<()>,
|
||||
reset_sysproxy: AtomicBool,
|
||||
inner_proxy: Arc<RwLock<(Sysproxy, Autoproxy)>>,
|
||||
guard: Arc<RwLock<GuardMonitor>>,
|
||||
@ -26,7 +27,7 @@ pub struct Sysopt {
|
||||
impl Default for Sysopt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
update_sysproxy: AtomicBool::new(false),
|
||||
update_lock: TokioMutex::new(()),
|
||||
reset_sysproxy: AtomicBool::new(false),
|
||||
inner_proxy: Arc::new(RwLock::new((Sysproxy::default(), Autoproxy::default()))),
|
||||
guard: Arc::new(RwLock::new(GuardMonitor::new(GuardType::None, Duration::from_secs(30)))),
|
||||
@ -107,95 +108,70 @@ impl Sysopt {
|
||||
|
||||
/// init the sysproxy
|
||||
pub async fn update_sysproxy(&self) -> Result<()> {
|
||||
if self.update_sysproxy.load(Ordering::Acquire) {
|
||||
logging!(info, Type::Core, "Sysproxy update is already in progress.");
|
||||
return Ok(());
|
||||
}
|
||||
if self
|
||||
.update_sysproxy
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
{
|
||||
logging!(info, Type::Core, "Sysproxy update is already in progress.");
|
||||
return Ok(());
|
||||
}
|
||||
defer! {
|
||||
logging!(info, Type::Core, "Sysproxy update completed.");
|
||||
self.update_sysproxy.store(false, Ordering::Release);
|
||||
}
|
||||
let _lock = self.update_lock.lock().await;
|
||||
|
||||
let verge = Config::verge().await.latest_arc();
|
||||
let port = {
|
||||
let verge_port = verge.verge_mixed_port;
|
||||
match verge_port {
|
||||
Some(port) => port,
|
||||
None => Config::clash().await.latest_arc().get_mixed_port(),
|
||||
}
|
||||
let port = match verge.verge_mixed_port {
|
||||
Some(port) => port,
|
||||
None => Config::clash().await.latest_arc().get_mixed_port(),
|
||||
};
|
||||
let pac_port = IVerge::get_singleton_port();
|
||||
|
||||
let (sys_enable, pac_enable, proxy_host, proxy_guard) = {
|
||||
(
|
||||
verge.enable_system_proxy.unwrap_or_default(),
|
||||
verge.proxy_auto_config.unwrap_or_default(),
|
||||
verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")),
|
||||
verge.enable_proxy_guard.unwrap_or_default(),
|
||||
)
|
||||
};
|
||||
|
||||
let (sys_enable, pac_enable, proxy_host, proxy_guard) = (
|
||||
verge.enable_system_proxy.unwrap_or_default(),
|
||||
verge.proxy_auto_config.unwrap_or_default(),
|
||||
verge.proxy_host.clone().unwrap_or_else(|| String::from("127.0.0.1")),
|
||||
verge.enable_proxy_guard.unwrap_or_default(),
|
||||
);
|
||||
// 先 await, 避免持有锁导致的 Send 问题
|
||||
let bypass = get_bypass().await;
|
||||
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
sys.host = proxy_host.clone().into();
|
||||
sys.port = port;
|
||||
sys.bypass = bypass.into();
|
||||
let (sys, auto, guard_type) = {
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.host = proxy_host.clone().into();
|
||||
sys.port = port;
|
||||
sys.bypass = bypass.into();
|
||||
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||
|
||||
auto.enable = false;
|
||||
auto.url = format!("http://{proxy_host}:{pac_port}/commands/pac");
|
||||
// `enable_system_proxy` is the master switch.
|
||||
// When disabled, force clear both global proxy and PAC at OS level.
|
||||
let guard_type = if !sys_enable {
|
||||
sys.enable = false;
|
||||
auto.enable = false;
|
||||
GuardType::None
|
||||
} else if pac_enable {
|
||||
sys.enable = false;
|
||||
auto.enable = true;
|
||||
if proxy_guard {
|
||||
GuardType::Autoproxy(auto.clone())
|
||||
} else {
|
||||
GuardType::None
|
||||
}
|
||||
} else {
|
||||
sys.enable = true;
|
||||
auto.enable = false;
|
||||
if proxy_guard {
|
||||
GuardType::Sysproxy(sys.clone())
|
||||
} else {
|
||||
GuardType::None
|
||||
}
|
||||
};
|
||||
|
||||
self.access_guard().write().set_guard_type(GuardType::None);
|
||||
(sys.clone(), auto.clone(), guard_type)
|
||||
};
|
||||
|
||||
// `enable_system_proxy` is the master switch.
|
||||
// When disabled, force clear both global proxy and PAC at OS level.
|
||||
if !sys_enable {
|
||||
self.access_guard().write().set_guard_type(guard_type);
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if pac_enable {
|
||||
sys.enable = false;
|
||||
auto.enable = true;
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
if proxy_guard {
|
||||
self.access_guard()
|
||||
.write()
|
||||
.set_guard_type(GuardType::Autoproxy(auto.clone()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if sys_enable {
|
||||
auto.enable = false;
|
||||
sys.enable = true;
|
||||
auto.set_auto_proxy()?;
|
||||
sys.set_system_proxy()?;
|
||||
if proxy_guard {
|
||||
self.access_guard()
|
||||
.write()
|
||||
.set_guard_type(GuardType::Sysproxy(sys.clone()));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// reset the sysproxy
|
||||
#[allow(clippy::unused_async)]
|
||||
pub async fn reset_sysproxy(&self) -> Result<()> {
|
||||
if self
|
||||
.reset_sysproxy
|
||||
@ -212,11 +188,19 @@ impl Sysopt {
|
||||
self.access_guard().write().set_guard_type(GuardType::None);
|
||||
|
||||
// 直接关闭所有代理
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
sys.set_system_proxy()?;
|
||||
auto.enable = false;
|
||||
auto.set_auto_proxy()?;
|
||||
let (sys, auto) = {
|
||||
let (sys, auto) = &mut *self.inner_proxy.write();
|
||||
sys.enable = false;
|
||||
auto.enable = false;
|
||||
(sys.clone(), auto.clone())
|
||||
};
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
sys.set_system_proxy()?;
|
||||
auto.set_auto_proxy()?;
|
||||
Ok(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -67,10 +67,14 @@ const SwitchRow = ({
|
||||
const handleChange = (_: React.ChangeEvent, value: boolean) => {
|
||||
pendingRef.current = true
|
||||
setChecked(value)
|
||||
onToggle(value).catch((err: any) => {
|
||||
pendingRef.current = false
|
||||
onError?.(err)
|
||||
})
|
||||
onToggle(value)
|
||||
.catch((err: any) => {
|
||||
setChecked(active)
|
||||
onError?.(err)
|
||||
})
|
||||
.finally(() => {
|
||||
pendingRef.current = false
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user