From 2bbed8db495fedcf7b87458673dc5b1623881456 Mon Sep 17 00:00:00 2001 From: gongheng Date: Mon, 17 Aug 2026 19:01:22 +0800 Subject: [PATCH 1/2] fix(device): load xrandr info asynchronously to avoid UI freeze MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 子进程挂起导致卡死 --- deepin-devicemanager/src/Page/MainWindow.cpp | 97 ++++++++++++++----- deepin-devicemanager/src/Page/MainWindow.h | 21 ++++ .../src/Tool/ThreadExecXrandr.cpp | 28 ++++-- 3 files changed, 117 insertions(+), 29 deletions(-) diff --git a/deepin-devicemanager/src/Page/MainWindow.cpp b/deepin-devicemanager/src/Page/MainWindow.cpp index ff6c1db6..b337e971 100644 --- a/deepin-devicemanager/src/Page/MainWindow.cpp +++ b/deepin-devicemanager/src/Page/MainWindow.cpp @@ -140,6 +140,13 @@ void MainWindow::refreshDataBaseLater() MainWindow::~MainWindow() { // 释放指针 + // 释放异步 xrandr 线程(runCmd 已设超时,wait 不会长时间阻塞) + if (mp_XrandrThread) { + if (mp_XrandrThread->isRunning()) + mp_XrandrThread->wait(6000); + delete mp_XrandrThread; + mp_XrandrThread = nullptr; + } if (mp_WorkingThread && mp_WorkingThread->isRunning()) mp_WorkingThread->terminate(); while (mp_WorkingThread && mp_WorkingThread->isRunning()) {} @@ -548,16 +555,21 @@ void MainWindow::slotLoadingFinish(const QString &message) void MainWindow::slotListItemClicked(const QString &itemStr) { - // xrandr would be execed later - if (tr("Monitor") == itemStr || tr("Overview") == itemStr) { //点击显示设备,执行线程加载信息 - ThreadExecXrandr tx(false, !checkWaylandMode()); - tx.start(); - tx.wait(); - } else if (tr("Display Adapter") == itemStr) { //点击显示适配器,执行线程加载信息 - ThreadExecXrandr tx(true, !checkWaylandMode()); - tx.start(); - tx.wait(); - } else if (tr("CPU") == itemStr) { //点击处理器,执行加载处理器信息线程 + // 显示相关模块:异步获取 xrandr 信息,避免在 UI 线程同步等待子进程阻塞事件循环 + if (tr("Monitor") == itemStr || tr("Overview") == itemStr || tr("Display Adapter") == itemStr) { + m_xrandrItem = itemStr; + // 上一次异步 xrandr 仍在运行时,仅记录最新待刷新项,待其结束后处理 + if (mp_XrandrThread && mp_XrandrThread->isRunning()) { + return; + } + startAsyncXrandr(itemStr); + return; + } + + // 切换到非显示模块时,取消待完成的显示模块刷新,避免异步完成后回切界面 + m_xrandrItem.clear(); + + if (tr("CPU") == itemStr) { //点击处理器,执行加载处理器信息线程 LoadCpuInfoThread lct; lct.start(); lct.wait(); @@ -572,24 +584,65 @@ void MainWindow::slotListItemClicked(const QString &itemStr) DeviceManager::instance()->correctPowerInfo(tool.getCurPowerInfo()); } - ThreadExecXrandr txgpu(true, !checkWaylandMode()); - txgpu.start(); - txgpu.wait(); - if(monitorNumber != txgpu.getMonitorNumber()) { + // 数据刷新时不处理界面刷新 + if (m_refreshing || mp_WorkingThread->isRunning()) return; - QString info; - DBusInterface::getInstance()->getInfo("is_server_running", info); - //请求后台更新信息 - if (!info.toInt()) { - refreshDataBaseLater(); - } - qCDebug(appLog)<< "Monitor refreshInfo" << __LINE__ << QDateTime::currentDateTime().toString("hh:mm:ss") << info << monitorNumber; - monitorNumber = txgpu.getMonitorNumber(); + updateDeviceForItem(itemStr); +} + +void MainWindow::startAsyncXrandr(const QString &itemStr) +{ + if (mp_XrandrThread) { + mp_XrandrThread->deleteLater(); + mp_XrandrThread = nullptr; + } + bool gpu = (tr("Display Adapter") == itemStr); + mp_XrandrThread = new ThreadExecXrandr(gpu, !checkWaylandMode()); + m_xrandrStartedItem = itemStr; + connect(mp_XrandrThread, &QThread::finished, this, &MainWindow::slotXrandrFinished); + mp_XrandrThread->start(); +} + +void MainWindow::slotXrandrFinished() +{ + if (!mp_XrandrThread) { + return; + } + + // 显示器热插拔检测:monitorNumber 变化则请求后台刷新信息 + if (monitorNumber != mp_XrandrThread->getMonitorNumber()) { + QString info; + DBusInterface::getInstance()->getInfo("is_server_running", info); + //请求后台更新信息 + if (!info.toInt()) { + 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; + } + // 异步执行期间切换到其他显示模块:按最新项重新加载对应 xrandr 信息 + if (item != m_xrandrStartedItem) { + startAsyncXrandr(item); + return; + } // 数据刷新时不处理界面刷新 if (m_refreshing || mp_WorkingThread->isRunning()) return; + updateDeviceForItem(item); +} + +void MainWindow::updateDeviceForItem(const QString &itemStr) +{ QList lst; bool ret = DeviceManager::instance()->getDeviceList(itemStr, lst); diff --git a/deepin-devicemanager/src/Page/MainWindow.h b/deepin-devicemanager/src/Page/MainWindow.h index 6a60c8fd..eb233e17 100644 --- a/deepin-devicemanager/src/Page/MainWindow.h +++ b/deepin-devicemanager/src/Page/MainWindow.h @@ -23,6 +23,7 @@ class DeviceWidget; class LoadInfoThread; class PageDriverManager; class DriverScanWidget; +class ThreadExecXrandr; using namespace Dtk::Widget; @@ -129,6 +130,18 @@ class MainWindow : public DMainWindow * @brief refreshDataBaseLater:刷新设备信息 */ void refreshDataBaseLater(); + + /** + * @brief startAsyncXrandr:异步获取 xrandr 显示信息,避免阻塞 UI 线程 + * @param itemStr:当前点击的设备模块名 + */ + void startAsyncXrandr(const QString &itemStr); + + /** + * @brief updateDeviceForItem:根据模块名获取设备列表并刷新界面 + * @param itemStr:设备模块名 + */ + void updateDeviceForItem(const QString &itemStr); private slots: /** * @brief slotSetPage @@ -158,6 +171,11 @@ private slots: */ void slotExportInfo(); + /** + * @brief slotXrandrFinished:异步 xrandr 信息加载完成槽 + */ + void slotXrandrFinished(); + /** * @brief changeUI:UI界面变化,BIOS界面行高 */ @@ -177,6 +195,9 @@ private slots: DriverScanWidget *mp_DriverScanWidget; //驱动管理扫描界面 PageDriverManager *mp_DriverManager; //驱动管理主界面 LoadInfoThread *mp_WorkingThread; //信息加载线程 + ThreadExecXrandr *mp_XrandrThread = nullptr; //异步获取 xrandr 信息的线程 + QString m_xrandrItem; //异步 xrandr 完成后待刷新的模块 + QString m_xrandrStartedItem; //当前异步 xrandr 实际加载的模块 DButtonBox *mp_ButtonBox; // titlebar上添加Buttonbox bool m_refreshing = false; // 判断界面是否正在刷新 bool m_IsFirstRefresh = true; diff --git a/deepin-devicemanager/src/Tool/ThreadExecXrandr.cpp b/deepin-devicemanager/src/Tool/ThreadExecXrandr.cpp index 2d3f4e7b..ae928766 100644 --- a/deepin-devicemanager/src/Tool/ThreadExecXrandr.cpp +++ b/deepin-devicemanager/src/Tool/ThreadExecXrandr.cpp @@ -49,13 +49,23 @@ ThreadExecXrandr::ThreadExecXrandr(bool gpu, bool isDXcbPlatform) void ThreadExecXrandr::run() { if (m_Gpu) { - getGpuInfoFromXrandr(); - } else { - if(Common::boardVendorType() == "PGUV") { - QList> lstMap; - getResolutionRateFromDBus(lstMap); - }else + // X11/dxcb 环境通过 xrandr 补充显卡分辨率信息;Wayland/无 X 环境不执行 xrandr 子进程 + if (m_isDXcbPlatform) { + getGpuInfoFromXrandr(); + } + } else { + // Wayland/无 X 环境或 PGUV 机型走 DBus,避免 xrandr 子进程阻塞 UI 线程 + if (!m_isDXcbPlatform || Common::boardVendorType() == "PGUV") { + QList> lstMap; + getResolutionRateFromDBus(lstMap); + } else { getMonitorInfoFromXrandrVerbose(); + } + } + // 显示器数量用于热插拔检测,纯 DBus 调用不启动子进程 + if (m_monitorLst.isEmpty()) { + QMap tmp; + getResolutionFromDBus(tmp); } } @@ -63,7 +73,11 @@ void ThreadExecXrandr::runCmd(QString &info, const QString &cmd) { QProcess process; process.start(cmd); - process.waitForFinished(-1); + // 设置超时,避免 xrandr 在部分硬件/驱动或无 X 环境下挂起导致界面长时间无响应 + if (!process.waitForFinished(5000)) { + process.kill(); + process.waitForFinished(1000); + } info = process.readAllStandardOutput(); } From 0449c2e0d57ef5832df91952d1d70b249cbcf953 Mon Sep 17 00:00:00 2001 From: gongheng Date: Mon, 17 Aug 2026 20:10:41 +0800 Subject: [PATCH 2/2] fix(device): wait for async xrandr thread before deletion in destructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review M01 on PR #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 崩溃风险 --- deepin-devicemanager/src/Page/MainWindow.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/deepin-devicemanager/src/Page/MainWindow.cpp b/deepin-devicemanager/src/Page/MainWindow.cpp index b337e971..a2848b6d 100644 --- a/deepin-devicemanager/src/Page/MainWindow.cpp +++ b/deepin-devicemanager/src/Page/MainWindow.cpp @@ -140,10 +140,13 @@ void MainWindow::refreshDataBaseLater() MainWindow::~MainWindow() { // 释放指针 - // 释放异步 xrandr 线程(runCmd 已设超时,wait 不会长时间阻塞) + // 释放异步 xrandr 线程:先断开 finished 信号避免析构中途触发槽; + // wait() 无超时等待,因 runCmd 每子进程已限 5s+1s,双 runCmd 最长约 12s 内结束, + // 保证 run() 退出后再释放对象,避免 use-after-free if (mp_XrandrThread) { + mp_XrandrThread->disconnect(this); // 避免 finished 槽在析构中途触发 if (mp_XrandrThread->isRunning()) - mp_XrandrThread->wait(6000); + mp_XrandrThread->wait(); // runCmd 每子进程已限 5s+1s,最长 ~12s 内结束 delete mp_XrandrThread; mp_XrandrThread = nullptr; } @@ -592,6 +595,8 @@ void MainWindow::slotListItemClicked(const QString &itemStr) void MainWindow::startAsyncXrandr(const QString &itemStr) { + // 兜底:正常路径下此分支不可达(上层 isRunning() 短路、完成槽已置空), + // 仅防御极端时序下残留的线程对象 if (mp_XrandrThread) { mp_XrandrThread->deleteLater(); mp_XrandrThread = nullptr;