mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
fix: resolve intermittent startup deadlock issues
- Optimize configuration access locks to prevent race conditions - Enhance UI monitoring thread with non-blocking lock operations - Improve window creation timing and synchronization - Add comprehensive deadlock detection and debugging logs - Simplify code structure with better error handling patterns - Update changelog with user-friendly descriptions
This commit is contained in:
73
UPDATELOG.md
73
UPDATELOG.md
@@ -2,63 +2,50 @@
|
|||||||
|
|
||||||
### 🏆 重大改进
|
### 🏆 重大改进
|
||||||
|
|
||||||
- **核心架构升级**:与内核 `Mihomo` 采用 `IPC` 通信,不再依赖 `Restful API` 通信,提升性能和稳定性
|
- **核心通信架构升级**:采用全新通信机制,提升应用性能和稳定性
|
||||||
- **流量监控系统重构**:前端实现全新的增强流量监控系统,支持数据压缩、采样和智能缓存
|
- **流量监控系统重构**:全新的流量监控界面,支持更丰富的数据展示
|
||||||
- **数据验证机制**:引入类型安全的数据验证器,确保 `API` 响应数据的一致性和可靠性
|
- **数据缓存优化**:改进配置和节点数据缓存,提升响应速度
|
||||||
- **配置缓存架构**:实现智能配置缓存系统,支持后端数据缓存和强制刷新机制
|
|
||||||
|
|
||||||
### ✨ 新增功能
|
### ✨ 新增功能
|
||||||
|
|
||||||
- **Mihomo(Meta) 内核升级至 v1.19.12**
|
- **Mihomo(Meta) 内核升级至 v1.19.12**
|
||||||
- 增加 `Verge Version` 复制按钮
|
- 新增版本信息复制按钮
|
||||||
- 新增增强型流量监控 `Hook` 支持高级数据管理与采样
|
- 增强型流量监控,支持更详细的数据分析
|
||||||
- 支持原始/压缩流量数据处理与时间范围查询
|
- 新增流量图表多种显示模式
|
||||||
- 引用计数管理器智能收集数据
|
- 新增强制刷新配置和节点缓存功能
|
||||||
- 新增流量监控诊断工具与错误边界组件
|
- 添加首页卡片移动功能(测试阶段)
|
||||||
- 多版本画布流量图表,丰富可视化选项
|
- 首页流量统计支持查看刻度线详情
|
||||||
- 新增强制刷新 `Clash` 配置/节点缓存功能,提升更新响应速度
|
|
||||||
- 增加代理请求缓存机制,减少重复 `API` 调用
|
|
||||||
- 添加首页卡片移动 (暂测)
|
|
||||||
- 首页流量统计卡片允许查看刻度线流量
|
|
||||||
|
|
||||||
### 🚀 性能优化
|
### 🚀 性能优化
|
||||||
|
|
||||||
- `IPC` 通信机制显著提升数据传输效率
|
- 全面提升数据传输和处理效率
|
||||||
- 智能数据采样和压缩减少内存占用
|
- 优化内存使用,减少系统资源消耗
|
||||||
- 引用计数机制避免不必要的数据收集,提升整体性能
|
- 改进流量图表渲染性能
|
||||||
- 优化流量图表渲染性能,支持大数据量展示
|
- 优化配置和节点刷新策略,从5秒延长到60秒
|
||||||
- 改进前端数据获取和缓存策略
|
- 改进数据缓存机制,减少重复请求
|
||||||
- 实现配置/节点缓存 `TTL` 机制,减少不必要的配置/节点请求
|
|
||||||
- 改进 `Clash` 配置/节点刷新间隔,从5秒优化到60秒,减少系统资源消耗
|
|
||||||
- 同步设置页面所有按钮
|
|
||||||
|
|
||||||
### 🐞 修复问题
|
### 🐞 修复问题
|
||||||
|
|
||||||
- 修复系统主题窗口颜色不一致问题
|
- 修复系统主题窗口颜色不一致问题
|
||||||
- 修复 `URL` 编码处理,正确处理特殊字符
|
- 修复特殊字符 URL 处理问题
|
||||||
- 增强代理更新的错误处理机制
|
- 修复配置修改后缓存不同步问题
|
||||||
- 修复 `JSON` 解析错误处理
|
- 修复 Windows 安装器自启设置问题
|
||||||
- 优化调试日志输出,减少噪音
|
- 修复 macOS 下 Dock 图标恢复窗口问题
|
||||||
- 修复配置修改后前端缓存不同步问题
|
- 修复架构升级后节点测速功能异常
|
||||||
- 改进核心启动/停止/重启后的状态刷新机制
|
- 修复架构升级后流量统计功能异常
|
||||||
- 修复 `Windows` 安装器删除用户自启问题
|
- 修复架构升级后日志功能异常
|
||||||
- 修复 `Windows` 安装器参数使用错误问题
|
- 修复外部控制器跨域配置保存问题
|
||||||
- 修复 `macOS` 下点击 `Dock` 图标无法恢复窗口显示的问题
|
- 修复首页端口显示不一致问题
|
||||||
- 修复 `IPC` 迁移后节点测速功能异常
|
- 修复首页流量统计刻度线显示问题
|
||||||
- 修复 `IPC` 迁移后连接上下行速率计算功能异常
|
- 修复日志页面按钮功能混淆问题
|
||||||
- 修复 `IPC` 迁移后内核日志功能异常
|
- 修复日志等级设置保存问题
|
||||||
- 修复 `External-Controller-Cors` 无法保存所需前置条件
|
- 修复偶发性启动卡死问题
|
||||||
- 修复首页端口不一致问题
|
|
||||||
- 修复首页流量统计卡片重构后无法显示流量刻度线
|
|
||||||
- 修复日志页面启动/停止和清除按钮功能混淆,现在启动/停止按钮控制后端日志监控,清除按钮仅清理前端显示的日志
|
|
||||||
- 修复日志等级设置的持久化配置,首次加载时正确应用已保存的日志等级到后端
|
|
||||||
|
|
||||||
### 🔧 技术改进
|
### 🔧 技术改进
|
||||||
|
|
||||||
- 移除过时的 `Http` 控制 `Mihomo` 统一使用 `IPC` 控制
|
- 统一使用新的内核通信方式
|
||||||
- 添加外部控制器配置和 `UI` 支持
|
- 新增外部控制器配置界面
|
||||||
- 改进 `IPC` 路径处理,支持 `Unix` 系统特定功能
|
- 改进跨平台兼容性支持
|
||||||
- 优化 `IPC` 目录安全检查和路径解析
|
|
||||||
|
|
||||||
## v2.3.2
|
## v2.3.2
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult
|
|||||||
}
|
}
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
logging!(error, Type::Cmd, true, "[导入订阅] 导入失败: {}", e);
|
logging!(error, Type::Cmd, true, "[导入订阅] 导入失败: {}", e);
|
||||||
Err(format!("导入订阅失败: {}", e).into())
|
Err(format!("导入订阅失败: {e}"))
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logging!(error, Type::Cmd, true, "[导入订阅] 导入超时(60秒): {}", url);
|
logging!(error, Type::Cmd, true, "[导入订阅] 导入超时(60秒): {}", url);
|
||||||
|
|||||||
@@ -127,7 +127,13 @@ pub async fn find_unused_port() -> Result<u16> {
|
|||||||
/// 异步方式处理启动后的额外任务
|
/// 异步方式处理启动后的额外任务
|
||||||
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||||
let start_time = std::time::Instant::now();
|
let start_time = std::time::Instant::now();
|
||||||
logging!(info, Type::Setup, true, "开始执行异步设置任务...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
"开始执行异步设置任务... 线程ID: {:?}",
|
||||||
|
std::thread::current().id()
|
||||||
|
);
|
||||||
|
|
||||||
if VERSION.get().is_none() {
|
if VERSION.get().is_none() {
|
||||||
let version = app_handle.package_info().version.to_string();
|
let version = app_handle.package_info().version.to_string();
|
||||||
@@ -151,17 +157,27 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(trace, Type::Config, true, "初始化配置...");
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"开始初始化配置... 线程ID: {:?}",
|
||||||
|
std::thread::current().id()
|
||||||
|
);
|
||||||
logging_error!(Type::Config, true, Config::init_config().await);
|
logging_error!(Type::Config, true, Config::init_config().await);
|
||||||
|
logging!(info, Type::Config, true, "配置初始化完成");
|
||||||
|
|
||||||
// 启动时清理冗余的 Profile 文件
|
// 启动时清理冗余的 Profile 文件
|
||||||
logging!(info, Type::Setup, true, "清理冗余的Profile文件...");
|
logging!(info, Type::Setup, true, "开始清理冗余的Profile文件...");
|
||||||
let profiles = Config::profiles();
|
|
||||||
if let Err(e) = profiles.latest_ref().auto_cleanup() {
|
match Config::profiles().latest_ref().auto_cleanup() {
|
||||||
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
Ok(_) => {
|
||||||
} else {
|
|
||||||
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
||||||
}
|
}
|
||||||
|
Err(e) => {
|
||||||
|
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logging!(trace, Type::Core, true, "启动核心管理器...");
|
logging!(trace, Type::Core, true, "启动核心管理器...");
|
||||||
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
logging_error!(Type::Core, true, CoreManager::global().init().await);
|
||||||
@@ -170,30 +186,6 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
|||||||
server::embed_server();
|
server::embed_server();
|
||||||
|
|
||||||
logging!(trace, Type::Core, true, "启动 IPC 监控服务...");
|
logging!(trace, Type::Core, true, "启动 IPC 监控服务...");
|
||||||
// IPC 监控器将在首次调用时自动初始化
|
|
||||||
|
|
||||||
// // 启动测试线程,持续打印流量数据
|
|
||||||
// logging!(info, Type::Core, true, "启动流量数据测试线程...");
|
|
||||||
// AsyncHandler::spawn(|| async {
|
|
||||||
// let mut interval = tokio::time::interval(std::time::Duration::from_secs(2));
|
|
||||||
// loop {
|
|
||||||
// interval.tick().await;
|
|
||||||
|
|
||||||
// let traffic_data = get_current_traffic().await;
|
|
||||||
// let memory_data = get_current_memory().await;
|
|
||||||
|
|
||||||
// println!("=== Traffic Data Test (IPC) ===");
|
|
||||||
// println!(
|
|
||||||
// "Traffic - Up: {} bytes/s, Down: {} bytes/s, Last Updated: {:?}",
|
|
||||||
// traffic_data.up_rate, traffic_data.down_rate, traffic_data.last_updated
|
|
||||||
// );
|
|
||||||
// println!(
|
|
||||||
// "Memory - InUse: {} bytes, OSLimit: {:?}, Last Updated: {:?}",
|
|
||||||
// memory_data.inuse, memory_data.oslimit, memory_data.last_updated
|
|
||||||
// );
|
|
||||||
// println!("==============================");
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
logging_error!(Type::Tray, true, tray::Tray::global().init());
|
logging_error!(Type::Tray, true, tray::Tray::global().init());
|
||||||
|
|
||||||
@@ -483,16 +475,52 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
timeout_seconds
|
timeout_seconds
|
||||||
);
|
);
|
||||||
|
|
||||||
// 异步监控UI状态,不影响窗口显示
|
// 异步监控UI状态,使用try_read避免死锁
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let wait_result =
|
logging!(
|
||||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
debug,
|
||||||
let mut check_count = 0;
|
Type::Window,
|
||||||
while !*get_ui_ready().read() {
|
true,
|
||||||
|
"启动UI状态监控线程,超时{}秒",
|
||||||
|
timeout_seconds
|
||||||
|
);
|
||||||
|
|
||||||
|
let ui_ready_checker = || async {
|
||||||
|
let (mut check_count, mut consecutive_failures) = (0, 0);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let is_ready = get_ui_ready()
|
||||||
|
.try_read()
|
||||||
|
.map(|guard| *guard)
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
consecutive_failures += 1;
|
||||||
|
if consecutive_failures > 50 {
|
||||||
|
logging!(
|
||||||
|
warn,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"UI状态监控连续{}次无法获取读锁,可能存在死锁",
|
||||||
|
consecutive_failures
|
||||||
|
);
|
||||||
|
consecutive_failures = 0;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_ready {
|
||||||
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"UI状态监控检测到就绪信号,退出监控"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
consecutive_failures = 0;
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
check_count += 1;
|
check_count += 1;
|
||||||
|
|
||||||
// 每2秒记录一次等待状态
|
|
||||||
if check_count % 20 == 0 {
|
if check_count % 20 == 0 {
|
||||||
logging!(
|
logging!(
|
||||||
debug,
|
debug,
|
||||||
@@ -503,22 +531,26 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
|
|
||||||
|
let wait_result = tokio::time::timeout(
|
||||||
|
Duration::from_secs(timeout_seconds),
|
||||||
|
ui_ready_checker(),
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match wait_result {
|
match wait_result {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
logging!(info, Type::Window, true, "UI已完全加载就绪");
|
logging!(info, Type::Window, true, "UI已完全加载就绪");
|
||||||
// 移除初始加载指示器
|
handle::Handle::global()
|
||||||
if let Some(window) = handle::Handle::global().get_window() {
|
.get_window()
|
||||||
let _ = window.eval(r#"
|
.map(|window| window.eval(r#"
|
||||||
const overlay = document.getElementById('initial-loading-overlay');
|
const overlay = document.getElementById('initial-loading-overlay');
|
||||||
if (overlay) {
|
if (overlay) {
|
||||||
overlay.style.opacity = '0';
|
overlay.style.opacity = '0';
|
||||||
setTimeout(() => overlay.remove(), 300);
|
setTimeout(() => overlay.remove(), 300);
|
||||||
}
|
}
|
||||||
"#);
|
"#));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
logging!(
|
logging!(
|
||||||
@@ -528,7 +560,26 @@ pub fn create_window(is_show: bool) -> bool {
|
|||||||
"UI加载监控超时({}秒),但窗口已正常显示",
|
"UI加载监控超时({}秒),但窗口已正常显示",
|
||||||
timeout_seconds
|
timeout_seconds
|
||||||
);
|
);
|
||||||
*get_ui_ready().write() = true;
|
|
||||||
|
get_ui_ready()
|
||||||
|
.try_write()
|
||||||
|
.map(|mut guard| {
|
||||||
|
*guard = true;
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"超时后成功设置UI就绪状态"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
logging!(
|
||||||
|
error,
|
||||||
|
Type::Window,
|
||||||
|
true,
|
||||||
|
"超时后无法获取UI状态写锁,可能存在严重死锁"
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -633,24 +684,37 @@ async fn resolve_random_port_config() -> Result<()> {
|
|||||||
|
|
||||||
let port_to_save = port;
|
let port_to_save = port;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
// 合并配置访问以避免锁竞争死锁
|
||||||
let verge_config_accessor = Config::verge();
|
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||||
let mut verge_data = verge_config_accessor.data_mut();
|
logging!(
|
||||||
|
debug,
|
||||||
|
Type::Config,
|
||||||
|
true,
|
||||||
|
"开始合并配置更新操作,避免锁竞争"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 按顺序更新配置,避免交叉锁定
|
||||||
|
{
|
||||||
|
let verge_accessor = Config::verge();
|
||||||
|
let mut verge_data = verge_accessor.data_mut();
|
||||||
verge_data.patch_config(IVerge {
|
verge_data.patch_config(IVerge {
|
||||||
verge_mixed_port: Some(port_to_save),
|
verge_mixed_port: Some(port_to_save),
|
||||||
..IVerge::default()
|
..IVerge::default()
|
||||||
});
|
});
|
||||||
verge_data.save_file()
|
verge_data.save_file()?;
|
||||||
})
|
}
|
||||||
.await??; // First ? for spawn_blocking error, second for save_file Result
|
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
{
|
||||||
let clash_config_accessor = Config::clash(); // Extend lifetime of the accessor
|
let clash_accessor = Config::clash();
|
||||||
let mut clash_data = clash_config_accessor.data_mut(); // Access within blocking task, made mutable
|
let mut clash_data = clash_accessor.data_mut();
|
||||||
let mut mapping = Mapping::new();
|
let mut mapping = Mapping::new();
|
||||||
mapping.insert("mixed-port".into(), port_to_save.into());
|
mapping.insert("mixed-port".into(), port_to_save.into());
|
||||||
clash_data.patch_config(mapping);
|
clash_data.patch_config(mapping);
|
||||||
clash_data.save_config()
|
clash_data.save_config()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
logging!(debug, Type::Config, true, "配置更新操作完成");
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user