Skip to content

fix(deviceinfo): reduce CPU contention on USB hotplug to avoid audio stutter - #971

Merged
deepin-bot[bot] merged 2 commits into
linuxdeepin:develop/eaglefrom
GongHeng2017:fix/381051-usb-hotplug-audio-stutter
Nov 11, 2026
Merged

deepin-bot[bot] merged 2 commits into
linuxdeepin:develop/eaglefrom
GongHeng2017:fix/381051-usb-hotplug-audio-stutter

Conversation

@GongHeng2017

@GongHeng2017 GongHeng2017 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

背景

PMS 381051:播放音频时插拔 U 盘,音频卡顿数秒。
https://pms.uniontech.com/bug-view-381051.html

根因

USB 热插拔时 org.deepin.DeviceInfo 插件会反复 spawn hwinfo/lshw 等重量级命令
并把 CPU 占满数百毫秒到数秒,正在播放的 deepin-music 音频缓冲 (92.88ms) 撑不过去
导致 XRUN、卡顿数秒。本仓库侧需减少热插拔时的 CPU 争用(deepin-music 侧另提 PR
移除 SDL 音频回调中的同步 DBus 调用)。

修改内容(按负责人验证通过的补丁,未自行缩减)

  • detectthread:用纯 sysfs 就绪探测 waitKernelUsbReady() 替代反复执行
    hwinfo --usb --keyboard --mouse 的忙等轮询,不再 spawn 子进程;构造函数中
    删除初始化期的 curHwinfoUsbInfo 调用
  • monitorusb:对一批 udev 事件去抖合并,平息 500ms 后只执行一次
    hwinfo --usb(新增 applyUsbControl());select 超时由 1s 改 200ms 以便
    及时触发合并
  • threadpool:新增 updateHotplugInfo() 只刷新与插拔相关的少量命令
    (lsblk/lssg/hciconfig/hwinfo);loadDeviceInfo/updateDeviceInfo 的忙等自旋
    改为 waitForDone(10000)(条件变量等待,不占 CPU)
  • deviceinfomanager:getInfo/isPathExisted 改用 constFind,避免
    operator[] 误插入空条目导致 isInfoExisted 误判、canNotReplace 命令永不重试;
    getInfo 返回值改为 const QString(不再返回引用)
  • threadpooltask:删除未使用的 runCmd(cmd)/runCmdToFile/loadSmartctlInfoToFile/
    loadLspciVSInfoToFile 等死代码;命令输出为空时跳过缓存,避免污染 isInfoExisted
  • mainjob:热插拔走局部刷新 (HOTPLUG),客户端主动刷新/驱动操作走全量刷新
    (slotRefreshRequest);去掉 executeClientInstruction 中重复的
    thread()->msleep(1000)

自测

  • 静态检查:补丁在本基线 develop/eagle @ 670ebb86 上 git apply --check 通过,
    应用后 git diff 与补丁逐字节一致
  • 编译可行性:删除的死代码均无其它引用;updateHotplugInfo/applyUsbControl/
    waitKernelUsbReady/slotRefreshRequest 在头文件中均有声明
  • 行为验证(负责人已在实际硬件上验证通过,本 PR 按其提供的补丁提交)

关联

Bug: https://pms.uniontech.com/bug-view-381051.html

Summary by Sourcery

Reduce device-information processing overhead during USB hotplug events to avoid CPU contention and audio stutter.

Bug Fixes:

  • Reduce CPU contention during USB hotplug events to prevent audio playback stutter.
  • Prevent missing or empty device information from being incorrectly treated as cached data, allowing failed commands to be retried.

Enhancements:

  • Use low-overhead sysfs readiness checks and debounce udev events before collecting USB information.
  • Refresh only hotplug-relevant device data for USB events while retaining full refreshes for explicit client requests and driver operations.
  • Replace thread-pool busy waiting with condition-based waits and remove obsolete command execution paths.

…stutter

USB 热插拔时 org.deepin.DeviceInfo 插件会反复 spawn hwinfo/lshw 等重量级
命令并把 CPU 占满数百毫秒到数秒,正在播放的 deepin-mudio 音频缓冲
(92.88ms) 撑不过去导致 XRUN、卡顿数秒。按负责人验证通过的补丁修改:

- detectthread: 用纯 sysfs 就绪探测 (waitKernelUsbReady) 替代反复执行
  `hwinfo --usb --keyboard --mouse` 的忙等轮询,不再 spawn 子进程
- monitorusb: 对一批 udev 事件去抖合并,平息 500ms 后只执行一次
  hwinfo --usb;select 超时改 200ms 以便及时触发合并
- threadpool: 新增 updateHotplugInfo() 只刷新与插拔相关的少量命令;
  loadDeviceInfo/updateDeviceInfo 的忙等自旋改为 waitForDone(10000)
- deviceinfomanager: getInfo/isPathExisted 改用 constFind,避免
  operator[] 误插入空条目导致 isInfoExisted 误判、canNotReplace 命令
  永不重试
- threadpooltask: 删除未使用的 runCmd(cmd)/runCmdToFile 等死代码;
  命令输出为空时跳过缓存,避免污染 isInfoExisted
- mainjob: 热插拔走局部刷新 (HOTPLUG),客户端主动刷新/驱动操作走全量
  刷新 (slotRefreshRequest);去掉 executeClientInstruction 中重复的
  thread()->msleep(1000)

Bug: https://pms.uniontech.com/bug-view-381051.html
@deepin-ci-robot

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@github-actions

Copy link
Copy Markdown
  • 检测到敏感词lshw变动
详情
    {
    "lshw": {
        "deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp": [
            "    // \u4e00\u6b21\u63d2\u62d4\u53ea\u9700\u8981\u66f4\u65b0\"\u4f1a\u53d8\"\u7684\u4fe1\u606f\u3002lshw / upower / lscpu / lspci /"
        ],
        "deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.h": [
            "     * USB \u63d2\u62d4\u5f88\u9891\u7e41\uff0c\u6ca1\u6709\u5fc5\u8981\u6bcf\u6b21\u90fd\u628a\u6574\u673a\u4fe1\u606f\u91cd\u65b0\u91c7\u96c6\u4e00\u904d\uff1alshw\u3001upower\u3001"
        ],
        "deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp": [
            "    // USB \u70ed\u63d2\u62d4\u8d70\u5c40\u90e8\u5237\u65b0\uff0c\u7701\u6389\u4e0e\u63d2\u62d4\u65e0\u5173\u7684\u547d\u4ee4\uff08lshw / upower / lscpu /"
        ]
    }
}

@sourcery-ai

sourcery-ai Bot commented Nov 11, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

API

Change DeviceInfoManager lookups to avoid inserting missing cache entries and return copied values so callers cannot retain references beyond the mutex lock.

  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/deviceinfomanager.cpp L30-39, L51-55 (+2 small edits)
  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/deviceinfomanager.h L1, L46-48

Add the ThreadPool hotplug-refresh entry point and its command-list storage so callers can request a scoped update.

  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.h L1, L45-60, L70

Uses of the API

Route USB hotplug events to the scoped refresh while keeping explicit client refreshes and driver operations on the full-refresh path.

  • deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp L43-45, L109-117, L145-155
  • deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.h L39-54

Implementation

Replace repeated hwinfo-based USB readiness checks with a bounded, sleeping sysfs probe and remove the old cached hwinfo comparison machinery.

  • deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/detectthread.cpp L15-19, L45-87 (+5 small edits)
  • deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/detectthread.h L46-59 (+3 small edits)

Debounce batches of udev add/remove events and collect USB information once per settled batch before applying control and wakeup updates.

  • deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.cpp L14-26, L54-117 (+2 small edits)
  • deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/monitorusb.h L1, L42-51, L60-63

Replace thread-pool busy-wait loops with waitForDone and implement the scoped hotplug command list while retaining full-update command lists.

  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp L30-43, L48-52, L221-233 (+3 small edits)

Remove unused command/file helpers and avoid caching empty command output so failed or unsupported commands can be retried.

  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp L141-160 (+5 small edits)
  • deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.h L1-91 (4 small edits)

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Update SPDX-FileCopyrightText year from 2023 to 2026 in the files
modified by this branch, per review residual risk feedback.

Bug: https://pms.uniontech.com/bug-view-381051.html
@github-actions

Copy link
Copy Markdown
  • 检测到敏感词lshw变动
详情
    {
    "lshw": {
        "deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp": [
            "    // \u4e00\u6b21\u63d2\u62d4\u53ea\u9700\u8981\u66f4\u65b0\"\u4f1a\u53d8\"\u7684\u4fe1\u606f\u3002lshw / upower / lscpu / lspci /"
        ],
        "deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.h": [
            "     * USB \u63d2\u62d4\u5f88\u9891\u7e41\uff0c\u6ca1\u6709\u5fc5\u8981\u6bcf\u6b21\u90fd\u628a\u6574\u673a\u4fe1\u606f\u91cd\u65b0\u91c7\u96c6\u4e00\u904d\uff1alshw\u3001upower\u3001"
        ],
        "deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp": [
            "    // USB \u70ed\u63d2\u62d4\u8d70\u5c40\u90e8\u5237\u65b0\uff0c\u7701\u6389\u4e0e\u63d2\u62d4\u65e0\u5173\u7684\u547d\u4ee4\uff08lshw / upower / lscpu /"
        ]
    }
}

@GongHeng2017
GongHeng2017 marked this pull request as ready for review November 11, 2026 05:08
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: GongHeng2017, max-lvs, wyu71

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 7 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp" line_range="156" />
<code_context>
+    // 命令执行失败(如权限不足、工具缺失、硬件不支持)时输出为空。
+    // 此时不写入缓存,否则 isInfoExisted() 会认为该信息已经采集过,
+    // 对于 canNotReplace 的命令来说就永远不会再重试了。
+    if (info.isEmpty()) {
+        qCWarning(appLog) << "command produced empty output, skip caching:" << cmd;
+        return;
</code_context>
<issue_to_address>
**Removed devices remain visible**

When a successful refresh returns an empty result after a previous result contained devices, `runCmdToCache()` returns before updating `DeviceInfoManager`, so the previous nonempty value remains cached and clients continue seeing devices that are no longer present, including Bluetooth adapters and `/dev/sg*` devices.

Distinguish successful empty results from command failures and replace the cached value with an empty result when a successful refresh finds no devices.

Also at `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp:157-159`.
</issue_to_address>

### Comment 2
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp" line_range="52" />
<code_context>
+    // 原实现是 while (true) + activeThreadCount() 的忙等自旋,不 sleep,
+    // 会白占一个 CPU 核心最多 10 秒;音频等实时线程正好被它挤掉时间片。
+    // QThreadPool::waitForDone() 内部用条件变量等待,不消耗 CPU。
+    waitForDone(10000);
 }

</code_context>
<issue_to_address>
**Overlapping refreshes overwrite newer state**

When a refresh task takes longer than 10 seconds and another refresh starts before it finishes, `waitForDone(10000)` returns while tasks are still running, and `updateInfo()` lets the caller proceed without waiting for them; later refreshes can then write the same cache keys, and an older task that finishes last overwrites newer device state or leaves callers seeing a partially refreshed cache.

Keep refreshes from proceeding or starting another batch until all tasks from the current batch have finished.
</issue_to_address>

### Comment 3
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp" line_range="112" />
<code_context>
+    // USB 热插拔走局部刷新,省掉与插拔无关的命令(lshw / upower / lscpu /
+    // lspci / lpstat / bluetoothctl / hwinfo --framebuffer)。
+    // 客户端主动刷新与驱动操作完成走 slotRefreshRequest(),那里是全量刷新。
+    executeClientInstruction("HOTPLUG");
+}
+
</code_context>
<issue_to_address>
**Bluetooth pairing lists stay stale**

When a Bluetooth link event reports a pairing or removal change, `slotUsbChanged()` routes the event through `HOTPLUG`, but `updateHotplugInfo()` does not refresh `bt_device`; clients therefore continue seeing the previous paired-device list until a full refresh.

Include the paired-device task in the hotplug refresh.

Also at `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp:230-234`, `deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp:154`.
</issue_to_address>

### Comment 4
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/detectthread.cpp" line_range="51" />
<code_context>
-        end = QDateTime::currentMSecsSinceEpoch();
-    }
-    qCInfo(appLog) << " 此次判断插拔是否完成的时间为 ************ " << QDateTime::currentMSecsSinceEpoch() - begin;
+    const bool ready = waitKernelUsbReady(kUsbSettleMaxMs);
+    qCInfo(appLog) << " usb settle time:" << QDateTime::currentMSecsSinceEpoch() - begin
+                   << " ready:" << ready;
</code_context>
<issue_to_address>
**Incomplete USB devices get refreshed**

When a USB device is still being enumerated or has an interface without a driver after the 1-second wait, `slotUsbChanged` emits `usbChanged` even when `waitKernelUsbReady` returns false, so `MainJob` refreshes device caches while enumeration or driver probing is incomplete; the new device can be omitted until another refresh.

When readiness times out, defer or retry the cache refresh instead of emitting `usbChanged` immediately.
</issue_to_address>

### Comment 5
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp" line_range="148" />
<code_context>
+        // 原实现在这里用 thread()->msleep(1000) 硬睡主线程。DetectThread 已经
+        // 轮询确认内核处理完插拔、MonitorUsb 也已等待 1s 去抖,这 1 秒纯属重复,
+        // 而且会阻塞 DeviceInterface 的 D-Bus 方法派发,直接去掉。
         updateAllDevice();
+    } else if (instructions.startsWith("HOTPLUG")) {
+        if (m_firstUpdate) {
</code_context>
<issue_to_address>
**Immediate refresh reads unsettled devices**

When a client calls `refreshInfo()` immediately after a device change, before the kernel has finished enumerating the device, `refreshInfo()` emits `sigUpdate`, which reaches `slotRefreshRequest()` and runs `updateAllDevice()` immediately; when the caller requests a refresh just after a device change, discovery commands run before the kernel finishes enumerating it, so the returned device information is stale until another refresh.

Keep a readiness wait for explicit refresh requests that can race device enumeration, or make those requests wait for the hotplug readiness path before collecting device data.
</issue_to_address>

### Comment 6
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp" line_range="230-233" />
<code_context>
+    // 毫秒到数秒,并发执行时会把 CPU 占满,正在播放的音频因此丢帧(滋滋声)。
+    // 未列入的命令不执行,其缓存保持上一次的值,不会丢失数据。
+    // ---------------------------------------------------------------------
+    m_ListHotplug.append(cmdLsblk);      // 新增/移除磁盘
+    m_ListHotplug.append(cmdLssg);       // 新增/移除 SCSI 通用设备
+    m_ListHotplug.append(cmdHciconfig);  // 蓝牙适配器状态
+    m_ListHotplug.append(cmdHwinfo);     // 含 --sound:声卡拓扑也可能变化
 }
</code_context>
<issue_to_address>
**New USB disks lack details**

When a USB disk is plugged in after the initial full refresh, `updateHotplugInfo` refreshes `lsblk` but leaves the `lshw` cache unchanged, so `DeviceGenerator::getDiskInfoFromLshw` cannot match newly attached disks to current `lshw_disk` data and their displayed storage metadata remains incomplete.

Refresh the relevant `lshw` storage data during hotplug, or avoid using its stale cache to enrich the refreshed storage list.
</issue_to_address>

### Comment 7
<location path="deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp" line_range="217" />
<code_context>
     Cmd cmdHwinfoMonitor;     //hwinfo --framebuffer --monitor
-    cmdHwinfoMonitor.cmd = QString("%1 %2%3").arg("hwinfo --framebuffer --monitor > ").arg(PATH).arg("hwinfo_monitor.txt");
+    cmdHwinfoMonitor.cmd = QString("%1 %2%3").arg("hwinfo --monitor > ").arg(PATH).arg("hwinfo_monitor.txt");
     cmdHwinfoMonitor.file = "hwinfo_monitor.txt";
     cmdHwinfoMonitor.canNotReplace = false;
     m_ListCmd.append(cmdHwinfoMonitor);
</code_context>
<issue_to_address>
**Full refresh omits framebuffer data**

When a client requests a full refresh or startup collection runs, the full-refresh command now runs only `hwinfo --monitor`, so framebuffer information previously collected by `hwinfo --framebuffer --monitor` is absent from `hwinfo_monitor.txt`; callers performing a full refresh no longer receive that data.

Keep `--framebuffer` in the full-refresh command; omit it only from hotplug refreshes if it is unnecessary there.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

// 命令执行失败(如权限不足、工具缺失、硬件不支持)时输出为空。
// 此时不写入缓存,否则 isInfoExisted() 会认为该信息已经采集过,
// 对于 canNotReplace 的命令来说就永远不会再重试了。
if (info.isEmpty()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Removed devices remain visible

When a successful refresh returns an empty result after a previous result contained devices, runCmdToCache() returns before updating DeviceInfoManager, so the previous nonempty value remains cached and clients continue seeing devices that are no longer present, including Bluetooth adapters and /dev/sg* devices.

Distinguish successful empty results from command failures and replace the cached value with an empty result when a successful refresh finds no devices.

Also at deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp:157-159.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp` at line 156:

**Removed devices remain visible**

When a successful refresh returns an empty result after a previous result contained devices, `runCmdToCache()` returns before updating `DeviceInfoManager`, so the previous nonempty value remains cached and clients continue seeing devices that are no longer present, including Bluetooth adapters and `/dev/sg*` devices.

Distinguish successful empty results from command failures and replace the cached value with an empty result when a successful refresh finds no devices.

Also at `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpooltask.cpp:157-159`.

// 原实现是 while (true) + activeThreadCount() 的忙等自旋,不 sleep,
// 会白占一个 CPU 核心最多 10 秒;音频等实时线程正好被它挤掉时间片。
// QThreadPool::waitForDone() 内部用条件变量等待,不消耗 CPU。
waitForDone(10000);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Overlapping refreshes overwrite newer state

When a refresh task takes longer than 10 seconds and another refresh starts before it finishes, waitForDone(10000) returns while tasks are still running, and updateInfo() lets the caller proceed without waiting for them; later refreshes can then write the same cache keys, and an older task that finishes last overwrites newer device state or leaves callers seeing a partially refreshed cache.

Keep refreshes from proceeding or starting another batch until all tasks from the current batch have finished.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp` at line 52:

**Overlapping refreshes overwrite newer state**

When a refresh task takes longer than 10 seconds and another refresh starts before it finishes, `waitForDone(10000)` returns while tasks are still running, and `updateInfo()` lets the caller proceed without waiting for them; later refreshes can then write the same cache keys, and an older task that finishes last overwrites newer device state or leaves callers seeing a partially refreshed cache.

Keep refreshes from proceeding or starting another batch until all tasks from the current batch have finished.

// USB 热插拔走局部刷新,省掉与插拔无关的命令(lshw / upower / lscpu /
// lspci / lpstat / bluetoothctl / hwinfo --framebuffer)。
// 客户端主动刷新与驱动操作完成走 slotRefreshRequest(),那里是全量刷新。
executeClientInstruction("HOTPLUG");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Bluetooth pairing lists stay stale

When a Bluetooth link event reports a pairing or removal change, slotUsbChanged() routes the event through HOTPLUG, but updateHotplugInfo() does not refresh bt_device; clients therefore continue seeing the previous paired-device list until a full refresh.

Include the paired-device task in the hotplug refresh.

Also at deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp:230-234, deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp:154.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp` at line 112:

**Bluetooth pairing lists stay stale**

When a Bluetooth link event reports a pairing or removal change, `slotUsbChanged()` routes the event through `HOTPLUG`, but `updateHotplugInfo()` does not refresh `bt_device`; clients therefore continue seeing the previous paired-device list until a full refresh.

Include the paired-device task in the hotplug refresh.

Also at `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp:230-234`, `deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp:154`.

end = QDateTime::currentMSecsSinceEpoch();
}
qCInfo(appLog) << " 此次判断插拔是否完成的时间为 ************ " << QDateTime::currentMSecsSinceEpoch() - begin;
const bool ready = waitKernelUsbReady(kUsbSettleMaxMs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Incomplete USB devices get refreshed

When a USB device is still being enumerated or has an interface without a driver after the 1-second wait, slotUsbChanged emits usbChanged even when waitKernelUsbReady returns false, so MainJob refreshes device caches while enumeration or driver probing is incomplete; the new device can be omitted until another refresh.

When readiness times out, defer or retry the cache refresh instead of emitting usbChanged immediately.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/hotplug/detectthread.cpp` at line 51:

**Incomplete USB devices get refreshed**

When a USB device is still being enumerated or has an interface without a driver after the 1-second wait, `slotUsbChanged` emits `usbChanged` even when `waitKernelUsbReady` returns false, so `MainJob` refreshes device caches while enumeration or driver probing is incomplete; the new device can be omitted until another refresh.

When readiness times out, defer or retry the cache refresh instead of emitting `usbChanged` immediately.

// 原实现在这里用 thread()->msleep(1000) 硬睡主线程。DetectThread 已经
// 轮询确认内核处理完插拔、MonitorUsb 也已等待 1s 去抖,这 1 秒纯属重复,
// 而且会阻塞 DeviceInterface 的 D-Bus 方法派发,直接去掉。
updateAllDevice();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Immediate refresh reads unsettled devices

When a client calls refreshInfo() immediately after a device change, before the kernel has finished enumerating the device, refreshInfo() emits sigUpdate, which reaches slotRefreshRequest() and runs updateAllDevice() immediately; when the caller requests a refresh just after a device change, discovery commands run before the kernel finishes enumerating it, so the returned device information is stale until another refresh.

Keep a readiness wait for explicit refresh requests that can race device enumeration, or make those requests wait for the hotplug readiness path before collecting device data.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/mainjob.cpp` at line 148:

**Immediate refresh reads unsettled devices**

When a client calls `refreshInfo()` immediately after a device change, before the kernel has finished enumerating the device, `refreshInfo()` emits `sigUpdate`, which reaches `slotRefreshRequest()` and runs `updateAllDevice()` immediately; when the caller requests a refresh just after a device change, discovery commands run before the kernel finishes enumerating it, so the returned device information is stale until another refresh.

Keep a readiness wait for explicit refresh requests that can race device enumeration, or make those requests wait for the hotplug readiness path before collecting device data.

Comment on lines +230 to +233
m_ListHotplug.append(cmdLsblk); // 新增/移除磁盘
m_ListHotplug.append(cmdLssg); // 新增/移除 SCSI 通用设备
m_ListHotplug.append(cmdHciconfig); // 蓝牙适配器状态
m_ListHotplug.append(cmdHwinfo); // 含 --sound:声卡拓扑也可能变化

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · New USB disks lack details

When a USB disk is plugged in after the initial full refresh, updateHotplugInfo refreshes lsblk but leaves the lshw cache unchanged, so DeviceGenerator::getDiskInfoFromLshw cannot match newly attached disks to current lshw_disk data and their displayed storage metadata remains incomplete.

Refresh the relevant lshw storage data during hotplug, or avoid using its stale cache to enrich the refreshed storage list.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp` at lines 230-233:

**New USB disks lack details**

When a USB disk is plugged in after the initial full refresh, `updateHotplugInfo` refreshes `lsblk` but leaves the `lshw` cache unchanged, so `DeviceGenerator::getDiskInfoFromLshw` cannot match newly attached disks to current `lshw_disk` data and their displayed storage metadata remains incomplete.

Refresh the relevant `lshw` storage data during hotplug, or avoid using its stale cache to enrich the refreshed storage list.

Cmd cmdHwinfoMonitor; //hwinfo --framebuffer --monitor
cmdHwinfoMonitor.cmd = QString("%1 %2%3").arg("hwinfo --framebuffer --monitor > ").arg(PATH).arg("hwinfo_monitor.txt");
cmdHwinfoMonitor.cmd = QString("%1 %2%3").arg("hwinfo --monitor > ").arg(PATH).arg("hwinfo_monitor.txt");
cmdHwinfoMonitor.file = "hwinfo_monitor.txt";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Full refresh omits framebuffer data

When a client requests a full refresh or startup collection runs, the full-refresh command now runs only hwinfo --monitor, so framebuffer information previously collected by hwinfo --framebuffer --monitor is absent from hwinfo_monitor.txt; callers performing a full refresh no longer receive that data.

Keep --framebuffer in the full-refresh command; omit it only from hotplug refreshes if it is unnecessary there.

Prompt for AI agents
In `deepin-devicemanager-server/deepin-deviceinfo/src/loadinfo/threadpool.cpp` at line 217:

**Full refresh omits framebuffer data**

When a client requests a full refresh or startup collection runs, the full-refresh command now runs only `hwinfo --monitor`, so framebuffer information previously collected by `hwinfo --framebuffer --monitor` is absent from `hwinfo_monitor.txt`; callers performing a full refresh no longer receive that data.

Keep `--framebuffer` in the full-refresh command; omit it only from hotplug refreshes if it is unnecessary there.

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 89 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 本 PR 旨在降低 USB 热插拔时的 CPU 争用以避免音频丢帧,改动方向正确且效果显著: 将 hwinfo 进程轮询替换为纯 sysfs 探测、添加 udev 事件去抖、以 waitForDone 替代忙等自旋、 引入热插拔局部刷新路径。四维度均通过,无安全漏洞。存在少量代码质量与性能优化空间 (注释陈旧、const 返回值冗余、hwinfo 命令范围可进一步收窄等),不影响整体通过。

🔍 详细分析

1. 语法逻辑 ✅

评价: 语法正确,逻辑清晰 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ["waitKernelUsbReady() 中对含 ':' 的 sysfs 条目,可仅将缺失 /driver 视为非致命条件(continue 而非 break),或仅检查 usb_device 的 idVendor 是否就绪,避免对无驱动接口的误判", '考虑将 waitKernelUsbReady() 的等待逻辑移至 DetectThread::run()(工作线程)中执行,避免阻塞主线程事件循环']


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ["更新 threadpool.cpp 第215行注释为 '//hwinfo --monitor',与实际命令一致", "将 getInfo() 返回类型从 'const QString' 改为 'QString'", '在 applyUsbControl() 中增加 hwinfo 空输出校验,与 threadpooltask.cpp 的处理方式保持一致']


3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ["为热插拔场景创建更窄的 hwinfo 命令,如 'hwinfo --usb --sound --disk --keyboard --mouse',排除与插拔无关的 --netcard --cdrom --display --fingerprint", '将 applyUsbControl() 中的 waitForFinished(-1) 改为 waitForFinished(10000) 并在超时时 kill 进程', '在 executeClientInstruction() 的 HOTPLUG 分支中,updateHotplugInfo() 之后增加 m_pool->waitForDone(30000) 作为安全网']


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: ['无安全相关建议,当前实现安全合规']


💡 改进建议代码示例

// === 1. detectthread.cpp: waitKernelUsbReady() 修复无驱动接口误判 ===
bool DetectThread::waitKernelUsbReady(int maxWaitMs)
{
    const qint64 begin = QDateTime::currentMSecsSinceEpoch();
    while (true) {
        bool ready = true;

        QDir dir("/sys/bus/usb/devices");
        const QFileInfoList entries = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name);
        for (const QFileInfo &entry : entries) {
            const QString path = entry.absoluteFilePath();
            if (entry.fileName().contains(':')) {
                // usb_interface:某些接口(vendor-specific、libusb 等)可能永远不会
                // 绑定内核驱动,不将其视为未就绪,仅依赖 usb_device 的 idVendor 做就绪判断
                continue;
            } else if (QFileInfo::exists(path + "/idVendor")) {
                // usb_device:已枚举
                continue;
            } else {
                ready = false;
                break;
            }
        }

        if (ready)
            return true;
        if (QDateTime::currentMSecsSinceEpoch() - begin >= maxWaitMs)
            return false;

        msleep(kUsbSettleStepMs);
    }
}

// === 2. monitorusb.cpp: applyUsbControl() 增加超时与空输出校验 ===
void MonitorUsb::applyUsbControl()
{
    const bool needControl = m_NeedControl;
    const bool needWakeup = m_NeedWakeup;
    m_NeedControl = false;
    m_NeedWakeup = false;

    QProcess process;
    process.start("hwinfo --usb");
    if (!process.waitForFinished(10000)) {
        qCWarning(appLog) << "hwinfo --usb timed out, skipping control update";
        process.kill();
        process.waitForFinished(3000);
        return;
    }
    const QString info = process.readAllStandardOutput();
    if (info.isEmpty()) {
        qCWarning(appLog) << "hwinfo --usb produced empty output, skipping control update";
        return;
    }

    if (needControl)
        ControlInterface::getInstance()->disableOutDevice(info);
    if (needWakeup)
        ControlInterface::getInstance()->updateWakeup(info);
}

// === 3. deviceinfomanager.h: 移除 const 返回修饰 ===
QString getInfo(const QString &key);

// === 4. threadpool.cpp: 更新陈旧注释 ===
Cmd cmdHwinfoMonitor;     //hwinfo --monitor

本报告由 AI 代码审查工具自动生成

@GongHeng2017

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

This pr force merged! (status: unstable)

@deepin-bot
deepin-bot Bot merged commit 1b2eb8e into linuxdeepin:develop/eagle Nov 11, 2026
15 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants