Skip to content

fix: defer DSysInfo queries in plugins to avoid dlopen deadlock#3341

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
yixinshark:defer-dsysinfo-plugin-load
Jul 9, 2026
Merged

fix: defer DSysInfo queries in plugins to avoid dlopen deadlock#3341
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
yixinshark:defer-dsysinfo-plugin-load

Conversation

@yixinshark

@yixinshark yixinshark commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace namespace-scope const globals (UosType, IsServerSystem, IsCommunitySystem, etc.) in plugin utils.h headers with static inline lazy-init helper functions that compute the value once on first runtime call, moving DSysInfo queries out of the dlopen global constructor phase.
  • Affected plugins: sound, power, commoninfo, deepinid.
  • This breaks one side of a deadlock chain observed in the control center: Thread A holds the glibc/ld.so loader lock during plugin dlopen -> calls DSysInfo -> emits qWarning -> enters dtklog and waits for the dtklog write lock; Thread B holds the dtklog write lock -> formats %{function} via QRegularExpression/pcre2 -> triggers TLS/loader init -> waits for the loader lock.

Background

On systems where /etc/os-version is missing or malformed, DSysInfo::ensureOsVersion() emits qWarning(), which enters the dtklog appender pipeline. When this happens inside a plugin's global/static initialization (during QPluginLoader::load()), the calling thread already holds the dynamic loader lock, creating a lock-order inversion with dtklog.

The companion fixes in dtkcore (DSysInfo::ensureOsVersion no longer logs while holding DSysInfoPrivate::mutex) and dtklog (AbstractStringAppender::qCleanupFuncinfo no longer uses QRegularExpression inside the appender write lock) address the other two links in the chain. This PR addresses the control-center/application side.

Test plan

  • Build all affected plugins (sound, power, commoninfo, deepinid) without errors.
  • Launch dde-control-center on a system with valid /etc/os-version — verify no regression in edition/type detection (server vs community vs professional, etc.).
  • Launch dde-control-center on a system with missing/malformed /etc/os-version — verify no startup hang/deadlock.
  • Verify isServerSystem() / isCommunitySystem() return the same values as the old IsServerSystem / IsCommunitySystem globals on the same system.

Summary by Sourcery

Defer system edition/type detection in control center plugins to avoid DSysInfo calls during plugin global initialization and break the loader/dtklog deadlock chain.

Bug Fixes:

  • Prevent potential startup deadlocks in dde-control-center when DSysInfo logging is triggered during plugin loading on systems with missing or malformed /etc/os-version.

Enhancements:

  • Replace plugin-wide constant globals for UOS/deepin edition and type detection with static inline helper functions that lazily cache DSysInfo query results at first use across sound, power, commoninfo, and deepinid plugins.
  • Align SPDX copyright year ranges to include 2026 in affected plugin headers and sources.

@sourcery-ai

sourcery-ai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR refactors DSysInfo-based OS/edition detection in several control-center plugins to use lazily initialized helper functions instead of namespace-scope/static globals, deferring any DSysInfo queries until first runtime use to avoid loader-lock-related deadlocks during plugin dlopen, and updates SPDX copyright year ranges accordingly.

Sequence diagram for deferred DSysInfo queries in plugins

sequenceDiagram
    actor AppThread
    participant QPluginLoader
    participant Plugin
    participant DSysInfo
    participant dtklog

    alt Before_change_global_const_init
        AppThread->>QPluginLoader: load
        QPluginLoader->>Plugin: run_namespace_globals
        Plugin->>DSysInfo: uosEditionType
        DSysInfo->>DSysInfo: ensureOsVersion
        DSysInfo->>dtklog: qWarning
        dtklog->>dtklog: acquire_write_lock
    else After_change_lazy_static_helpers
        AppThread->>QPluginLoader: load
        QPluginLoader->>Plugin: construct_without_DSysInfo
        QPluginLoader-->>AppThread: load_finished
        AppThread->>Plugin: call_isCommunitySystem
        Plugin->>Plugin: isCommunitySystem
        Plugin->>DSysInfo: uosEditionType
        DSysInfo->>DSysInfo: ensureOsVersion
        DSysInfo->>dtklog: qWarning
        dtklog->>dtklog: acquire_write_lock
    end
Loading

File-Level Changes

Change Details Files
Replace namespace/global DSysInfo-derived constants in plugin utility headers with lazy-init helper functions that query DSysInfo on first use instead of at load time.
  • Remove UosType/UosEdition and boolean IsSystem/IsDeepin const globals in commoninfo and deepinid plugin utils headers.
  • Introduce static inline helper functions (e.g. isServerSystem(), isCommunitySystem(), isDeepinDesktop(), isNotDeepinUos()) that cache the computed DSysInfo-based booleans in function-local static variables.
  • Ensure all new helpers call DSysInfo query functions directly rather than relying on previous global variables.
src/plugin-commoninfo/operation/utils.h
src/plugin-deepinid/operation/utils.h
Refactor sound and power plugins to use new lazy isServerSystem() helpers instead of static globals for server-edition detection.
  • Replace const/static UosType and IsServerSystem globals with a static or static inline isServerSystem() function that computes and caches the edition check via DSysInfo::uosType().
  • Update soundmodel and powermodel logic that previously referenced IsServerSystem to call isServerSystem() instead, preserving behavior while deferring DSysInfo queries to runtime.
src/plugin-sound/operation/soundmodel.cpp
src/plugin-power/operation/utils.h
src/plugin-power/operation/powermodel.cpp
Update deepinid plugin implementation files to use the new edition-detection helpers instead of global booleans, ensuring runtime-only DSysInfo access in business logic.
  • Replace uses of IsCommunitySystem in URL selection, edition naming, icon naming, OAuth URI logic, and license state handling with calls to isCommunitySystem().
  • Keep the semantics of deepin vs UOS edition handling identical while changing the underlying mechanism for obtaining the edition information.
src/plugin-deepinid/operation/utils.cpp
src/plugin-deepinid/operation/deepinidworker.cpp
src/plugin-deepinid/operation/deepinidinterface.cpp
Refresh SPDX copyright year ranges in affected plugin files.
  • Extend existing UnionTech SPDX-FileCopyrightText year ranges to include 2026 and normalize ranges where necessary.
src/plugin-commoninfo/operation/utils.h
src/plugin-deepinid/operation/utils.h
src/plugin-sound/operation/soundmodel.cpp
src/plugin-deepinid/operation/utils.cpp
src/plugin-power/operation/utils.h
src/plugin-deepinid/operation/deepinidworker.cpp
src/plugin-power/operation/powermodel.cpp
src/plugin-deepinid/operation/deepinidinterface.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, and left some high level feedback:

  • The new is*System() helpers are duplicated across multiple plugins (commoninfo, power, deepinid, sound); consider centralizing them in a shared header/source to avoid drift if the logic or DSysInfo API ever changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `is*System()` helpers are duplicated across multiple plugins (`commoninfo`, `power`, `deepinid`, `sound`); consider centralizing them in a shared header/source to avoid drift if the logic or `DSysInfo` API ever changes.

## Individual Comments

### Comment 1
<location path="src/plugin-commoninfo/operation/utils.h" line_range="34" />
<code_context>
-const bool IsEducationSystem = (DSysInfo::UosEducation == UosEdition); // 是否是教育版
-const bool IsDeepinDesktop = (DSysInfo::DeepinDesktop == DSysInfo::deepinType());//是否是Deepin桌面
-const bool IsNotDeepinUos = !DSysInfo::isDeepin(); // 是否是 Deepin/Uos 以外的发行版
+static inline bool isServerSystem()
+{
+    static const bool serverSystem = DSysInfo::UosServer == DSysInfo::uosType();
</code_context>
<issue_to_address>
**issue (complexity):** Consider using C++17 inline variables or a small helper function to cache system info once and avoid repetitive per-flag functions in this header.

You can keep the one-time caching semantics and avoid the per-flag function boilerplate by using C++17 inline variables (or at least reducing repetition with a helper).

### 1. Replace function+local-static with inline variables

If you’re building with C++17, this keeps semantics (single initialization, header-safe) while making the header shorter and more readable:

```cpp
inline const DSysInfo::UosType UosType      = DSysInfo::uosType();
inline const DSysInfo::UosEdition UosEdition = DSysInfo::uosEditionType();

inline const bool IsServerSystem      = (DSysInfo::UosServer      == UosType);
inline const bool IsCommunitySystem   = (DSysInfo::UosCommunity   == UosEdition);
inline const bool IsProfessionalSystem= (DSysInfo::UosProfessional== UosEdition);
inline const bool IsHomeSystem        = (DSysInfo::UosHome        == UosEdition);
inline const bool IsEducationSystem   = (DSysInfo::UosEducation   == UosEdition);
inline const bool IsDeepinDesktop     = (DSysInfo::DeepinDesktop  == DSysInfo::deepinType());
inline const bool IsNotDeepinUos      = !DSysInfo::isDeepin();
```

Call sites can still use the function-style naming if you prefer by adding trivial inline accessors:

```cpp
inline bool isServerSystem()      { return IsServerSystem; }
inline bool isCommunitySystem()   { return IsCommunitySystem; }
// etc.
```

This preserves the single-evaluation behavior while avoiding repeated `static inline bool ... { static const bool ...; return ...; }` boilerplate.

### 2. If inline variables aren’t available, at least deduplicate logic

You can keep your current “function + internal static” approach but reduce repetition:

```cpp
static inline bool isUosEdition(DSysInfo::UosEdition edition)
{
    static const DSysInfo::UosEdition currentEdition = DSysInfo::uosEditionType();
    return currentEdition == edition;
}

static inline bool isCommunitySystem()   { return isUosEdition(DSysInfo::UosCommunity); }
static inline bool isProfessionalSystem(){ return isUosEdition(DSysInfo::UosProfessional); }
static inline bool isHomeSystem()        { return isUosEdition(DSysInfo::UosHome); }
static inline bool isEducationSystem()   { return isUosEdition(DSysInfo::UosEducation); }
```

This keeps your caching behavior but centralizes the edition query and removes duplicated initialization logic.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/plugin-commoninfo/operation/utils.h
@yixinshark yixinshark force-pushed the defer-dsysinfo-plugin-load branch from 7b4e914 to 3165c9b Compare July 9, 2026 02:41
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:92分

■ 【总体评价】

代码通过将全局const变量重构为static inline函数解决了静态初始化顺序问题(SIOF),整体质量良好
逻辑正确且解决了潜在初始化风险,但因soundmodel.cpp中函数声明不一致及少量代码规范性问题扣8分

■ 【详细分析】

  • 1.语法逻辑(基本正确)✓

diff中所有文件将全局const变量改为static inline函数,使用static const局部变量缓存结果,这是解决SIOF的标准做法。函数返回值类型为bool,逻辑判断正确。调用处如deepinidinterface.cpp:19、deepinidworker.cpp:410、utils.cpp:30等均正确替换为函数调用形式。
潜在问题:soundmodel.cpp:16-20中isServerSystem()函数声明为static而非static inline,与其他文件不一致,虽然不影响功能但破坏了一致性
建议:统一所有文件中的函数声明为static inline;确保所有调用点已正确替换

  • 2.代码质量(良好)✓

命名规范从PascalCase(IsServerSystem)改为camelCase(isServerSystem),符合Qt代码风格。注释保留得当,如"//是否是服务器版"等说明性注释在函数内保留。代码结构清晰,每个函数职责单一。
潜在问题:plugin-commoninfo/operation/utils.h和plugin-deepinid/operation/utils.h中存在重复的函数定义,未来维护需同步修改两处;soundmodel.cpp中函数声明方式与其他文件不一致
建议:考虑将公共系统判断函数抽取到独立的共享头文件中;统一所有isServerSystem()等函数的声明方式为static inline

  • 3.代码性能(高效)✓

使用static const局部变量实现惰性初始化,仅在首次调用时执行DSysInfo::uosType()等函数,后续调用直接返回缓存值。inline关键字避免了函数调用开销。这种模式既解决了SIOF问题又保证了运行时性能。
建议:保持当前实现方式,无需优化

  • 4.代码安全(存在0个安全漏洞)✓

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个
本次变更不涉及用户输入处理、网络通信、文件操作等敏感操作,仅重构系统信息获取逻辑。DSysInfo相关函数为系统级API,不存在注入风险。所有URL(如login.deepin.org、login.uniontech.com)为硬编码常量,非用户可控输入。

  • 建议:无需额外安全修复措施

■ 【改进建议代码示例】

// soundmodel.cpp 修复:统一使用 static inline 声明
// 同时建议抽取公共函数到共享头文件

// === 方案1:修复 soundmodel.cpp 的一致性问题 ===
// src/plugin-sound/operation/soundmodel.cpp

- static bool isServerSystem()
+ static inline bool isServerSystem()
  {
      static const bool serverSystem = Dtk::Core::DSysInfo::UosServer == Dtk::Core::DSysInfo::uosType();
      return serverSystem;
  }


// === 方案2(推荐):抽取公共系统判断函数到独立共享头文件 ===
// src/shared/utils/systeminfo.h
#ifndef SYSTEMINFO_H
#define SYSTEMINFO_H

#include <DStandardPaths>
#include <DSysInfo>

DCORE_USE_NAMESPACE

namespace SystemInfo {

static inline bool isServerSystem()
{
    static const bool serverSystem = DSysInfo::UosServer == DSysInfo::uosType();
    return serverSystem;
}

static inline bool isCommunitySystem()
{
    static const bool communitySystem = DSysInfo::UosCommunity == DSysInfo::uosEditionType();
    return communitySystem;
}

static inline bool isProfessionalSystem()
{
    static const bool professionalSystem = DSysInfo::UosProfessional == DSysInfo::uosEditionType();
    return professionalSystem;
}

static inline bool isHomeSystem()
{
    static const bool homeSystem = DSysInfo::UosHome == DSysInfo::uosEditionType();
    return homeSystem;
}

static inline bool isEducationSystem()
{
    static const bool educationSystem = DSysInfo::UosEducation == DSysInfo::uosEditionType();
    return educationSystem;
}

static inline bool isDeepinDesktop()
{
    static const bool deepinDesktop = DSysInfo::DeepinDesktop == DSysInfo::deepinType();
    return deepinDesktop;
}

static inline bool isNotDeepinUos()
{
    static const bool notDeepinUos = !DSysInfo::isDeepin();
    return notDeepinUos;
}

} // namespace SystemInfo

#endif // SYSTEMINFO_H

// 各插件使用时只需 include 该头文件并调用 SystemInfo::isServerSystem() 等

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: caixr23, yixinshark

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

The control center loads plugins via QPluginLoader during startup. Several
plugins called DSysInfo::uosType()/uosEditionType()/deepinType()/isDeepin()
in global/static initialization (namespace-scope const variables), which runs
while the dynamic loader holds the glibc/ld.so loader lock.

On systems where /etc/os-version is missing or malformed, DSysInfo internally
emits qWarning(), which enters the dtklog appender pipeline. Meanwhile another
thread already holding the dtklog write lock formats %{function} via
QRegularExpression/pcre2, which can trigger TLS/loader initialization and
wait on the same loader lock. This forms a classic deadlock:

  Thread A: loader lock -> DSysInfo -> qWarning -> wait dtklog lock
  Thread B: dtklog lock -> %{function} regex/pcre2/TLS -> wait loader lock

Replace the namespace-scope const globals (UosType, IsServerSystem,
IsCommunitySystem, etc.) with static inline lazy-init helper functions that
compute the value once on first runtime call via function-local statics.
This keeps the "compute once" semantics but moves the DSysInfo calls out of
the dlopen global constructor phase, breaking the loader-lock side of the
deadlock chain.

Affected plugins: sound, power, commoninfo, deepinid.
The helpers use `static inline` to preserve internal linkage (matching the
original const globals) and avoid ODR/symbol collisions across plugins that
share the same utils.h include guard name.

Log: defer DSysInfo queries in plugins to avoid dlopen deadlock

fix: 延迟插件中 DSysInfo 的调用以避免 dlopen 死锁

控制中心启动时通过 QPluginLoader 加载插件。多个插件在全局/静态初始化
(命名空间作用域 const 变量)中调用了 DSysInfo::uosType() 等接口,这些
代码在动态加载器持有 glibc/ld.so 加载锁期间执行。

当 /etc/os-version 缺失或格式异常时,DSysInfo 内部会触发 qWarning(),
进入 dtklog appender 写入流程。此时另一个线程已持有 dtklog 写锁,正在
格式化 %{function},经由 QRegularExpression/pcre2 触发 TLS/加载器初始化,
并等待同一把加载锁,形成经典死锁:

  线程 A:加载锁 -> DSysInfo -> qWarning -> 等待 dtklog 锁
  线程 B:dtklog 锁 -> %{function} regex/pcre2/TLS -> 等待加载锁

将命名空间作用域的 const 全局变量(UosType、IsServerSystem 等)替换为
static inline 懒加载辅助函数,通过函数局部 static 在首次运行时调用时
计算一次。保留"只计算一次"语义,但把 DSysInfo 调用移出 dlopen 全局构造
阶段,打断死锁链中加载锁一侧。

涉及插件:sound、power、commoninfo、deepinid。
辅助函数使用 static inline 保持内部链接(与原 const 全局变量一致),
避免多个共用 utils.h include guard 名的插件之间产生 ODR/符号冲突。

Log: 延迟插件中 DSysInfo 的调用以避免 dlopen 死锁
Change-Id: I1b72cc0a72252730e640997b5481c96f3989343e
@yixinshark yixinshark force-pushed the defer-dsysinfo-plugin-load branch from 3165c9b to dc6e6ca Compare July 9, 2026 05:52
@yixinshark

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Jul 9, 2026

Copy link
Copy Markdown

This pr force merged! (status: blocked)

@deepin-bot deepin-bot Bot merged commit c331437 into linuxdeepin:master Jul 9, 2026
16 of 19 checks passed
@yixinshark

Copy link
Copy Markdown
Contributor Author

Summary

  • Replace namespace-scope const globals (UosType, IsServerSystem, IsCommunitySystem, etc.) in plugin utils.h headers with static inline lazy-init helper functions that compute the value once on first runtime call, moving DSysInfo queries out of the dlopen global constructor phase.
  • Affected plugins: sound, power, commoninfo, deepinid.
  • This breaks one side of a deadlock chain observed in the control center: Thread A holds the glibc/ld.so loader lock during plugin dlopen -> calls DSysInfo -> emits qWarning -> enters dtklog and waits for the dtklog write lock; Thread B holds the dtklog write lock -> formats %{function} via QRegularExpression/pcre2 -> triggers TLS/loader init -> waits for the loader lock.

Background

On systems where /etc/os-version is missing or malformed, DSysInfo::ensureOsVersion() emits qWarning(), which enters the dtklog appender pipeline. When this happens inside a plugin's global/static initialization (during QPluginLoader::load()), the calling thread already holds the dynamic loader lock, creating a lock-order inversion with dtklog.

The companion fixes in dtkcore (DSysInfo::ensureOsVersion no longer logs while holding DSysInfoPrivate::mutex) and dtklog (AbstractStringAppender::qCleanupFuncinfo no longer uses QRegularExpression inside the appender write lock) address the other two links in the chain. This PR addresses the control-center/application side.

Test plan

  • Build all affected plugins (sound, power, commoninfo, deepinid) without errors.
  • Launch dde-control-center on a system with valid /etc/os-version — verify no regression in edition/type detection (server vs community vs professional, etc.).
  • Launch dde-control-center on a system with missing/malformed /etc/os-version — verify no startup hang/deadlock.
  • Verify isServerSystem() / isCommunitySystem() return the same values as the old IsServerSystem / IsCommunitySystem globals on the same system.

Summary by Sourcery

Defer system edition/type detection in control center plugins to avoid DSysInfo calls during plugin global initialization and break the loader/dtklog deadlock chain.

Bug Fixes:

  • Prevent potential startup deadlocks in dde-control-center when DSysInfo logging is triggered during plugin loading on systems with missing or malformed /etc/os-version.

Enhancements:

  • Replace plugin-wide constant globals for UOS/deepin edition and type detection with static inline helper functions that lazily cache DSysInfo query results at first use across sound, power, commoninfo, and deepinid plugins.
  • Align SPDX copyright year ranges to include 2026 in affected plugin headers and sources.

BUG-368917

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