Skip to content

fix: prevent X11 input shape event storm in application tray - #521

Open
wineee wants to merge 1 commit into
linuxdeepin:masterfrom
wineee:fix
Open

wineee wants to merge 1 commit into
linuxdeepin:masterfrom
wineee:fix

Conversation

@wineee

@wineee wineee commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

X11 Input Shape 事件风暴导致 CPU 占用过高技术报告

摘要

在 Treeland(Wayland 合成器)环境下,打开 wine 版企业微信后出现 Xwayland CPU 占用飙升至 98% 的问题。经过系统化排查,定位根因为 dde-tray-loader 的 application-tray 插件在处理 X11 LEAVE_NOTIFY 事件时缺少窗口归属过滤与幂等保护,任何 X11 客户端(wine 企业微信、nm-applet 等)的离开事件都会触发一次 setX11WindowInputShape,而该调用又会引发 Xwayland 的 enter/leave 重算并广播新事件,形成每秒十几万次的事件风暴死循环。

问题现象

进程 CPU 占用 角色
Xwayland :1 ~93-98% 被动处理 shape 请求并广播事件
wineserver ~40% 转发 wine 的 X11 请求(首次触发场景)
explorer.exe /desktop ~47% wine 桌面 shell(首次触发场景)
trayplugin-load ~40-75% 事件风暴源头(元凶)
nm-applet ~93% 事件受害者 + 独立内存泄漏问题

排查过程

1. 定位高 CPU 进程

ps aux | grep -iE 'xwayland|wine|trayplugin'

发现多个进程异常高 CPU,其中 Xwayland 主线程占 99%。

2. Xwayland 主线程栈采样

通过 gdb 多次采样 Xwayland 主线程,反复命中同一调用链:

ProcShapeMask / ProcShapeRectangles    (Xext/shape.c)
  → miSetShape                         (mi/miwindow.c)
  → WindowsRestructured                (dix/events.c)
  → CheckMotion
  → DoEnterLeaveEvents
  → DeviceEnterLeaveEvent / CoreEnterLeaveEvent
  → DeviceEnterLeaveEvent 中 calloc   (分配 enter/leave 事件)

关键证据:每次 shape 请求都会触发 WindowsRestructured 对全窗口树做 enter/leave 事件重算,这是极昂贵的操作。

3. 反查 shape 请求的发送方

在 Xwayland 进程内通过 gdb 读取 client 结构体:

client 指针 → osPrivate (OsCommPtr) → socket fd 9 → inode 2397894

通过 ss 反查该 inode 的对端:

@/tmp/.X11-unix/X1 2397894 ↔ trayplugin-load (pid 63825, fd 18)

确认元凶是 trayplugin-load(dde-dock 托盘插件)。

4. 抓取请求内容

strace 抓 trayplugin-load 的写操作:

writev(18, [...]) = 138175 次 / 4 秒

解码 X11 请求字节流:

请求1: xcb_shape_rectangles(SO_SET, SK_INPUT, UNSORTED, window=0x400003, 矩形 w=0 h=0)
请求2: xcb_shape_mask(SO_SET, SK_INPUT, window=0x400003, XCB_PIXMAP_NONE)
请求3: xcb_configure_window(window=0x400003, STACK_MODE_BELOW)

与源码 Util::setX11WindowInputShape 逐字节对应。

5. 定位代码

grep -rn "setX11WindowInputShape" plugins/application-tray/

命中:

  • xembedprotocolhandler.cpp:72 — nativeEventFilter 处理 LEAVE_NOTIFY
  • util.cpp:270 — setX11WindowInputShape 实现

根因分析

死循环触发链

X11 客户端产生 LEAVE_NOTIFY 事件(wine 企业微信 / nm-applet / 任意 X11 应用)
  ↓
XembedProtocol::nativeEventFilter 捕获事件
  ↓ 无窗口归属判断,对所有窗口都响应
UTIL->setX11WindowInputShape(lE->event, QSize(0, 0))
  ↓ 同步连发 3 条 X11 请求
xcb_shape_rectangles + xcb_shape_mask + xcb_configure_window(BELOW)
  ↓ shape/堆叠顺序变化
Xwayland: miSetShape → WindowsRestructured → enter/leave 重算
  ↓ 向订阅 LEAVE_NOTIFY 的客户端广播新事件
新的 LEAVE_NOTIFY 到达 nativeEventFilter
  ↓
回到起点(无限循环)

三个缺陷

  1. 缺少窗口归属过滤:nativeEventFilter 对所有 X11 客户端的 LEAVE_NOTIFY 都响应,包括 wine 企业微信、nm-applet 等无关窗口。

  2. 缺少幂等保护:setX11WindowInputShape 每次调用都无条件发送 3 条 X11 请求,即使目标窗口的 input shape 已经是目标值。

  3. 缺少防重入机制:事件处理路径中没有任何去抖 / 防重入保护,导致事件风暴正反馈。

触发条件

  • 容器窗口注册了 XCB_EVENT_MASK_LEAVE_WINDOW | XCB_EVENT_MASK_ENTER_WINDOW 事件掩码
  • 任意 X11 客户端产生 LEAVE_NOTIFY 事件即可点燃风暴
  • 不止企业微信,nm-applet、其他 wine 应用等任何 X11 客户端都可能触发

补充发现

nm-applet 是独立问题:启动仅 14 分钟即内存泄漏至 20 GB(RSS),其 CPU 高占用与 shape 循环无关,是自身 GTK 事件循环空转 + 内存泄漏导致的。

修复方案

修改文件

文件 改动
util.h 新增 removeX11WindowInputShapeRecord() 声明、m_inputShapes 记录表、#include <QSize>
util.cpp setX11WindowInputShape 增加幂等保护;新增记录清理函数
xembedprotocolhandler.h 新增 ownsX11Window() 声明
xembedprotocolhandler.cpp nativeEventFilter 增加窗口归属过滤;析构时清理记录

三层防护

  1. 幂等保护(核心):相同窗口 + 相同 shape 时直接返回,斩断循环。
  2. 窗口归属过滤(治本):只处理自己 reparent 的托盘图标窗口和容器窗口。
  3. 资源清理(防泄漏):容器窗口销毁时清理记录,避免窗口 ID 复用误判。

验证结果

编译验证

cmake -B /tmp/tray-build -S . -DCMAKE_BUILD_TYPE=Debug
cmake --build /tmp/tray-build --target application-tray -j$(nproc)

application-tray 目标编译通过,无错误,产物生成 libapplication-tray.so。

运行时验证(待部署后执行)

验证项 方法 预期
CPU 恢复正常 打开 wine 企业微信,观察 Xwayland/trayplugin CPU 不再飙升
托盘功能正常 悬停/点击托盘图标 功能正常
其他客户端不再触发 触发 nm-applet 离开事件 无 Xwayland CPU 飙升

遗留风险与后续建议

  1. 部署验证未完成:补丁已提交但未部署到系统(/usr/lib/dde-dock/plugins/),需替换 .so 后重新登录验证。

  2. 更彻底的优化方向(超出本次修复范围):

    • setX11WindowInputShape 连发 3 条请求(rectangles + mask + configure)可考虑合并
    • configure_window(STACK_MODE_BELOW) 改变堆叠顺序是引发新事件的元凶之一,纯 input shape 场景可能不需要
  3. nm-applet 内存泄漏:这是独立问题,需要单独排查(可能是 GTK 版本或特定场景触发)。

  4. 监控建议:可考虑添加 Xwayland CPU 超阈值告警,快速发现类似事件风暴问题。

附录:关键命令

# 采样 Xwayland 主线程
sudo gdb -p <xwayland_pid> -batch -ex "thread 1" -ex "bt 20"

# 反查 client 对应的进程
sudo gdb -p <xwayland_pid> -batch -ex "x/8wx <osPrivate_addr>"
sudo ls -l /proc/<xwayland_pid>/fd/<fd>
ss -xp | grep <inode>

# 抓取客户端 X11 请求
sudo strace -f -p <client_pid> -e writev -s 100

# 解码 shape 请求
python3 -c "import struct; ..."

Summary by Sourcery

Prevent application-tray X11 leave-event feedback loops from driving excessive Xwayland CPU usage.

Bug Fixes:

  • Prevent X11 input-shape event storms by processing leave events only for tray windows managed by the application-tray plugin and skipping redundant shape updates.

Enhancements:

  • Invalidate tray-window ownership and clear cached shape state when embedded icons are destroyed or removed, preventing stale state from affecting recycled X11 window IDs.

@sourcery-ai

sourcery-ai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The application-tray X11 event path now avoids shape-update feedback loops by handling LEAVE_NOTIFY only for active, plugin-owned windows and skipping redundant input-shape requests. Cached shape state is explicitly invalidated when tray windows are destroyed or deregistered, preventing stale state from affecting recycled X11 IDs.

Sequence diagram for preventing the X11 input-shape event storm

sequenceDiagram
    participant X11 as X11EventSource
    participant Filter as XembedProtocol
    participant Handler as XembedProtocolHandler
    participant Util as Util
    participant Xwayland

    X11->>Filter: nativeEventFilter(LEAVE_NOTIFY)
    Filter->>Handler: ownsX11Window(eventWindow)
    alt unmanaged window
        Handler-->>Filter: false
        Filter-->>X11: return false
    else owned window
        Handler-->>Filter: true
        Filter->>Util: setX11WindowInputShape(eventWindow, QSize)
        alt shape unchanged
            Util-->>Filter: return early
        else shape changed
            Util->>Util: m_inputShapes.insert(window, size)
            Util->>Xwayland: xcb_shape_rectangles / xcb_shape_mask / xcb_configure_window
        end
    end
Loading

File-Level Changes

Change Details Files
Make input-shape updates idempotent to stop recursive X11 enter/leave event generation.
  • Cache the last shape per X11 window and skip duplicate updates.
  • Add explicit removal of cached shape records.
plugins/application-tray/util.cpp
plugins/application-tray/util.h
Restrict leave-event handling to active tray windows owned by the plugin.
  • Check each registered handler for ownership of the event window before changing its shape.
  • Track handler ownership and invalidate it when windows are destroyed or removed.
  • Continue propagating destroy notifications after synchronously invalidating matching handlers.
plugins/application-tray/xembedprotocolhandler.cpp
plugins/application-tray/xembedprotocolhandler.h
Prevent stale shape state from affecting recycled X11 window IDs.
  • Clear cached records during handler invalidation and destruction for both icon and container windows.
plugins/application-tray/xembedprotocolhandler.cpp

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

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

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

## Individual Comments

### Comment 1
<location path="plugins/application-tray/xembedprotocolhandler.cpp" line_range="184" />
<code_context>
 {
     if (m_containerWid) {
         xcb_destroy_window(Util::instance()->getX11Connection(), m_containerWid);
+        Util::instance()->removeX11WindowInputShapeRecord(m_containerWid);
     }
     UTIL->removeUniqueId(m_id);
</code_context>
<issue_to_address>
**issue (bug_risk):** The cleanup removes the cached shape only for `m_containerWid`, but `nativeEventFilter` also calls `setX11WindowInputShape` for the managed icon window `m_windowId`, so its cache entry survives handler destruction. If that X11 ID is later reused, the idempotency guard treats the new window as already having a 0x0 shape and skips the shape requests; `ownsX11Window` also treats the reused ID as managed.

**Triggers:** When an embedded tray icon is destroyed and its X11 window ID is reused before the handler is removed.

**Suggested fix:** Remove the shape record for `m_windowId` as well, and invalidate the handler's ownership state when the embedded icon is destroyed.
</issue_to_address>

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

Comment thread plugins/application-tray/xembedprotocolhandler.cpp

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Address the critical stale-ownership/XID-reuse issue and track stacking state separately from shape state.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Fixes X11 input-shape event storms in the application tray by filtering unmanaged windows and caching shape updates.

Changes:

  • Adds X11 window ownership checks.
  • Adds idempotent input-shape tracking.
  • Cleans cached state during tray lifecycle changes.
File Summary Findings
plugins/​application-tray/​xembedprotocolhandler.h Declares ownership and invalidation state. —
plugins/​application-tray/​xembedprotocolhandler.cpp Filters events and manages cleanup. Critical (2 votes): Ownership can remain stale during delayed cleanup, allowing XID reuse to affect unrelated windows.
plugins/​application-tray/​util.h Declares shape-cache storage and cleanup. —
plugins/​application-tray/​util.cpp Implements idempotent shape updates. Moderate (2 votes): Shape-only caching can skip required stacking-order updates.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread plugins/application-tray/xembedprotocolhandler.cpp
Comment thread plugins/application-tray/util.cpp
1. Filter LEAVE_NOTIFY to only windows managed by the handler
2. Add idempotency guard to setX11WindowInputShape
3. Clean up shape records when container window is destroyed

Log: Fix high CPU usage of Xwayland and tray plugin

Influence:
1. Open wine WeCom and hover tray icons to verify CPU stays normal
2. Trigger nm-applet leave events and confirm no Xwayland CPU spike
3. Verify tray icon hover/click still works after the fix

fix: 防止应用托盘的 X11 输入形状事件风暴

1. 只处理 handler 所管理窗口的 LEAVE_NOTIFY 事件
2. 为 setX11WindowInputShape 增加幂等保护
3. 容器窗口销毁时清理形状记录

Log: 修复 Xwayland 与托盘插件 CPU 占用过高的问题

Influence:
1. 打开 wine 企业微信并悬停托盘图标,确认 CPU 保持正常
2. 触发 nm-applet 离开事件,确认 Xwayland 无 CPU 飙升
3. 验证托盘图标悬停/点击功能仍然正常

PMS: BUG-378231
@wineee
wineee marked this pull request as ready for review September 23, 2026 03:25

@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 reviewed your changes and they look great!


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

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: BLumia, wineee

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

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

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

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过
评分详情 未发现安全漏洞,代码逻辑正确,注释完整,性能优化合理。本次变更有效修复了 X11 输入形状事件风暴导致的 CPU 占用过高问题。
分析模式 全量分析(GitHub PR)
修改文件数 4 个文件

🔍 详细分析

1. 语法逻辑 ✅

评分: 25/25 分 ✓ 通过

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

分析要点:

  1. setX11WindowInputShape 幂等保护:使用 m_inputShapes.find(window) 查找,检查 it != end() 后再解引用,正确处理空哈希表场景(util.cpp:279-287)
  2. ownsX11Window:return m_owned && (window == m_windowId || window == m_containerWid) 短路求值正确,m_owned 先于窗口 ID 检查(xembedprotocolhandler.cpp:235)
  3. invalidate():if (!m_owned) return 防重入保护,m_owned = false 先于清理操作执行(xembedprotocolhandler.cpp:240-243)
  4. nativeEventFilter DESTROY_NOTIFY:fall-through 设计正确,不消费事件(返回 false)让 FdoSelectionManager 继续处理(xembedprotocolhandler.cpp:108-110)
  5. 析构函数:调用 removeX11WindowInputShapeRecord 对不存在的 key 是安全的(QHash no-op)(xembedprotocolhandler.cpp:209-214)

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


2. 代码质量 ✅

评分: 25/25 分 ✓ 通过

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

分析要点:

  1. setX11WindowInputShape 幂等保护注释详细解释了事件风暴机制(miSetShape -> WindowsRestructured -> enter/leave recomputation)(util.cpp:272-278)
  2. LEAVE_NOTIFY 过滤注释说明了为什么需要过滤——之前所有客户端的 LEAVE_NOTIFY 都被处理,导致事件风暴无限循环(xembedprotocolhandler.cpp:73-77)
  3. DESTROY_NOTIFY 注释解释了 DBus 通知延迟(最多 200ms)导致的竞态条件和窗口 ID 回收风险(xembedprotocolhandler.cpp:94-98)
  4. ownsX11Window 和 invalidate() 方法声明注释清晰说明用途和设计意图(xembedprotocolhandler.h:55-64)
  5. m_inputShapes 成员注释包含跨引用(XembedProtocol::nativeEventFilter),便于维护者理解关联(util.h:89-92)
  6. invalidate() 方法正确封装了清理逻辑,在 onTrayIconsChanged、onRemoveItemByPid、DESTROY_NOTIFY 三处复用,无代码重复

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


3. 代码性能 ✅

评分: 20/20 分 ✓ 通过

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

分析要点:

  1. 幂等保护:O(1) 哈希查找替代 XCB shape round-trip 系统调用,从根本上消除事件风暴(每秒数十万事件 → 零冗余调用)
  2. LEAVE_NOTIFY 过滤:避免为非管理窗口触发 setX11WindowInputShape,减少不必要的 XCB 调用和事件重计算
  3. invalidate() 及时清理缓存记录,避免 m_inputShapes 无限增长导致内存浪费
  4. DESTROY_NOTIFY 同步处理避免 200ms DBus 延迟期间的窗口 ID 回收问题,防止陈旧缓存导致幂等保护误判
  5. 遍历 m_registedItem 复杂度 O(n),n 为托盘图标数量(通常 1-10),性能影响可忽略

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


4. 代码安全 🔒

评分: 30/30 分 ✓ 通过

🔐 存在 0 个安全漏洞

分析要点:

  1. 代码处理 X11 窗口 ID 和尺寸参数,均为系统内部值,无用户可控输入
  2. 无硬编码密钥或敏感信息
  3. 无缓冲区溢出风险(使用 Qt 容器 QHash,自带边界检查)
  4. reinterpret_cast<xcb_destroy_notify_event_t *>(ev) 在 XCB_EVENT_RESPONSE_TYPE(ev) 验证事件类型后使用,符合安全编程规范
  5. 窗口 ID 比较为直接整数比较,无注入风险

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个

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


📋 审查结论

本次提交旨在修复 X11 输入形状事件风暴导致的 Xwayland 和托盘插件 CPU 占用过高问题(PMS: BUG-378231)。代码通过三个核心机制实现修复:

  1. LEAVE_NOTIFY 过滤(xembedprotocolhandler.cpp:70-92):只处理 handler 所管理窗口的 LEAVE_NOTIFY 事件,避免无关客户端事件触发 shape 设置
  2. 幂等保护(util.cpp:272-288):为 setX11WindowInputShape 增加缓存检查,相同 shape 不重复设置,从根本上消除事件风暴
  3. 生命周期管理(xembedprotocolhandler.cpp:93-110, 238-251):增加 DESTROY_NOTIFY 同步处理和 invalidate() 方法,确保窗口销毁后及时清理缓存,防止窗口 ID 回收导致的误判

代码逻辑正确,注释详尽,性能优化有效,无安全风险。建议合入。


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

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