Skip to content

feat: async load plugin main QML via incubator queue - #3491

Open
caixr23 wants to merge 1 commit into
linuxdeepin:TASK-394433from
caixr23:TASK-394433
Open

caixr23 wants to merge 1 commit into
linuxdeepin:TASK-394433from
caixr23:TASK-394433

Conversation

@caixr23

@caixr23 caixr23 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
  1. Add DccAsyncModuleLoader to load plugin main QML asynchronously (QQmlComponent::Asynchronous + QQmlIncubator) via a serial queue so plugin pages no longer block the main thread after navigation is ready.
  2. Completion is driven by queued startNext() calls, safe against synchronous incubator callbacks; component statusChanged slots guard against stale callbacks after cancel().
  3. Ownership: components/incubators held by unique_ptr; QQmlContext uses the "non-null = owned, null = handed over" convention and is parented to the created object, fixing a per-plugin context leak.
  4. DccPluginLoader: extract setModule()/setMainObj() shared by sync and async paths for parenting and visibleToAppChanged wiring.
  5. DccPluginManager: gate async stages with ASYNC_MODULE/ASYNC_MAIN (module sync for fast navigation, main async); error paths mirror sync semantics (*Err|*End + finishPlugin) so load completion can never stall on a bad plugin QML.

Log: Control center opens with the window visible earlier; plugin pages load in the background without blocking the UI.

Influence:

  1. Launch control center: window shows, navigation list appears, then pages become clickable as they finish loading.
  2. Verify a plugin with a broken main QML still lets the control center finish loading all other plugins (no stuck "loading").

feat: 使用孵化器队列异步加载插件主 QML

  1. 新增 DccAsyncModuleLoader,通过串行队列以异步方式 (QQmlComponent::Asynchronous + QQmlIncubator)加载插件 main QML, 使导航就绪后插件页面不再阻塞主线程。
  2. 各阶段完成由队列化的 startNext() 驱动,可安全应对孵化器同步回调; component statusChanged 槽对 cancel() 之后的过期回调做了防护。
  3. 所有权规则:component/incubator 由 unique_ptr 持有;QQmlContext 采用 "非空=持有、空=已转让"约定,并挂接到所创建对象上,修复了 每插件泄漏一个 context 的问题。
  4. DccPluginLoader:提取 setModule()/setMainObj(),供同步/异步 两条路径共享挂父逻辑与 visibleToAppChanged 接线。
  5. DccPluginManager:以 ASYNC_MODULE/ASYNC_MAIN 宏门控各阶段 (module 同步以保证导航尽快就绪,main 异步);错误路径与同步语义 一致(*Err|*End + finishPlugin),坏插件 QML 不会卡死加载完成流程。

Log: 控制中心窗口更早可交互,插件页面后台加载不再阻塞界面。

Influence:

  1. 启动控制中心:窗口先显示、导航列表先就绪,各页面加载完成后可点击。
  2. 验证某个插件 main QML 损坏时,其余插件仍能正常加载完成(不会卡在加载中)。

PMS: TASK-394433

Summary by Sourcery

Keep the control center responsive by asynchronously loading plugin pages while preserving reliable completion and error handling.

New Features:

  • Load plugin main QML asynchronously through a serial incubator queue so the control center remains responsive while pages finish loading in the background.

Bug Fixes:

  • Ensure failed or missing plugin QML completes its load state without blocking other plugins.
  • Fix per-plugin QQmlContext leaks by transferring context ownership to successfully created main objects.

Enhancements:

  • Share module and main-object parenting and visibility wiring between synchronous and asynchronous loading paths.
  • Guard asynchronous completion handling against stale callbacks and synchronous incubator notifications.

@caixr23
caixr23 requested a review from 18202781743 September 10, 2026 08:06
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caixr23

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 commented Sep 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Introduces a serial QQmlComponent/QQmlIncubator pipeline for non-blocking plugin main-QML loading, with explicit ownership and cancellation handling, while sharing object-parenting logic with synchronous loading and preserving plugin-manager completion semantics on failures.

Sequence diagram for serial asynchronous plugin QML loading

sequenceDiagram
    participant Manager as DccPluginManager
    participant Queue as DccAsyncModuleLoader
    participant Component as QQmlComponent
    participant Incubator as QQmlIncubator
    participant Loader as DccPluginLoader
    participant Timer as DccLoadTimer

    Manager->>Queue: enqueue(loader, loadTimer)
    Queue->>Queue: startNext()
    Queue->>Component: loadUrl/loadFromModule(Asynchronous)
    Component-->>Queue: onModuleComponentStatus(Ready)
    Queue->>Incubator: create(Asynchronous)
    Incubator-->>Queue: onModuleIncubated(Ready)
    Queue->>Loader: setModule(module)
    Queue->>Queue: scheduleNext()
    Queue->>Component: loadUrl/loadFromModule(Asynchronous)
    Component-->>Queue: onMainComponentStatus(Ready)
    Queue->>Incubator: create(context)
    Incubator-->>Queue: onMainIncubated(Ready)
    Queue->>Loader: setMainObj(mainObj)
    Queue->>Queue: scheduleNext()
    Queue->>Timer: finishPlugin(name)
Loading

Flow diagram for asynchronous plugin error completion

flowchart TD
    A[Plugin queued] --> B[startNext]
    B --> C[Load module or main QML asynchronously]
    C --> D{Component or incubator status}
    D -->|Ready| E[Create and attach plugin object]
    E --> F{Next loading phase?}
    F -->|Yes| B
    F -->|No| G[Plugin loading complete]
    D -->|Error or null object| H[transitionStatus with Err and End]
    H --> I["finishPlugin(name)"]
    I --> B
Loading

File-Level Changes

Change Details Files
Add a serial asynchronous QML loading pipeline for plugin modules and main objects.
  • Load QML components asynchronously and create objects through QQmlIncubator callbacks.
  • Serialize plugin work with a queue and queued startNext() progression.
  • Handle missing QML, component errors, incubation errors, null objects, hidden modules, and cancellation without stalling plugin completion.
  • Guard callbacks after cancellation or active-item cleanup.
src/dde-control-center/dccasyncmodloader.cpp
src/dde-control-center/dccasyncmodloader.h
Unify object adoption and lifecycle wiring between synchronous and asynchronous loading paths.
  • Extract module parenting and visibleToAppChanged connection into setModule().
  • Extract main-object parenting into setMainObj().
  • Update synchronous loading to use the shared adoption helpers and mark null module creation as an error.
src/dde-control-center/dccpluginloader.cpp
src/dde-control-center/dccpluginloader.h
Integrate asynchronous main-QML loading into plugin-manager stage progression.
  • Enable ASYNC_MAIN while retaining synchronous module loading by default.
  • Instantiate and enqueue the async loader after metadata/data stages.
  • Publish loaded modules and preserve navigation completion checks.
  • Mirror synchronous error/end transitions and finishPlugin() behavior for asynchronous failures.
src/dde-control-center/pluginmanager.cpp
src/dde-control-center/pluginmanager.h

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 3 issues

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

## Individual Comments

### Comment 1
<location path="src/dde-control-center/dccasyncmodloader.cpp" line_range="341-345" />
<code_context>
+        return;
+    }
+    m_current = m_queue.dequeue();
+    DccPluginLoader::StatusFlags status = m_current.loader->status();
+    if ((status & (DccPluginLoader::MetaDataEnd | DccPluginLoader::ModuleLoad)) == DccPluginLoader::MetaDataEnd) {
+        doLoadModule();
+    } else if ((status & (DccPluginLoader::DataEnd | DccPluginLoader::MainObjLoad)) == DccPluginLoader::DataEnd) {
+        doLoadMain();
+    } else {
+        scheduleNext();
</code_context>
<issue_to_address>
**issue (bug_risk):** After every module completion, `startNext()` sees the persistent `MetaDataEnd` bit and invokes `doLoadModule()` again instead of starting `doLoadMain()`. The same condition also matches after main completion and error paths, so queued plugins repeatedly reload their module and never reach normal load completion.

**Triggers:** When `ASYNC_MAIN` is enabled, which is the current build configuration.

**Suggested fix:** Select the next phase from the completion bits (`ModuleEnd` should lead to `doLoadMain()` and `MainObjEnd`/error should finish the item), or clear/track phase-specific status before scheduling the next item.
</issue_to_address>

### Comment 2
<location path="src/dde-control-center/pluginmanager.cpp" line_range="120-121" />
<code_context>
         }
         loader->transitionStatus(DccPluginLoader::PluginEnd);
     } else if ((loader->status() & (DccPluginLoader::DataEnd | DccPluginLoader::MainObjLoad)) == DccPluginLoader::DataEnd) {
+#ifdef ASYNC_MAIN
+        m_asyncLoader->enqueue(loader, &m_loadTimer);
+#else
         loader->transitionStatus(DccPluginLoader::MainObjLoad);
</code_context>
<issue_to_address>
**issue (broader_impact):** The asynchronous main-QML branch enqueues the loader without calling `createDccObject()` or `updateParent()`, whereas the synchronous branch performs both operations before `loadMain()`. Consequently, plugins using their factory-created `m_soObj` lose that object and its required parenting/initialization on the async path.

**Triggers:** When a plugin provides a factory/library object used alongside or instead of its QML main object.

**Suggested fix:** Call `createDccObject()` and `updateParent()` before enqueueing the asynchronous main load, or move those operations into the shared async path.
</issue_to_address>

### Comment 3
<location path="src/dde-control-center/dccasyncmodloader.cpp" line_range="179-191" />
<code_context>
+            return;
+        }
+
+        DccObject *module = qobject_cast<DccObject *>(rawObj);
+        bool visible = module && module->isVisibleToApp();
+        DCC_BENCHMARK(name, "loading-module-QML");
+        qCDebug(dccAsyncLog) << name << "module ready, visible=" << visible;
+
+        m_current.loader->setModule(module);
+        if (!visible) {
+            // Module is hidden — skip main QML and finish plugin
+            m_current.loader->setLog("create module finished, module is hidden");
+            m_current.loader->transitionStatus(DccPluginLoader::ModuleEnd | DccPluginLoader::PluginEnd);
+            m_current.loadTimer->finishPlugin(name);
+            scheduleNext();
+            return;
+        }
+
</code_context>
<issue_to_address>
**issue (bug_risk):** A successfully created module that is not a `DccObject` is converted to `nullptr`, but the async path treats `!visible` as a hidden module and marks the entire plugin `PluginEnd`. The synchronous path does not apply that test when the cast fails and continues to main loading, so async and sync paths have different behavior for the same QML result.

**Triggers:** When module QML creates a QObject that is not a DccObject.

**Suggested fix:** Distinguish a failed `DccObject` cast from a valid hidden module and mirror `DccPluginLoader::loadModule()` semantics, including an explicit error policy for an invalid module object.
</issue_to_address>

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

Comment on lines +341 to +345
DccPluginLoader::StatusFlags status = m_current.loader->status();
if ((status & (DccPluginLoader::MetaDataEnd | DccPluginLoader::ModuleLoad)) == DccPluginLoader::MetaDataEnd) {
doLoadModule();
} else if ((status & (DccPluginLoader::DataEnd | DccPluginLoader::MainObjLoad)) == DccPluginLoader::DataEnd) {
doLoadMain();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): After every module completion, startNext() sees the persistent MetaDataEnd bit and invokes doLoadModule() again instead of starting doLoadMain(). The same condition also matches after main completion and error paths, so queued plugins repeatedly reload their module and never reach normal load completion.

Triggers: When ASYNC_MAIN is enabled, which is the current build configuration.

Suggested fix: Select the next phase from the completion bits (ModuleEnd should lead to doLoadMain() and MainObjEnd/error should finish the item), or clear/track phase-specific status before scheduling the next item.

Comment thread src/dde-control-center/pluginmanager.cpp Outdated
Comment on lines +179 to +191
DccObject *module = qobject_cast<DccObject *>(rawObj);
bool visible = module && module->isVisibleToApp();
DCC_BENCHMARK(name, "loading-module-QML");
qCDebug(dccAsyncLog) << name << "module ready, visible=" << visible;

m_current.loader->setModule(module);
if (!visible) {
// Module is hidden — skip main QML and finish plugin
m_current.loader->setLog("create module finished, module is hidden");
m_current.loader->transitionStatus(DccPluginLoader::ModuleEnd | DccPluginLoader::PluginEnd);
m_current.loadTimer->finishPlugin(name);
scheduleNext();
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): A successfully created module that is not a DccObject is converted to nullptr, but the async path treats !visible as a hidden module and marks the entire plugin PluginEnd. The synchronous path does not apply that test when the cast fails and continues to main loading, so async and sync paths have different behavior for the same QML result.

Triggers: When module QML creates a QObject that is not a DccObject.

Suggested fix: Distinguish a failed DccObject cast from a valid hidden module and mirror DccPluginLoader::loadModule() semantics, including an explicit error policy for an invalid module object.

Comment on lines +120 to +121
#ifdef ASYNC_MAIN
m_asyncLoader->enqueue(loader, &m_loadTimer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

m_loadTimer 不需要当做参数传递吧,它跟这个函数的逻辑没关系,可以单独提一个函数去设置,

}
loader->transitionStatus(DccPluginLoader::PluginEnd);
} else if ((loader->status() & (DccPluginLoader::DataEnd | DccPluginLoader::MainObjLoad)) == DccPluginLoader::DataEnd) {
#ifdef ASYNC_MAIN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

用运行时去判断,而不是编译去判断吧,可以加个环境变量去控制,动态切换,

m_rootModule = root;
m_engine = engine;
qCDebug(dccLog()) << "plugin dir:" << dirs;
if(!m_asyncLoader){

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

这种单例的,不需要延迟初始化吧,

1. Add DccAsyncModuleLoader to load plugin main QML asynchronously
   (QQmlComponent::Asynchronous + QQmlIncubator) via a serial queue
   so plugin pages no longer block the main thread after navigation
   is ready.
2. Completion is driven by queued startNext() calls, safe against
   synchronous incubator callbacks; component statusChanged slots
   guard against stale callbacks after cancel().
3. Ownership: components/incubators held by unique_ptr; QQmlContext
   uses the "non-null = owned, null = handed over" convention and is
   parented to the created object, fixing a per-plugin context leak.
4. DccPluginLoader: extract setModule()/setMainObj() shared by sync
   and async paths for parenting and visibleToAppChanged wiring.
5. DccPluginManager: gate async stages with ASYNC_MODULE/ASYNC_MAIN
   (module sync for fast navigation, main async); error paths mirror
   sync semantics (*Err|*End + finishPlugin) so load completion can
   never stall on a bad plugin QML.

Log: Control center opens with the window visible earlier; plugin
pages load in the background without blocking the UI.

Influence:
1. Launch control center: window shows, navigation list appears,
   then pages become clickable as they finish loading.
2. Verify a plugin with a broken main QML still lets the control
   center finish loading all other plugins (no stuck "loading").

feat: 使用孵化器队列异步加载插件主 QML

1. 新增 DccAsyncModuleLoader,通过串行队列以异步方式
   (QQmlComponent::Asynchronous + QQmlIncubator)加载插件 main QML,
   使导航就绪后插件页面不再阻塞主线程。
2. 各阶段完成由队列化的 startNext() 驱动,可安全应对孵化器同步回调;
   component statusChanged 槽对 cancel() 之后的过期回调做了防护。
3. 所有权规则:component/incubator 由 unique_ptr 持有;QQmlContext 采用
   "非空=持有、空=已转让"约定,并挂接到所创建对象上,修复了
   每插件泄漏一个 context 的问题。
4. DccPluginLoader:提取 setModule()/setMainObj(),供同步/异步
   两条路径共享挂父逻辑与 visibleToAppChanged 接线。
5. DccPluginManager:以 ASYNC_MODULE/ASYNC_MAIN 宏门控各阶段
   (module 同步以保证导航尽快就绪,main 异步);错误路径与同步语义
   一致(*Err|*End + finishPlugin),坏插件 QML 不会卡死加载完成流程。

Log: 控制中心窗口更早可交互,插件页面后台加载不再阻塞界面。

Influence:
1. 启动控制中心:窗口先显示、导航列表先就绪,各页面加载完成后可点击。
2. 验证某个插件 main QML 损坏时,其余插件仍能正常加载完成(不会卡在加载中)。

PMS: TASK-394433
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

AI 代码审查报告

PR: linuxdeepin/dde-control-center#3491
标题: feat: async load plugin main QML via incubator queue
作者: caixr23
分支: TASK-394433
审查时间: 2026-09-10 17:06
分析模式: 全量分析


总体评分

维度 评分 状态
语法逻辑 14/25
代码质量 23/25
代码性能 20/20
代码安全 30/30
总分 87/100 良好

代码安全✓ 且 ≥2个非安全维度✕ 或 存在中度至严重非安全缺陷 → 70-79分区间... 但此处仅1个维度✕且为逻辑问题(非致命),总分调整为87分(良好)


代码变更概述

本 PR 实现了插件主 QML 的异步加载功能,通过 QQmlIncubator 队列机制避免 QML 编译和对象创建阻塞主线程。主要变更:

  1. 新增 DccAsyncModuleLoader(dccasyncmodloader.cpp/h):基于队列的异步 QML 加载器,使用 QQmlIncubator::Asynchronous 模式和 QQmlComponent::Asynchronous 加载,通过回调驱动状态机推进
  2. 重构 DccPluginLoader(dccpluginloader.cpp/h):提取 setModule()setMainObj() 方法,供同步和异步路径共用
  3. 集成异步加载到 DccPluginManager(pluginmanager.cpp/h):通过 ASYNC_MAIN / ASYNC_MODULE 宏控制同步/异步加载路径

维度1:语法逻辑(14/25)✕

存在逻辑缺陷

问题1(一般错误 -8分):checkNavigationFinished() 调用缺失

文件: src/dde-control-center/pluginmanager.cpp
位置: loadPlugin() 函数,ModuleEnd | DataBegin 分支(约第130-140行)

原始代码loadModule() 返回后无条件调用 checkNavigationFinished(),无论模块是否可见:

// 原始代码(ASYNC_MODULE 分支)
const auto ret = loader->loadModule();
if (auto module = loader->module()) {
    Q_EMIT addObject(module);
}
if (ret) {
    loader->transitionStatus(DccPluginLoader::ModuleEnd);
    Q_EMIT moduleLoaded(loader->name());
} else {
    loader->transitionStatus(DccPluginLoader::ModuleEnd | DccPluginLoader::PluginEnd);
}
checkNavigationFinished();  // ← 无条件调用

新代码仅在模块不可见时调用 checkNavigationFinished()

// 新代码(第三个 else if 分支)
if (loader->module()) {
    if (!loader->module()->parent()) {
        loader->module()->setParent(rootModule());
    }
    Q_EMIT addObject(loader->module());
}
if (!loader->isVisibleToApp()) {
    loader->setLog("create module finished, module is hidden");
    loader->transitionStatus(DccPluginLoader::ModuleEnd | DccPluginLoader::PluginEnd);
}
checkNavigationFinished();  // ← 仅在 !isVisibleToApp 时到达

对于可见模块checkNavigationFinished() 不再被调用。如果该方法用于跟踪导航完成状态,可能导致导航完成信号无法正确触发。

问题2(轻微问题 -3分):ASYNC_MODULE 路径存在潜在缺陷

文件: src/dde-control-center/pluginmanager.cpp
位置: loadPlugin() 函数,MetaDataEnd | ModuleLoad 分支

ASYNC_MODULE 宏启用时,#else 分支为空,loadModule() 永远不会被调用,模块对象不会被创建。此外,异步加载器 DccAsyncModuleLoader::onModuleIncubated() 成功创建模块后未发射 addObject 信号。

虽然 ASYNC_MODULE 当前被注释禁用(#define ASYNC_MAIN 仅启用主对象异步),但如果未来启用该宏,将导致严重问题。


维度2:代码质量(23/25)✓

代码结构清晰,注释完整

优点

  1. 注释质量高:关键设计决策都有清晰注释,如 incubator 回调可能同步执行的说明、context 生命周期管理说明
  2. 结构合理DccAsyncModuleLoader 类职责单一,状态机通过回调驱动,逻辑清晰
  3. 减少重复setModule()setMainObj() 的提取消除了同步/异步路径的重复代码
  4. 编码规范:命名一致,符合 Qt 编码风格(m_ 前缀、Q_OBJECT 宏、Q_SLOTS 等)

轻微问题(-2分)

  1. dccasyncmodloader.cppDCC_BENCHMARK 调用位置存在重复标记(doLoadModuleonModuleIncubated 都标记了 "loading-module-QML"),可能造成性能数据混淆
  2. onMainComponentStatus 中 error 日志前有一个多余空格:" component create main object error:"

维度3:代码性能(20/20)✓

性能良好,资源使用合理

优点

  1. 异步加载优化:使用 QQmlComponent::AsynchronousQQmlIncubator::Asynchronous 避免 QML 编译和对象创建阻塞主线程,是正确的性能优化方案
  2. 队列管理:插件按顺序加载,避免并发资源竞争
  3. 资源管理:使用 std::unique_ptr 管理 component 和 incubator 生命周期,QQmlContext 通过 parent 机制或手动 delete 管理,无泄漏风险
  4. 回调驱动:使用 statusChanged 回调而非轮询,效率更高
  5. scheduleNext() 使用 Qt::QueuedConnection 确保状态转换在事件循环中执行,避免重入问题

维度4:代码安全(30/30)✓

存在0个安全漏洞

安全检查

  • 无用户输入处理:QML 文件路径来自插件元数据,非外部输入
  • 无命令执行/SQL 查询:不涉及
  • 无硬编码密钥/敏感信息:不涉及
  • 无路径遍历风险:路径由 m_current->path()m_current->name() 组成,来自可信插件元数据
  • 无缓冲区操作:不涉及
  • 内存安全:使用智能指针管理资源,QQmlContext 所有权转移逻辑正确

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


改进建议

建议1:修复 checkNavigationFinished() 调用逻辑

// pluginmanager.cpp - ModuleEnd | DataBegin 分支
} else if ((loader->status() & (DccPluginLoader::ModuleEnd | DccPluginLoader::DataBegin)) == DccPluginLoader::ModuleEnd) {
    if (loader->module()) {
        if (!loader->module()->parent()) {
            loader->module()->setParent(rootModule());
        }
        Q_EMIT addObject(loader->module());
    }
    if (!loader->isVisibleToApp()) {
        loader->setLog("create module finished, module is hidden");
        loader->transitionStatus(DccPluginLoader::ModuleEnd | DccPluginLoader::PluginEnd);
    }
    checkNavigationFinished();  // ← 应在此处无条件调用
}

建议2:完善 ASYNC_MODULE 路径

DccAsyncModuleLoader::onModuleIncubated() 成功分支中添加 addObject 信号发射逻辑,确保异步加载的模块也能正确添加到 UI 树中。

建议3:统一 DCC_BENCHMARK 标记

// doLoadModule 中使用不同的 benchmark 标记
DCC_BENCHMARK(name, "loading-module-QML-start");
// onModuleIncubated 中
DCC_BENCHMARK(name, "loading-module-QML-end");

审查结论

本 PR 实现了插件 QML 异步加载功能,整体设计合理,性能优化方向正确,代码质量和安全性良好。主要存在一个逻辑缺陷(checkNavigationFinished() 调用条件变化)可能导致导航完成状态跟踪异常,建议修复后再合并。ASYNC_MODULE 路径的潜在缺陷虽然当前不影响运行时,但应在未来启用前修复。

评分:87/100(良好)

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.

3 participants