Skip to content

feat: async load plugin QML via incubator queue - #3502

Open
caixr23 wants to merge 1 commit into
linuxdeepin:masterfrom
caixr23:T394433
Open

caixr23 wants to merge 1 commit into
linuxdeepin:masterfrom
caixr23:T394433

Conversation

@caixr23

@caixr23 caixr23 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor
  1. Load plugin module/main QML asynchronously (QQmlComponent::Asynchronous
    • QQmlIncubator) inside DccPluginLoader: AsyncIncubator subclasses QQmlIncubator so the engine drives completion via statusChanged directly, with guards against stale callbacks after cancelAsync().
  2. DccPluginManager drives a serial async queue (m_asyncQueue, m_asyncBusy, startNextAsync): module QML stays synchronous (ASYNC_MODULE off) so navigation appears fast, main QML loads asynchronously (ASYNC_MAIN on) without blocking the main thread.
  3. Split loadModule()/loadMain() into sync wrappers (transitionStatus Load/End) and doLoadModule()/doLoadMain() implementations; extract setModule()/setMainObj() shared by both paths for parenting and visibleToAppChanged wiring.
  4. Add ModuleAdd status; move addObject emission and the hidden-module short-circuit into DccPluginManager::loadPlugin with a ModuleAdd guard against duplicate adds on reload.
  5. cancelLoad()/reset() abort queued and in-flight async loading; QQmlContext ownership follows "non-null = owned, null = handed over" and is parented to the created object to avoid leaks.

Log: Control center navigation appears earlier; plugin pages load in the background without blocking the UI.

Influence:

  1. Launch control center: window shows and navigation appears first, plugin pages finish loading in the background without freezing.
  2. Verify a plugin with broken main QML does not stall overall loading; other plugins still finish and cancel/reload paths stay clean.

feat: 通过孵化器队列异步加载插件 QML

  1. 在 DccPluginLoader 内以异步方式(QQmlComponent::Asynchronous + QQmlIncubator)加载插件 module/main QML:AsyncIncubator 继承 QQmlIncubator,由引擎的 statusChanged 直接驱动完成,并对 cancelAsync() 之后的过期回调做了防护。
  2. DccPluginManager 以串行异步队列驱动(m_asyncQueue、m_asyncBusy、 startNextAsync):module QML 保持同步(ASYNC_MODULE 关闭)保证导航 尽快出现,main QML 异步加载(ASYNC_MAIN 开启)不阻塞主线程。
  3. 将 loadModule()/loadMain() 拆分为同步包装(transitionStatus Load/End)与 doLoadModule()/doLoadMain() 实现;提取 setModule()/ setMainObj() 供同步/异步两条路径共享挂父逻辑与 visibleToAppChanged 接线。
  4. 新增 ModuleAdd 状态;将 addObject 发送与隐藏插件短路移入 DccPluginManager::loadPlugin,并以 ModuleAdd 防止重新加载时重复添加。
  5. cancelLoad()/reset() 会中止排队与进行中的异步加载;QQmlContext 所有权遵循"非空=持有、空=已转让"约定并挂接到所创建对象,避免泄漏。

Log: 控制中心导航更早出现,插件页面后台加载不阻塞界面。

Influence:

  1. 启动控制中心:窗口与导航列表先就绪,各插件页面后台加载完成, 过程中界面不卡顿。
  2. 验证某个插件 main QML 损坏时不会卡死整体加载,其余插件正常完成, 取消/重新加载路径无残留。

PMS: TASK-394433

Summary by Sourcery

Enable non-blocking background loading of plugin pages while preserving fast synchronous navigation initialization.

New Features:

  • Load plugin main QML asynchronously through a serial incubator queue so navigation becomes available sooner without blocking the UI.

Bug Fixes:

  • Prevent broken or cancelled QML loads from stalling subsequent plugins and avoid duplicate module additions during reloads.
  • Clean up asynchronous loading state and QML contexts reliably across cancellation, reset, and failed creation paths.

Enhancements:

  • Separate synchronous and asynchronous loading flows while sharing object setup and visibility handling.

@caixr23
caixr23 requested a review from 18202781743 September 11, 2026 05:45
@sourcery-ai

sourcery-ai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR changes plugin startup so module QML remains synchronous for fast navigation while main QML is created through a cancellation-safe QQmlComponent/QQmlIncubator pipeline serialized by DccPluginManager, with revised status handling, ownership cleanup, and duplicate-safe module registration.

Sequence diagram for serialized asynchronous plugin main QML loading

sequenceDiagram
    participant DccPluginManager
    participant DccPluginLoader
    participant QQmlComponent
    participant AsyncIncubator
    participant MainQML

    DccPluginManager->>DccPluginLoader: createDccObject()
    DccPluginManager->>DccPluginManager: startNextAsync()
    DccPluginManager->>DccPluginLoader: asyncLoadMain()
    DccPluginLoader->>QQmlComponent: loadUrl/loadFromModule(Asynchronous)
    QQmlComponent-->>DccPluginLoader: onComponentStatus(Ready)
    DccPluginLoader->>QQmlComponent: create(AsyncIncubator, context)
    QQmlComponent->>AsyncIncubator: statusChanged(Ready)
    AsyncIncubator->>DccPluginLoader: onIncubated(Ready)
    DccPluginLoader->>MainQML: setMainObj()
    DccPluginLoader->>DccPluginLoader: finishAsync()
    DccPluginLoader-->>DccPluginManager: MainObjEnd
    DccPluginManager->>DccPluginManager: startNextAsync()
Loading

File-Level Changes

Change Details Files
Introduces asynchronous QML component loading and incubation with cancellation-safe lifecycle and explicit context ownership.
  • Adds an AsyncIncubator driven by QQmlIncubator::statusChanged.
  • Loads module and main QML through asynchronous QQmlComponent APIs.
  • Separates synchronous wrappers from shared loading implementations and object-parenting helpers.
  • Guards late callbacks and cleans up components, incubators, and contexts on completion or cancellation.
src/dde-control-center/dccpluginloader.cpp
src/dde-control-center/dccpluginloader.h
Adds a serial plugin-manager queue that keeps module loading synchronous while backgrounding main-page loading.
  • Queues asynchronous module/main stages with m_asyncQueue and m_asyncBusy.
  • Advances the queue from module/main completion notifications.
  • Enables ASYNC_MAIN while leaving ASYNC_MODULE disabled so navigation appears promptly.
  • Clears queued and in-flight asynchronous work during cancellation.
src/dde-control-center/pluginmanager.cpp
src/dde-control-center/pluginmanager.h
Reworks plugin status transitions and module registration to support asynchronous completion and reloads.
  • Adds ModuleAdd as a duplicate-registration guard.
  • Moves addObject emission and hidden-module handling into the manager.
  • Preserves moduleLoaded/navigation checks after module registration.
  • Marks QML creation errors and missing main QML without blocking subsequent queued plugins.
src/dde-control-center/dccpluginloader.cpp
src/dde-control-center/dccpluginloader.h
src/dde-control-center/pluginmanager.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="src/dde-control-center/dccpluginloader.cpp" line_range="596-599" />
<code_context>
+            qCDebug(dccAsyncLog) << name() << "main object ready";
+            DccObject *mainObj = qobject_cast<DccObject *>(rawObj);
+            // 先定对象归属,再挂 context,最后置空(所有权转移点)
+            setMainObj(mainObj);
+            m_asyncContext->setParent(mainObj);
+            m_asyncContext = nullptr;
+            transitionStatus(MainObjEnd);
+        }
+        finishAsync();
</code_context>
<issue_to_address>
**issue (bug_risk):** If asynchronous main QML creation returns a valid `QObject` that is not a `DccObject`, `setMainObj()` leaves `m_mainObj` null and does not parent the raw object; the following `setParent(mainObj)` reparents the context to null and then clears `m_asyncContext`. Both the created object and its context are consequently leaked.

**Triggers:** When a main QML root object violates the expected `DccObject` type.

**Suggested fix:** Parent the context and created object using the raw `QObject` first, and treat a failed `DccObject` cast as a creation error.
</issue_to_address>

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

Comment on lines +596 to +599
setMainObj(mainObj);
m_asyncContext->setParent(mainObj);
m_asyncContext = nullptr;
transitionStatus(MainObjEnd);

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): If asynchronous main QML creation returns a valid QObject that is not a DccObject, setMainObj() leaves m_mainObj null and does not parent the raw object; the following setParent(mainObj) reparents the context to null and then clears m_asyncContext. Both the created object and its context are consequently leaked.

Triggers: When a main QML root object violates the expected DccObject type.

Suggested fix: Parent the context and created object using the raw QObject first, and treat a failed DccObject cast as a creation error.

@deepin-bot

deepin-bot Bot commented Sep 11, 2026

Copy link
Copy Markdown

TAG Bot

New tag: 6.1.107
DISTRIBUTION: unstable
Suggest: synchronizing this PR through rebase #3503

case QQmlComponent::Ready: {
if (isModule) {
transitionStatus(ModuleCreate);
DCC_BENCHMARK(name(), "module-object-create");

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.

DCC_BENCHMARK(name(), "module-object-create"); 这个日志无效了吧,

1. Load plugin module/main QML asynchronously (QQmlComponent::Asynchronous
   + QQmlIncubator) inside DccPluginLoader: AsyncIncubator subclasses
   QQmlIncubator so the engine drives completion via statusChanged
   directly, with guards against stale callbacks after cancelAsync().
2. DccPluginManager drives a serial async queue (m_asyncQueue,
   m_asyncBusy, startNextAsync): module QML stays synchronous
   (ASYNC_MODULE off) so navigation appears fast, main QML loads
   asynchronously (ASYNC_MAIN on) without blocking the main thread.
3. Split loadModule()/loadMain() into sync wrappers (transitionStatus
   Load/End) and doLoadModule()/doLoadMain() implementations; extract
   setModule()/setMainObj() shared by both paths for parenting and
   visibleToAppChanged wiring.
4. Add ModuleAdd status; move addObject emission and the hidden-module
   short-circuit into DccPluginManager::loadPlugin with a ModuleAdd
   guard against duplicate adds on reload.
5. cancelLoad()/reset() abort queued and in-flight async loading;
   QQmlContext ownership follows "non-null = owned, null = handed over"
   and is parented to the created object to avoid leaks.

Log: Control center navigation appears earlier; plugin pages load in
the background without blocking the UI.

Influence:
1. Launch control center: window shows and navigation appears first,
   plugin pages finish loading in the background without freezing.
2. Verify a plugin with broken main QML does not stall overall loading;
   other plugins still finish and cancel/reload paths stay clean.

feat: 通过孵化器队列异步加载插件 QML

1. 在 DccPluginLoader 内以异步方式(QQmlComponent::Asynchronous +
   QQmlIncubator)加载插件 module/main QML:AsyncIncubator 继承
   QQmlIncubator,由引擎的 statusChanged 直接驱动完成,并对
   cancelAsync() 之后的过期回调做了防护。
2. DccPluginManager 以串行异步队列驱动(m_asyncQueue、m_asyncBusy、
   startNextAsync):module QML 保持同步(ASYNC_MODULE 关闭)保证导航
   尽快出现,main QML 异步加载(ASYNC_MAIN 开启)不阻塞主线程。
3. 将 loadModule()/loadMain() 拆分为同步包装(transitionStatus
   Load/End)与 doLoadModule()/doLoadMain() 实现;提取 setModule()/
   setMainObj() 供同步/异步两条路径共享挂父逻辑与
   visibleToAppChanged 接线。
4. 新增 ModuleAdd 状态;将 addObject 发送与隐藏插件短路移入
   DccPluginManager::loadPlugin,并以 ModuleAdd 防止重新加载时重复添加。
5. cancelLoad()/reset() 会中止排队与进行中的异步加载;QQmlContext
   所有权遵循"非空=持有、空=已转让"约定并挂接到所创建对象,避免泄漏。

Log: 控制中心导航更早出现,插件页面后台加载不阻塞界面。

Influence:
1. 启动控制中心:窗口与导航列表先就绪,各插件页面后台加载完成,
   过程中界面不卡顿。
2. 验证某个插件 main QML 损坏时不会卡死整体加载,其余插件正常完成,
   取消/重新加载路径无残留。

PMS: TASK-394433
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.

2 participants