fix: unexpected latency when switching nodes #6363

This commit is contained in:
Tunglies 2026-02-26 15:43:05 +08:00
parent 49fd3b04dc
commit 262b6f8adf
No known key found for this signature in database
GPG Key ID: B9B01B389469B3E8
2 changed files with 122 additions and 34 deletions

View File

@ -1,20 +1,52 @@
use super::CmdResult;
use crate::core::tray::Tray;
use crate::process::AsyncHandler;
use clash_verge_logging::{Type, logging};
use std::sync::atomic::{AtomicBool, Ordering};
static TRAY_SYNC_RUNNING: AtomicBool = AtomicBool::new(false);
static TRAY_SYNC_PENDING: AtomicBool = AtomicBool::new(false);
// TODO: 前端通过 emit 发送更新事件, tray 监听更新事件
/// 同步托盘和GUI的代理选择状态
#[tauri::command]
pub async fn sync_tray_proxy_selection() -> CmdResult<()> {
use crate::core::tray::Tray;
if TRAY_SYNC_RUNNING
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
AsyncHandler::spawn(move || async move {
run_tray_sync_loop().await;
});
} else {
TRAY_SYNC_PENDING.store(true, Ordering::Release);
}
match Tray::global().update_menu().await {
Ok(_) => {
logging!(info, Type::Cmd, "Tray proxy selection synced successfully");
Ok(())
Ok(())
}
async fn run_tray_sync_loop() {
loop {
match Tray::global().update_menu().await {
Ok(_) => {
logging!(info, Type::Cmd, "Tray proxy selection synced successfully");
}
Err(e) => {
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e}");
}
}
Err(e) => {
logging!(error, Type::Cmd, "Failed to sync tray proxy selection: {e}");
Err(e.to_string().into())
if !TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel) {
TRAY_SYNC_RUNNING.store(false, Ordering::Release);
if TRAY_SYNC_PENDING.swap(false, Ordering::AcqRel)
&& TRAY_SYNC_RUNNING
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_ok()
{
continue;
}
break;
}
}
}

View File

@ -1,5 +1,4 @@
import { useLockFn } from "ahooks";
import { useCallback, useMemo } from "react";
import { useCallback, useMemo, useRef } from "react";
import {
closeConnection,
getConnections,
@ -34,10 +33,19 @@ interface ProxySelectionOptions {
enableConnectionCleanup?: boolean;
}
interface ProxyChangeRequest {
groupName: string;
proxyName: string;
previousProxy?: string;
skipConfigSave: boolean;
}
// 代理选择 Hook
export const useProxySelection = (options: ProxySelectionOptions = {}) => {
const { current, patchCurrent } = useProfiles();
const { verge } = useVerge();
const pendingRequestRef = useRef<ProxyChangeRequest | null>(null);
const isProcessingRef = useRef(false);
const { onSuccess, onError, enableConnectionCleanup = true } = options;
@ -51,36 +59,46 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
);
// 切换节点
const changeProxy = useLockFn(
async (
groupName: string,
proxyName: string,
previousProxy?: string,
skipConfigSave: boolean = false,
) => {
const syncTraySelection = useCallback(() => {
syncTrayProxySelection().catch((error) => {
console.error("[ProxySelection] 托盘状态同步失败:", error);
});
}, []);
const persistSelection = useCallback(
(groupName: string, proxyName: string, skipConfigSave: boolean) => {
if (!current || skipConfigSave) return;
const selected = current.selected ? [...current.selected] : [];
const index = selected.findIndex((item) => item.name === groupName);
if (index < 0) {
selected.push({ name: groupName, now: proxyName });
} else {
selected[index] = { name: groupName, now: proxyName };
}
patchCurrent({ selected }).catch((error) => {
console.error("[ProxySelection] 保存代理选择失败:", error);
});
},
[current, patchCurrent],
);
const executeChange = useCallback(
async (request: ProxyChangeRequest) => {
const { groupName, proxyName, previousProxy, skipConfigSave } = request;
debugLog(`[ProxySelection] 代理切换: ${groupName} -> ${proxyName}`);
try {
if (current && !skipConfigSave) {
const selected = current.selected ? [...current.selected] : [];
const index = selected.findIndex((item) => item.name === groupName);
if (index < 0) {
selected.push({ name: groupName, now: proxyName });
} else {
selected[index] = { name: groupName, now: proxyName };
}
await patchCurrent({ selected });
}
await selectNodeForGroup(groupName, proxyName);
await syncTrayProxySelection();
onSuccess?.();
syncTraySelection();
persistSelection(groupName, proxyName, skipConfigSave);
debugLog(
`[ProxySelection] 代理和状态同步完成: ${groupName} -> ${proxyName}`,
);
onSuccess?.();
if (
config.enableConnectionCleanup &&
config.autoCloseConnection &&
@ -96,8 +114,9 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
try {
await selectNodeForGroup(groupName, proxyName);
await syncTrayProxySelection();
onSuccess?.();
syncTraySelection();
persistSelection(groupName, proxyName, skipConfigSave);
debugLog(
`[ProxySelection] 代理切换回退成功: ${groupName} -> ${proxyName}`,
);
@ -110,6 +129,43 @@ export const useProxySelection = (options: ProxySelectionOptions = {}) => {
}
}
},
[config, onError, onSuccess, persistSelection, syncTraySelection],
);
const flushChangeQueue = useCallback(async () => {
if (isProcessingRef.current) return;
isProcessingRef.current = true;
try {
while (pendingRequestRef.current) {
const request = pendingRequestRef.current;
pendingRequestRef.current = null;
await executeChange(request);
}
} finally {
isProcessingRef.current = false;
if (pendingRequestRef.current) {
void flushChangeQueue();
}
}
}, [executeChange]);
const changeProxy = useCallback(
(
groupName: string,
proxyName: string,
previousProxy?: string,
skipConfigSave: boolean = false,
) => {
pendingRequestRef.current = {
groupName,
proxyName,
previousProxy,
skipConfigSave,
};
void flushChangeQueue();
},
[flushChangeQueue],
);
const handleSelectChange = useCallback(