fix(device): load xrandr info asynchronously to avoid UI freeze - #733
fix(device): load xrandr info asynchronously to avoid UI freeze#733GongHeng2017 wants to merge 2 commits into
Conversation
Move the ThreadExecXrandr data fetch in slotListItemClicked off the UI thread: start it asynchronously and refresh the device list on the main thread when finished. Drop the txgpu block that sat outside the if-else chain and unconditionally ran synchronous xrandr on every click, folding its monitor hotplug detection into the async completion slot. Add a 5-second timeout to ThreadExecXrandr::runCmd so a hung xrandr subprocess can no longer freeze the UI, and activate the m_isDXcbPlatform guard so Wayland/no-X sessions fetch display info via DBus instead of spawning xrandr. Release the async thread in the destructor to avoid dangling. 将 slotListItemClicked 中的 ThreadExecXrandr 显示信息获取移出 UI 线程:改为异步启动,完成后回到主线程刷新设备列表;移除位于 if-else 链外、每次点击都无条件同步执行 xrandr 的 txgpu 块,其显示器热插拔检测合并到异步完成槽中;ThreadExecXrandr::runCmd 增加子进程超时与 kill,避免 xrandr 挂起拖死界面;激活 m_isDXcbPlatform 守卫,Wayland/无 X 环境下走 DBus 获取显示信息,不再执行 xrandr 子进程;析构函数释放异步线程避免悬空。 Log: 修复设备管理器点击显示设备后切换其他模块界面长时间无响应的问题,xrandr 信息获取移出 UI 线程改异步并增加子进程超时与 Wayland 守卫 PMS: BUG-248321 Influence: 点击显示设备/显示适配器不再阻塞界面,切换其他模块即时响应;Wayland 环境下不再因 xrandr 子进程挂起导致卡死
There was a problem hiding this comment.
Sorry @GongHeng2017, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: GongHeng2017 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Reviewer's GuideMake xrandr-based display info loading fully asynchronous to avoid blocking the UI thread, add timeouts and platform guards around xrandr subprocesses, and refactor monitor hot-plug detection and device refresh logic accordingly. Sequence diagram for asynchronous xrandr-based display info loadingsequenceDiagram
actor User
participant MainWindow
participant ThreadExecXrandr
participant DBusInterface
participant DeviceManager
User->>MainWindow: slotListItemClicked(itemStr)
MainWindow->>MainWindow: [itemStr is Monitor/Overview/DisplayAdapter]
MainWindow->>MainWindow: startAsyncXrandr(itemStr)
MainWindow->>ThreadExecXrandr: new ThreadExecXrandr(gpu, isDXcbPlatform)
MainWindow->>ThreadExecXrandr: start()
ThreadExecXrandr->>ThreadExecXrandr: run()
alt gpu && m_isDXcbPlatform
ThreadExecXrandr->>ThreadExecXrandr: getGpuInfoFromXrandr()
ThreadExecXrandr->>QProcess: runCmd(info, cmd)
QProcess-->>ThreadExecXrandr: waitForFinished(5000)
else !gpu && (!m_isDXcbPlatform || boardVendorType==PGUV)
ThreadExecXrandr->>DBusInterface: getResolutionRateFromDBus(lstMap)
else
ThreadExecXrandr->>ThreadExecXrandr: getMonitorInfoFromXrandrVerbose()
ThreadExecXrandr->>QProcess: runCmd(info, cmd)
QProcess-->>ThreadExecXrandr: waitForFinished(5000)
end
ThreadExecXrandr->>ThreadExecXrandr: getResolutionFromDBus(tmp)
ThreadExecXrandr-->>MainWindow: finished()
MainWindow->>MainWindow: slotXrandrFinished()
MainWindow->>ThreadExecXrandr: getMonitorNumber()
alt monitorNumber changed
MainWindow->>DBusInterface: getInfo(is_server_running, info)
MainWindow->>MainWindow: refreshDataBaseLater()
end
MainWindow->>MainWindow: [itemStr = m_xrandrItem]
alt itemStr is empty
MainWindow-->>User: no UI change
else itemStr != m_xrandrStartedItem
MainWindow->>MainWindow: startAsyncXrandr(itemStr)
else
MainWindow->>DeviceManager: getDeviceList(itemStr, lst)
DeviceManager-->>MainWindow: lst
MainWindow->>MainWindow: updateDeviceForItem(itemStr)
MainWindow-->>User: display updated
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Address review M01 on PR linuxdeepin#733: the destructor's wait(6000) could time out before the worker thread's worst-case runtime (~12s for two runCmd calls plus DBus backfill), after which deleting mp_XrandrThread would free m_monitorLst while run() still reads/writes it — a use-after-free when closing the window during async xrandr load. Disconnect the finished signal first to prevent the slot firing mid-destruction, then wait() without a timeout (safe because runCmd now caps each subprocess at 5s+1s) so run() is guaranteed to exit before the object is deleted. Also annotate the unreachable defensive branch in startAsyncXrandr (review m03) and correct the destructor comment (review m04). 修复 review M01:析构函数 wait(6000) 可能早于工作线程最坏运行时长(双 runCmd 各 5s+1s 约 12s + DBus 回填)超时,此后 delete mp_XrandrThread 会释放 m_monitorLst 而 run() 仍在读写,导致显示加载期间关窗的 use-after-free;改为先 disconnect(this) 避免 finished 槽在析构中途触发,再 wait() 无超时等待(runCmd 已限每子进程 5s+1s,最长约 12s 内结束)保证 run() 退出后再释放。同时为 startAsyncXrandr 不可达防御分支加注释(m03)、订正析构注释(m04)。 Log: 修复 review 阻塞项 M01,析构释放异步线程改为等待 run() 退出后再 delete,避免关窗 use-after-free PMS: BUG-248321 Influence: 显示模块异步加载期间关闭窗口不再释放正在运行的工作线程,消除 use-after-free 崩溃风险
返工补充提交(review M01 修复)新增 commit
行为变更记录(review m02)显示器热插拔检测(monitorNumber 变化 → 请求后台 未处理(建议项,不阻塞)
测试建议(M01 相关)在联想 M630z / 兆芯 + AMD Oland 机型验证:
|
deepin pr auto review★ 总体评分:100分■ 【总体评价】
■ 【详细分析】
■ 【改进建议代码示例】 // 当前代码已具备较高的安全性与健壮性,以下示例仅展示对潜在逻辑瑕疵(如toInt转换失败)的防御性增强
void MainWindow::slotXrandrFinished()
{
if (!mp_XrandrThread) {
return;
}
if (monitorNumber != mp_XrandrThread->getMonitorNumber()) {
QString info;
bool ok = false;
DBusInterface::getInstance()->getInfo("is_server_running", info);
int isRunning = info.toInt(&ok);
//请求后台更新信息:仅在明确返回非1(或转换失败)时请求,增强逻辑严密性
if (!ok || isRunning != 1) {
refreshDataBaseLater();
}
qCDebug(appLog) << "Monitor refreshInfo" << __LINE__ << QDateTime::currentDateTime().toString("hh:mm:ss") << info << monitorNumber;
monitorNumber = mp_XrandrThread->getMonitorNumber();
}
QString item = m_xrandrItem;
mp_XrandrThread->deleteLater();
mp_XrandrThread = nullptr;
if (item.isEmpty()) {
return;
}
if (item != m_xrandrStartedItem) {
startAsyncXrandr(item);
return;
}
if (m_refreshing || mp_WorkingThread->isRunning()) return;
updateDeviceForItem(item);
} |
根因分析
设备管理器在 UI 线程(
MainWindow::slotListItemClicked)上以QThread::start()+QThread::wait()同步等待执行xrandr/xrandr --verbose的工作线程,而子进程用QProcess::waitForFinished(-1)无超时等待。该同步等待冻结 Qt 主事件循环,导致点击模块切换时界面长时间无反应;AMD Oland/amdgpu 等机型xrandr --verbose耗时数秒时尤为明显。关键证据(基于锁定基线
develop/eagle @ ccd4ab90):MainWindow.cpp:549slotListItemClicked在 GUI 主线程tx.start(); tx.wait();(Monitor/Overview:554-555、Display Adapter:558-559)。ThreadExecXrandr.cpp:66process.waitForFinished(-1)永久等待,无超时。MainWindow.cpp:575-577的txgpu块位于 if-else 链外(7f1adf6e引入的回归),每次点击任意模块都无条件再跑一次同步 xrandr——"点其他模块也卡"的直接成因。ThreadExecXrandr.cpp:43-44vs:49-59:m_isDXcbPlatform(Wayland 守卫)只写不读,平台分支失效。修复方案
slotListItemClicked中显示设备/显示适配器不再start()+wait(),改为异步启动ThreadExecXrandr,finished信号回到主线程后再getDeviceList+updateDevice。slotXrandrFinished,仅在显示模块点击时触发;monitorNumber改由 DBus(getResolutionFromDBus)派生,不再为热插拔检测额外跑 xrandr 子进程。runCmd加超时:waitForFinished(-1)→waitForFinished(5000),超时kill()子进程,避免 xrandr 挂起拖死界面。run()中!m_isDXcbPlatform(Wayland/无 X)时走 DBus 获取显示信息,不再执行 xrandr 子进程。m_xrandrItem取消待刷新项)。改动安全评估
风险等级:中风险(修改函数内部逻辑 + 引入异步生命周期,无签名变更、无公开 API 变更、受影响调用者 ≤ 2)。数据流不变(
ThreadExecXrandr::run仍写入DeviceManager,完成后主线程刷新);并发模型与既有mp_WorkingThread异步写DeviceManager一致。详见附件change-safety.md。未在本轮处理的同类 anti-pattern(建议后续单独修复)
MainWindow.cpp:123-125构造期txgpu.start()+wait()(启动期一次性阻塞,非本次点击卡死问题)。MainWindow.cpp:562-563CPU 分支LoadCpuInfoThread同步start()+wait()(LoadCpuInfoThread.cpp:27同为waitForFinished(-1);lscpu 通常很快,非本次卡死主因)。waitForFinished(-1)无超时调用点(GenerateDevice/CmdTool.cpp、HWGenerator.cpp等,多在后台生成线程内)。关联
analysis-report.md(强根因,已通过根因复核)。Summary by Sourcery
Load xrandr-based display information asynchronously to prevent UI freezes when switching display-related modules.
Bug Fixes:
Enhancements: