mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-04-13 05:20:28 +08:00
refactor: fix startup init chain — resolve_done semantics, dedupe events, cleanup
Backend:
- Move resolve_done() from sync setup() to async task after futures::join!
so Timer waits for actual init completion instead of firing immediately
- Replace std:🧵:sleep(50ms) with tokio::time::sleep in async context
- Remove duplicate refresh_tray_menu in tray_init (keep post-join call only)
- Delete dead code reset_resolve_done (process restarts, static is destroyed)
- Rename create_window(is_show) → create_window(should_create) for clarity
Frontend:
- Remove duplicate verge://refresh-clash-config listener from AppDataProvider
(useLayoutEvents handles it via invalidateQueries — single consumer path)
- Stabilize useEffect deps with useRef for TQ refetch references
- Simplify AppDataProvider event listener setup (profile-changed + proxy only)
This commit is contained in:
parent
437fef1c30
commit
3aa39bff94
@ -3,7 +3,7 @@ use crate::{
|
|||||||
core::{CoreManager, handle, tray},
|
core::{CoreManager, handle, tray},
|
||||||
feat::clean_async,
|
feat::clean_async,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
utils::{self, resolve::reset_resolve_done},
|
utils,
|
||||||
};
|
};
|
||||||
use clash_verge_logging::{Type, logging};
|
use clash_verge_logging::{Type, logging};
|
||||||
use serde_yaml_ng::{Mapping, Value};
|
use serde_yaml_ng::{Mapping, Value};
|
||||||
@ -42,7 +42,6 @@ pub async fn restart_app() {
|
|||||||
if cleanup_result { 0 } else { 1 }
|
if cleanup_result { 0 } else { 1 }
|
||||||
);
|
);
|
||||||
|
|
||||||
reset_resolve_done();
|
|
||||||
let app_handle = handle::Handle::app_handle();
|
let app_handle = handle::Handle::app_handle();
|
||||||
app_handle.restart();
|
app_handle.restart();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -251,7 +251,6 @@ pub fn run() {
|
|||||||
resolve::resolve_setup_async();
|
resolve::resolve_setup_async();
|
||||||
resolve::resolve_setup_sync();
|
resolve::resolve_setup_sync();
|
||||||
resolve::init_signal();
|
resolve::init_signal();
|
||||||
resolve::resolve_done();
|
|
||||||
|
|
||||||
logging!(info, Type::Setup, "初始化已启动");
|
logging!(info, Type::Setup, "初始化已启动");
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -62,14 +62,9 @@ pub fn resolve_setup_async() {
|
|||||||
init_system_proxy_guard().await;
|
init_system_proxy_guard().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
let tray_init = async {
|
|
||||||
init_tray().await;
|
|
||||||
refresh_tray_menu().await;
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = futures::join!(
|
let _ = futures::join!(
|
||||||
core_init,
|
core_init,
|
||||||
tray_init,
|
init_tray(),
|
||||||
init_timer(),
|
init_timer(),
|
||||||
init_hotkey(),
|
init_hotkey(),
|
||||||
init_auto_lightweight_boot(),
|
init_auto_lightweight_boot(),
|
||||||
@ -79,6 +74,7 @@ pub fn resolve_setup_async() {
|
|||||||
|
|
||||||
Handle::refresh_clash();
|
Handle::refresh_clash();
|
||||||
refresh_tray_menu().await;
|
refresh_tray_menu().await;
|
||||||
|
resolve_done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +216,3 @@ pub fn resolve_done() {
|
|||||||
pub fn is_resolve_done() -> bool {
|
pub fn is_resolve_done() -> bool {
|
||||||
RESOLVE_DONE.load(Ordering::Acquire)
|
RESOLVE_DONE.load(Ordering::Acquire)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_resolve_done() {
|
|
||||||
RESOLVE_DONE.store(false, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@ impl WindowManager {
|
|||||||
logging!(info, Type::Window, "窗口不存在,创建新窗口");
|
logging!(info, Type::Window, "窗口不存在,创建新窗口");
|
||||||
if Self::create_window(true).await {
|
if Self::create_window(true).await {
|
||||||
logging!(info, Type::Window, "窗口创建成功");
|
logging!(info, Type::Window, "窗口创建成功");
|
||||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
WindowOperationResult::Created
|
WindowOperationResult::Created
|
||||||
} else {
|
} else {
|
||||||
logging!(warn, Type::Window, "窗口创建失败");
|
logging!(warn, Type::Window, "窗口创建失败");
|
||||||
@ -286,11 +286,11 @@ impl WindowManager {
|
|||||||
|
|
||||||
/// 创建新窗口,防抖避免重复调用
|
/// 创建新窗口,防抖避免重复调用
|
||||||
/// 窗口创建后保持隐藏,由前端 index.html 在 overlay 渲染后调用 show,避免主题闪烁
|
/// 窗口创建后保持隐藏,由前端 index.html 在 overlay 渲染后调用 show,避免主题闪烁
|
||||||
pub fn create_window(is_show: bool) -> Pin<Box<dyn Future<Output = bool> + Send>> {
|
pub fn create_window(should_create: bool) -> Pin<Box<dyn Future<Output = bool> + Send>> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
logging!(info, Type::Window, "开始创建/显示主窗口, is_show={}", is_show);
|
logging!(info, Type::Window, "开始创建主窗口, should_create={}", should_create);
|
||||||
|
|
||||||
if !is_show {
|
if !should_create {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import React, { useCallback, useEffect, useMemo } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
getBaseConfig,
|
getBaseConfig,
|
||||||
getRuleProviders,
|
getRuleProviders,
|
||||||
@ -79,106 +79,45 @@ export const AppDataProvider = ({
|
|||||||
...TQ_MIHOMO,
|
...TQ_MIHOMO,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const refreshProxyRef = useRef(refreshProxy)
|
||||||
|
const refreshRulesRef = useRef(refreshRules)
|
||||||
|
const refreshRuleProvidersRef = useRef(refreshRuleProviders)
|
||||||
|
useEffect(() => {
|
||||||
|
refreshProxyRef.current = refreshProxy
|
||||||
|
}, [refreshProxy])
|
||||||
|
useEffect(() => {
|
||||||
|
refreshRulesRef.current = refreshRules
|
||||||
|
}, [refreshRules])
|
||||||
|
useEffect(() => {
|
||||||
|
refreshRuleProvidersRef.current = refreshRuleProviders
|
||||||
|
}, [refreshRuleProviders])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let lastProfileId: string | null = null
|
let lastProfileId: string | null = null
|
||||||
let lastUpdateTime = 0
|
let lastUpdateTime = 0
|
||||||
const refreshThrottle = 800
|
const refreshThrottle = 800
|
||||||
|
|
||||||
let isUnmounted = false
|
|
||||||
const scheduledTimeouts = new Set<number>()
|
|
||||||
const cleanupFns: Array<() => void> = []
|
const cleanupFns: Array<() => void> = []
|
||||||
|
|
||||||
const registerCleanup = (fn: () => void) => {
|
|
||||||
if (isUnmounted) {
|
|
||||||
try {
|
|
||||||
fn()
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[DataProvider] Immediate cleanup failed:', error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cleanupFns.push(fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addWindowListener = (eventName: string, handler: EventListener) => {
|
|
||||||
// eslint-disable-next-line @eslint-react/web-api-no-leaked-event-listener
|
|
||||||
window.addEventListener(eventName, handler)
|
|
||||||
return () => window.removeEventListener(eventName, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
const scheduleTimeout = (
|
|
||||||
callback: () => void | Promise<void>,
|
|
||||||
delay: number,
|
|
||||||
) => {
|
|
||||||
if (isUnmounted) return -1
|
|
||||||
|
|
||||||
const timeoutId = window.setTimeout(() => {
|
|
||||||
scheduledTimeouts.delete(timeoutId)
|
|
||||||
if (!isUnmounted) {
|
|
||||||
void callback()
|
|
||||||
}
|
|
||||||
}, delay)
|
|
||||||
|
|
||||||
scheduledTimeouts.add(timeoutId)
|
|
||||||
return timeoutId
|
|
||||||
}
|
|
||||||
|
|
||||||
const clearAllTimeouts = () => {
|
|
||||||
scheduledTimeouts.forEach((timeoutId) => clearTimeout(timeoutId))
|
|
||||||
scheduledTimeouts.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleProfileChanged = (event: { payload: string }) => {
|
const handleProfileChanged = (event: { payload: string }) => {
|
||||||
const newProfileId = event.payload
|
const newProfileId = event.payload
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
lastProfileId === newProfileId &&
|
lastProfileId === newProfileId &&
|
||||||
now - lastUpdateTime < refreshThrottle
|
now - lastUpdateTime < refreshThrottle
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
lastProfileId = newProfileId
|
lastProfileId = newProfileId
|
||||||
lastUpdateTime = now
|
lastUpdateTime = now
|
||||||
|
refreshRulesRef.current().catch(() => {})
|
||||||
scheduleTimeout(() => {
|
refreshRuleProvidersRef.current().catch(() => {})
|
||||||
refreshRules().catch((error) =>
|
|
||||||
console.warn('[DataProvider] Rules refresh failed:', error),
|
|
||||||
)
|
|
||||||
refreshRuleProviders().catch((error) =>
|
|
||||||
console.warn('[DataProvider] Rule providers refresh failed:', error),
|
|
||||||
)
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleRefreshClash = () => {
|
|
||||||
const now = Date.now()
|
|
||||||
if (now - lastUpdateTime <= refreshThrottle) return
|
|
||||||
|
|
||||||
lastUpdateTime = now
|
|
||||||
scheduleTimeout(async () => {
|
|
||||||
await Promise.all([
|
|
||||||
refreshProxy().catch((error) =>
|
|
||||||
console.error('[DataProvider] Proxy refresh failed:', error),
|
|
||||||
),
|
|
||||||
refreshClashConfig().catch((error) =>
|
|
||||||
console.error('[DataProvider] Clash config refresh failed:', error),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
}, 200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleRefreshProxy = () => {
|
const handleRefreshProxy = () => {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (now - lastUpdateTime <= refreshThrottle) return
|
if (now - lastUpdateTime <= refreshThrottle) return
|
||||||
|
|
||||||
lastUpdateTime = now
|
lastUpdateTime = now
|
||||||
scheduleTimeout(() => {
|
refreshProxyRef.current().catch(() => {})
|
||||||
refreshProxy().catch((error) =>
|
|
||||||
console.warn('[DataProvider] Proxy refresh failed:', error),
|
|
||||||
)
|
|
||||||
}, 200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initializeListeners = async () => {
|
const initializeListeners = async () => {
|
||||||
@ -187,62 +126,34 @@ export const AppDataProvider = ({
|
|||||||
'profile-changed',
|
'profile-changed',
|
||||||
handleProfileChanged,
|
handleProfileChanged,
|
||||||
)
|
)
|
||||||
registerCleanup(unlistenProfile)
|
cleanupFns.push(unlistenProfile)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[AppDataProvider] 监听 Profile 事件失败:', error)
|
console.error('[AppDataProvider] 监听 Profile 事件失败:', error)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const unlistenClash = await listen(
|
|
||||||
'verge://refresh-clash-config',
|
|
||||||
handleRefreshClash,
|
|
||||||
)
|
|
||||||
const unlistenProxy = await listen(
|
const unlistenProxy = await listen(
|
||||||
'verge://refresh-proxy-config',
|
'verge://refresh-proxy-config',
|
||||||
handleRefreshProxy,
|
handleRefreshProxy,
|
||||||
)
|
)
|
||||||
|
cleanupFns.push(unlistenProxy)
|
||||||
registerCleanup(() => {
|
|
||||||
unlistenClash()
|
|
||||||
unlistenProxy()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[AppDataProvider] 设置 Tauri 事件监听器失败:', error)
|
console.warn('[AppDataProvider] 设置 Tauri 事件监听器失败:', error)
|
||||||
|
|
||||||
const fallbackHandlers: Array<[string, EventListener]> = [
|
|
||||||
['verge://refresh-clash-config', handleRefreshClash],
|
|
||||||
['verge://refresh-proxy-config', handleRefreshProxy],
|
|
||||||
]
|
|
||||||
|
|
||||||
fallbackHandlers.forEach(([eventName, handler]) => {
|
|
||||||
registerCleanup(addWindowListener(eventName, handler))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeListeners()
|
void initializeListeners()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isUnmounted = true
|
cleanupFns.forEach((fn) => {
|
||||||
clearAllTimeouts()
|
|
||||||
|
|
||||||
const errors: Error[] = []
|
|
||||||
cleanupFns.splice(0).forEach((fn) => {
|
|
||||||
try {
|
try {
|
||||||
fn()
|
fn()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errors.push(error instanceof Error ? error : new Error(String(error)))
|
console.error('[DataProvider] Cleanup error:', error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (errors.length > 0) {
|
|
||||||
console.error(
|
|
||||||
`[DataProvider] ${errors.length} errors during cleanup:`,
|
|
||||||
errors,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [refreshProxy, refreshClashConfig, refreshRules, refreshRuleProviders])
|
}, [])
|
||||||
|
|
||||||
const { data: sysproxy, refetch: refreshSysproxy } = useQuery({
|
const { data: sysproxy, refetch: refreshSysproxy } = useQuery({
|
||||||
queryKey: ['getSystemProxy'],
|
queryKey: ['getSystemProxy'],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user