Skip to content

fix(dfm-search): eliminate cross-thread race on m_strategy causing heap corruption - #375

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
Johnson-zs:master
Aug 4, 2026
Merged

fix(dfm-search): eliminate cross-thread race on m_strategy causing heap corruption#375
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
Johnson-zs:master

Conversation

@Johnson-zs

@Johnson-zs Johnson-zs commented Aug 4, 2026

Copy link
Copy Markdown
Contributor
  • Remove cancelSearch() slot and requestCancel signal so the main thread no longer reads the worker's m_strategy via Qt::DirectConnection (root cause of the SIGBUS heap corruption)
  • Inject the engine-level atomic cancellation flag into strategies via setCancelledFlag(); strategies now read m_cancelledRef instead of a per-strategy flag
  • Drop the redundant BaseSearchStrategy::m_cancelled and enforce mandatory injection with runtime check (not Q_ASSERT, which is stripped in Release)
  • If m_engineCancelled is null in doSearch, emit error and abort instead of proceeding with null m_cancelledRef
  • Add null guards on all m_cancelledRef dereferences in strategies (cancel() and search loops)
  • Reset m_cancelled at the start of doSyncSearch() to match async search(), fixing silent empty results after a cancelled sync search
  • Set m_cancelled on sync search timeout so the worker actually stops

Log: 消除主线程与 worker 线程对 SearchWorker::m_strategy 的无锁竞争(SIGBUS 堆损坏根因),补齐同步搜索取消标志重置,增加 m_cancelledRef 空指针防御
Bug: https://pms.uniontech.com/bug-view-372535.html

Summary by Sourcery

Unify search cancellation around a shared engine-level atomic flag to remove unsafe cross-thread access to strategies and improve cancellation reliability for both async and sync searches.

Bug Fixes:

  • Fix a cross-thread race between the main thread and SearchWorker on m_strategy that could corrupt the heap during cancellation.
  • Ensure synchronous searches reset the cancellation flag before execution and correctly signal cancellation on timeout to avoid silent empty results.

Enhancements:

  • Inject an engine-owned atomic cancellation flag into all search strategies and SearchWorker, and guard against null flags at runtime to prevent undefined behaviour.
  • Adjust all search strategies to consult the injected shared cancellation flag and safely handle null pointers when checking or setting cancellation state.

@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.

Sorry @Johnson-zs, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Johnson-zs

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 Aug 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Refactors dfm-search cancellation to use an engine-level atomic flag injected into strategies, removing cross-thread access to SearchWorker::m_strategy and hardening cancellation handling (including sync searches) with null-guarded pointers and timeout handling.

Sequence diagram for engine-level cancellation flag in dfm-search

sequenceDiagram
    actor User
    participant GenericSearchEngine
    participant SearchWorker
    participant BaseSearchStrategy

    User->>GenericSearchEngine: search(query)
    GenericSearchEngine->>SearchWorker: doSearch(query, options, searchType)
    Note over GenericSearchEngine,SearchWorker: init() already called
    GenericSearchEngine->>SearchWorker: setEngineCancelledFlag(&m_cancelled)

    SearchWorker->>BaseSearchStrategy: setCancelledFlag(m_engineCancelled)
    SearchWorker->>BaseSearchStrategy: search(query)

    User->>GenericSearchEngine: cancel()
    GenericSearchEngine->>GenericSearchEngine: m_cancelled.store(true)

    loop search loop
        BaseSearchStrategy->>BaseSearchStrategy: m_cancelledRef->load()
        alt [cancel flag is true]
            BaseSearchStrategy-->>SearchWorker: searchFinished(results)
        end
    end
Loading

File-Level Changes

Change Details Files
Introduce engine-level atomic cancellation flag and inject it into all search strategies instead of per-strategy atomics.
  • Add std::atomic* m_cancelledRef to BaseSearchStrategy and a setCancelledFlag() injector with runtime null check and warning.
  • Replace all direct uses of per-strategy m_cancelled with reads/writes through m_cancelledRef and guard dereferences with null checks in search loops and cancel() implementations.
  • Update SearchCancellationGuard and CancellableCollector call sites to use the injected m_cancelledRef pointer rather than an internal atomic instance.
src/dfm-search/dfm-search-lib/core/searchstrategy/basesearchstrategy.h
src/dfm-search/dfm-search-lib/contentsearch/contentstrategies/indexedstrategy.cpp
src/dfm-search/dfm-search-lib/ocrtextsearch/ocrtextstrategies/indexedstrategy.cpp
src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/indexedstrategy.cpp
src/dfm-search/dfm-search-lib/filenamesearch/filenamestrategies/realtimestrategy.cpp
src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp
Remove direct main-thread-to-worker cancellation signal path and instead share the engine cancellation flag with the worker and strategies.
  • Remove GenericSearchEngine::requestCancel signal and SearchWorker::cancelSearch slot and their Qt::DirectConnection wiring.
  • Add SearchWorker::setEngineCancelledFlag() to store a pointer to the engine-level atomic flag.
  • In GenericSearchEngine::init(), inject &m_cancelled into the worker so strategies receive the same flag via SearchWorker::doSearch().
src/dfm-search/dfm-search-lib/core/genericsearchengine.cpp
src/dfm-search/dfm-search-lib/core/genericsearchengine.h
src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp
src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.h
Harden worker search startup against missing cancellation flag and improve sync search cancellation semantics, including timeout handling.
  • In SearchWorker::doSearch(), abort the search with an InternalError if m_engineCancelled is null and emit a warning instead of proceeding.
  • In GenericSearchEngine::doSyncSearch(), reset m_cancelled to false before starting a sync search to avoid reusing a previous cancelled state.
  • On sync search timeout in doSyncSearch(), replace emitting requestCancel with setting m_cancelled to true so the worker stops via the shared flag.
src/dfm-search/dfm-search-lib/core/searchstrategy/searchworker.cpp
src/dfm-search/dfm-search-lib/core/genericsearchengine.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

…ap corruption

- Remove cancelSearch() slot and requestCancel signal so the main thread no longer reads the worker's m_strategy via Qt::DirectConnection (root cause of the SIGBUS heap corruption)
- Inject the engine-level atomic cancellation flag into strategies via setCancelledFlag(); strategies now read m_cancelledRef instead of a per-strategy flag
- Drop the redundant BaseSearchStrategy::m_cancelled and enforce mandatory injection with runtime check (not Q_ASSERT, which is stripped in Release)
- If m_engineCancelled is null in doSearch, emit error and abort instead of proceeding with null m_cancelledRef
- Add null guards on all m_cancelledRef dereferences in strategies (cancel() and search loops)
- Reset m_cancelled at the start of doSyncSearch() to match async search(), fixing silent empty results after a cancelled sync search
- Set m_cancelled on sync search timeout so the worker actually stops

Log: 消除主线程与 worker 线程对 SearchWorker::m_strategy 的无锁竞争(SIGBUS 堆损坏根因),补齐同步搜索取消标志重置,增加 m_cancelledRef 空指针防御
Bug: https://pms.uniontech.com/bug-view-372535.html
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

★ 总体评分:95分

■ 【总体评价】

代码彻底重构了多线程搜索的取消机制,通过共享原子指针替代跨线程信号槽,完美解决了空指针解引用崩溃与状态残留问题。
逻辑严密且防御性编程到位,无任何安全漏洞,因少量重复判断代码扣5分。

■ 【详细分析】

  • 1.语法逻辑(完全正确)✓

代码将各策略内部的局部原子变量提升为引擎层共享指针,在 GenericSearchEngine::doSyncSearch 中增加了 m_cancelled.store(false) 重置状态,彻底解决了多次搜索间的状态残留问题。移除了 Qt::DirectConnection 的跨线程信号 requestCancel,消除了死锁与竞态条件风险。在 SearchWorker::doSearchContentIndexedStrategy::performContentSearch 等多处增加了严格的空指针拦截,确保底层 SearchCancellationGuardCancellableCollector 不会解引用空指针引发 SIGSEGV。
建议:无

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

注释详尽且切中要害,如明确解释了不使用 Q_ASSERT 是因为 Release 构建会将其移除,体现了深厚的工程经验。引擎、工作线程、具体策略三者的职责划分非常清晰,取消标志的注入与读取路径单一且明确。
潜在问题:在 FileNameRealTimeStrategyRecentSearchStrategy 等多个派生类的循环中,存在大量重复的 m_cancelledRef && m_cancelledRef->load() 判断模式,增加了代码冗余。
建议:在 BaseSearchStrategy 基类中提供一个 inline bool isCancelled() const { return m_cancelledRef && m_cancelledRef->load(); } 方法,派生类统一调用此方法以消除重复代码。

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

去除了跨线程的信号槽发射与事件队列投递开销,改为直接对共享的 std::atomic<bool> 进行无锁读写,工作线程能够以极低的延迟感知到主线程的取消请求,减少了不必要的系统调用和线程上下文切换。
建议:无

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

漏洞对比统计:新增漏洞 0 个,减少漏洞 1 个,持平 0 个
总体风险描述:本次修改成功消除了原有机制中底层组件解引用空指针导致 SIGSEGV 崩溃的风险,未引入命令注入、路径遍历或内存泄漏等新的安全漏洞,整体安全性极高。
建议:无

■ 【改进建议代码示例】

diff --git a/src/dfm-search/dfm-search-lib/core/searchstrategy/basesearchstrategy.h b/src/dfm-search/dfm-search-lib/core/searchstrategy/basesearchstrategy.h
index 205e36fd..8a2c1f91 100644
--- a/src/dfm-search/dfm-search-lib/core/searchstrategy/basesearchstrategy.h
+++ b/src/dfm-search/dfm-search-lib/core/searchstrategy/basesearchstrategy.h
@@ -89,6 +89,12 @@ protected:
     SearchResultList m_results;
     std::atomic<bool> *m_cancelledRef { nullptr };
+
+    /**
+     * @brief 安全读取取消标志,封装空指针校验逻辑供派生类统一调用
+     */
+    inline bool isCancelled() const {
+        return m_cancelledRef && m_cancelledRef->load();
+    }
 };
 
 DFM_SEARCH_END_NS
diff --git a/src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp b/src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp
index 8aa452bc..d4f2c1e3 100644
--- a/src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp
+++ b/src/dfm-search/dfm-search-lib/recentsearch/recentstrategies/recentsearchstrategy.cpp
@@ -202,7 +202,7 @@ void RecentSearchStrategy::search(const SearchQuery &query)
     // Step 1: 从 DBus 拉取全部最近使用记录
     QList<RecentItem> items = fetchRecentItems();
 
-    if (m_cancelledRef && m_cancelledRef->load()) {
+    if (isCancelled()) {
         emit searchFinished(m_results);
         return;
     }
@@ -236,7 +236,7 @@ void RecentSearchStrategy::search(const SearchQuery &query)
 
     int count = 0;
     for (const RecentItem &item : std::as_const(items)) {
-        if ((m_cancelledRef && m_cancelledRef->load()) || count >= maxResults) {
+        if (isCancelled() || count >= maxResults) {
             break;
         }
 

@Johnson-zs

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Aug 4, 2026

Copy link
Copy Markdown

This pr force merged! (status: blocked)

@deepin-bot
deepin-bot Bot merged commit 0b86533 into linuxdeepin:master Aug 4, 2026
20 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.

3 participants