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:
Tunglies
2025-08-06 22:11:42 +08:00
parent a9cfb2cfaa
commit 499626b946
3 changed files with 169 additions and 118 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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,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!(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文件...");
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, "启动核心管理器...");
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();
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,16 +475,52 @@ 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() {
logging!(
debug,
Type::Window,
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;
check_count += 1;
// 每2秒记录一次等待状态
if check_count % 20 == 0 {
logging!(
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;
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();
// 合并配置访问以避免锁竞争死锁
tokio::task::spawn_blocking(move || -> Result<()> {
logging!(
debug,
Type::Config,
true,
"开始合并配置更新操作,避免锁竞争"
);
// 按顺序更新配置,避免交叉锁定
{
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()
})
.await??; // First ? for spawn_blocking error, second for save_file Result
verge_data.save_file()?;
}
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 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()
clash_data.save_config()?;
}
logging!(debug, Type::Config, true, "配置更新操作完成");
Ok(())
})
.await??;