mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-28 16:30:52 +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**
|
||||
- 增加 `Verge Version` 复制按钮
|
||||
- 新增增强型流量监控 `Hook` 支持高级数据管理与采样
|
||||
- 支持原始/压缩流量数据处理与时间范围查询
|
||||
- 引用计数管理器智能收集数据
|
||||
- 新增流量监控诊断工具与错误边界组件
|
||||
- 多版本画布流量图表,丰富可视化选项
|
||||
- 新增强制刷新 `Clash` 配置/节点缓存功能,提升更新响应速度
|
||||
- 增加代理请求缓存机制,减少重复 `API` 调用
|
||||
- 添加首页卡片移动 (暂测)
|
||||
- 首页流量统计卡片允许查看刻度线流量
|
||||
- 新增版本信息复制按钮
|
||||
- 增强型流量监控,支持更详细的数据分析
|
||||
- 新增流量图表多种显示模式
|
||||
- 新增强制刷新配置和节点缓存功能
|
||||
- 添加首页卡片移动功能(测试阶段)
|
||||
- 首页流量统计支持查看刻度线详情
|
||||
|
||||
### 🚀 性能优化
|
||||
|
||||
- `IPC` 通信机制显著提升数据传输效率
|
||||
- 智能数据采样和压缩减少内存占用
|
||||
- 引用计数机制避免不必要的数据收集,提升整体性能
|
||||
- 优化流量图表渲染性能,支持大数据量展示
|
||||
- 改进前端数据获取和缓存策略
|
||||
- 实现配置/节点缓存 `TTL` 机制,减少不必要的配置/节点请求
|
||||
- 改进 `Clash` 配置/节点刷新间隔,从5秒优化到60秒,减少系统资源消耗
|
||||
- 同步设置页面所有按钮
|
||||
- 全面提升数据传输和处理效率
|
||||
- 优化内存使用,减少系统资源消耗
|
||||
- 改进流量图表渲染性能
|
||||
- 优化配置和节点刷新策略,从5秒延长到60秒
|
||||
- 改进数据缓存机制,减少重复请求
|
||||
|
||||
### 🐞 修复问题
|
||||
|
||||
- 修复系统主题窗口颜色不一致问题
|
||||
- 修复 `URL` 编码处理,正确处理特殊字符
|
||||
- 增强代理更新的错误处理机制
|
||||
- 修复 `JSON` 解析错误处理
|
||||
- 优化调试日志输出,减少噪音
|
||||
- 修复配置修改后前端缓存不同步问题
|
||||
- 改进核心启动/停止/重启后的状态刷新机制
|
||||
- 修复 `Windows` 安装器删除用户自启问题
|
||||
- 修复 `Windows` 安装器参数使用错误问题
|
||||
- 修复 `macOS` 下点击 `Dock` 图标无法恢复窗口显示的问题
|
||||
- 修复 `IPC` 迁移后节点测速功能异常
|
||||
- 修复 `IPC` 迁移后连接上下行速率计算功能异常
|
||||
- 修复 `IPC` 迁移后内核日志功能异常
|
||||
- 修复 `External-Controller-Cors` 无法保存所需前置条件
|
||||
- 修复首页端口不一致问题
|
||||
- 修复首页流量统计卡片重构后无法显示流量刻度线
|
||||
- 修复日志页面启动/停止和清除按钮功能混淆,现在启动/停止按钮控制后端日志监控,清除按钮仅清理前端显示的日志
|
||||
- 修复日志等级设置的持久化配置,首次加载时正确应用已保存的日志等级到后端
|
||||
- 修复特殊字符 URL 处理问题
|
||||
- 修复配置修改后缓存不同步问题
|
||||
- 修复 Windows 安装器自启设置问题
|
||||
- 修复 macOS 下 Dock 图标恢复窗口问题
|
||||
- 修复架构升级后节点测速功能异常
|
||||
- 修复架构升级后流量统计功能异常
|
||||
- 修复架构升级后日志功能异常
|
||||
- 修复外部控制器跨域配置保存问题
|
||||
- 修复首页端口显示不一致问题
|
||||
- 修复首页流量统计刻度线显示问题
|
||||
- 修复日志页面按钮功能混淆问题
|
||||
- 修复日志等级设置保存问题
|
||||
- 修复偶发性启动卡死问题
|
||||
|
||||
### 🔧 技术改进
|
||||
|
||||
- 移除过时的 `Http` 控制 `Mihomo` 统一使用 `IPC` 控制
|
||||
- 添加外部控制器配置和 `UI` 支持
|
||||
- 改进 `IPC` 路径处理,支持 `Unix` 系统特定功能
|
||||
- 优化 `IPC` 目录安全检查和路径解析
|
||||
- 统一使用新的内核通信方式
|
||||
- 新增外部控制器配置界面
|
||||
- 改进跨平台兼容性支持
|
||||
|
||||
## v2.3.2
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@ pub async fn import_profile(url: String, option: Option<PrfOption>) -> CmdResult
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
logging!(error, Type::Cmd, true, "[导入订阅] 导入失败: {}", e);
|
||||
Err(format!("导入订阅失败: {}", e).into())
|
||||
Err(format!("导入订阅失败: {e}"))
|
||||
}
|
||||
Err(_) => {
|
||||
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) {
|
||||
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() {
|
||||
let version = app_handle.package_info().version.to_string();
|
||||
@@ -151,16 +157,26 @@ 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!(info, Type::Config, true, "配置初始化完成");
|
||||
|
||||
// 启动时清理冗余的 Profile 文件
|
||||
logging!(info, Type::Setup, true, "清理冗余的Profile文件...");
|
||||
let profiles = Config::profiles();
|
||||
if let Err(e) = profiles.latest_ref().auto_cleanup() {
|
||||
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
||||
} else {
|
||||
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
||||
logging!(info, Type::Setup, true, "开始清理冗余的Profile文件...");
|
||||
|
||||
match Config::profiles().latest_ref().auto_cleanup() {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Setup, true, "启动时Profile文件清理完成");
|
||||
}
|
||||
Err(e) => {
|
||||
logging!(warn, Type::Setup, true, "启动时清理Profile文件失败: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
logging!(trace, Type::Core, true, "启动核心管理器...");
|
||||
@@ -170,30 +186,6 @@ pub async fn resolve_setup_async(app_handle: &AppHandle) {
|
||||
server::embed_server();
|
||||
|
||||
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());
|
||||
|
||||
@@ -483,42 +475,82 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
timeout_seconds
|
||||
);
|
||||
|
||||
// 异步监控UI状态,不影响窗口显示
|
||||
// 异步监控UI状态,使用try_read避免死锁
|
||||
tokio::spawn(async move {
|
||||
let wait_result =
|
||||
tokio::time::timeout(Duration::from_secs(timeout_seconds), async {
|
||||
let mut check_count = 0;
|
||||
while !*get_ui_ready().read() {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
check_count += 1;
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"启动UI状态监控线程,超时{}秒",
|
||||
timeout_seconds
|
||||
);
|
||||
|
||||
// 每2秒记录一次等待状态
|
||||
if check_count % 20 == 0 {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"UI加载状态检查... ({}秒)",
|
||||
check_count / 10
|
||||
);
|
||||
}
|
||||
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;
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
consecutive_failures = 0;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
check_count += 1;
|
||||
|
||||
if check_count % 20 == 0 {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Window,
|
||||
true,
|
||||
"UI加载状态检查... ({}秒)",
|
||||
check_count / 10
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let wait_result = tokio::time::timeout(
|
||||
Duration::from_secs(timeout_seconds),
|
||||
ui_ready_checker(),
|
||||
)
|
||||
.await;
|
||||
|
||||
match wait_result {
|
||||
Ok(_) => {
|
||||
logging!(info, Type::Window, true, "UI已完全加载就绪");
|
||||
// 移除初始加载指示器
|
||||
if let Some(window) = handle::Handle::global().get_window() {
|
||||
let _ = window.eval(r#"
|
||||
handle::Handle::global()
|
||||
.get_window()
|
||||
.map(|window| window.eval(r#"
|
||||
const overlay = document.getElementById('initial-loading-overlay');
|
||||
if (overlay) {
|
||||
overlay.style.opacity = '0';
|
||||
setTimeout(() => overlay.remove(), 300);
|
||||
}
|
||||
"#);
|
||||
}
|
||||
"#));
|
||||
}
|
||||
Err(_) => {
|
||||
logging!(
|
||||
@@ -528,7 +560,26 @@ pub fn create_window(is_show: bool) -> bool {
|
||||
"UI加载监控超时({}秒),但窗口已正常显示",
|
||||
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;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let verge_config_accessor = Config::verge();
|
||||
let mut verge_data = verge_config_accessor.data_mut();
|
||||
verge_data.patch_config(IVerge {
|
||||
verge_mixed_port: Some(port_to_save),
|
||||
..IVerge::default()
|
||||
});
|
||||
verge_data.save_file()
|
||||
})
|
||||
.await??; // First ? for spawn_blocking error, second for save_file Result
|
||||
// 合并配置访问以避免锁竞争死锁
|
||||
tokio::task::spawn_blocking(move || -> Result<()> {
|
||||
logging!(
|
||||
debug,
|
||||
Type::Config,
|
||||
true,
|
||||
"开始合并配置更新操作,避免锁竞争"
|
||||
);
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let clash_config_accessor = Config::clash(); // Extend lifetime of the accessor
|
||||
let mut clash_data = clash_config_accessor.data_mut(); // Access within blocking task, made mutable
|
||||
let mut mapping = Mapping::new();
|
||||
mapping.insert("mixed-port".into(), port_to_save.into());
|
||||
clash_data.patch_config(mapping);
|
||||
clash_data.save_config()
|
||||
// 按顺序更新配置,避免交叉锁定
|
||||
{
|
||||
let verge_accessor = Config::verge();
|
||||
let mut verge_data = verge_accessor.data_mut();
|
||||
verge_data.patch_config(IVerge {
|
||||
verge_mixed_port: Some(port_to_save),
|
||||
..IVerge::default()
|
||||
});
|
||||
verge_data.save_file()?;
|
||||
}
|
||||
|
||||
{
|
||||
let clash_accessor = Config::clash();
|
||||
let mut clash_data = clash_accessor.data_mut();
|
||||
let mut mapping = Mapping::new();
|
||||
mapping.insert("mixed-port".into(), port_to_save.into());
|
||||
clash_data.patch_config(mapping);
|
||||
clash_data.save_config()?;
|
||||
}
|
||||
|
||||
logging!(debug, Type::Config, true, "配置更新操作完成");
|
||||
Ok(())
|
||||
})
|
||||
.await??;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user