Skip to content

fix: prevent cross-thread model access in notification settings - #1761

Merged
deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
wjyrich:fix-bug-379243
Oct 13, 2026
Merged

deepin-bot[bot] merged 1 commit into
linuxdeepin:masterfrom
wjyrich:fix-bug-379243

Conversation

@wjyrich

@wjyrich wjyrich commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
  1. Add AppModelObserver class that lives in the model's thread and snapshots app items
  2. Replace direct model reads in appItems() with cached snapshots
  3. Use generation counter to invalidate stale snapshots when accessor changes
  4. Coalesce model change signals with a zero-interval timer to avoid redundant refreshes
  5. Publish cache before emitting signals so consumers get consistent data
  6. Register AppItem as a metatype for cross-thread signal delivery
  7. Remove appItemsImpl() and make appItems() return only the cached snapshot
  8. Include moc file for the new Q_OBJECT class in the cpp

Influence:

  1. Verify notification settings display correct app list after startup
  2. Test adding and removing applications while notifications panel is open
  3. Verify app list updates correctly when model is reset or reordered
  4. Test rapid successive model changes to confirm coalescing works without data loss
  5. Verify no crashes or warnings when switching app accessor models
  6. Confirm appItem and appValue queries return correct values immediately after change signals

fix: 修复通知设置中跨线程访问模型的问题

  1. 新增 AppModelObserver 类,运行在模型所在线程并对应用项进行快照
  2. 将 appItems() 中对模型的直接读取替换为缓存快照
  3. 使用代数计数器在访问器变更时使过期快照失效
  4. 使用零间隔定时器合并模型变更信号,避免冗余刷新
  5. 在发出信号前先发布缓存,确保消费者获得一致数据
  6. 注册 AppItem 为元类型以支持跨线程信号传递
  7. 移除 appItemsImpl(),appItems() 仅返回缓存快照
  8. 在 cpp 中包含新 Q_OBJECT 类的 moc 文件

Influence:

  1. 验证启动后通知设置正确显示应用列表
  2. 测试通知面板打开时添加和移除应用
  3. 验证模型重置或重排序时应用列表正确更新
  4. 测试快速连续的模型变更,确认合并机制不丢失数据
  5. 验证切换应用访问器模型时无崩溃或警告
  6. 确认变更信号发出后 appItem 和 appValue 查询立即返回正确值

PMS: BUG-379243 BUG-379237

Summary by Sourcery

Ensure notification settings access application data through consistent, thread-safe snapshots while safely coordinating model monitoring across threads and shutdown.

Bug Fixes:

  • Prevent cross-thread access to the application model used by notification settings.
  • Ensure notification application data remains consistent during model changes and shutdown.

Enhancements:

  • Use thread-safe cached application snapshots for notification queries and publish updates before change signals.
  • Add explicit model-monitoring shutdown coordination to avoid callbacks during worker teardown.

@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR eliminates cross-thread model access by introducing a model-thread snapshot observer, asynchronously coalescing model changes, and delivering generation-validated cached snapshots to NotificationSetting before its consumer signals are emitted.

Sequence diagram for model-thread app snapshot updates

sequenceDiagram
    participant Model as QAbstractItemModel
    participant Observer as AppModelObserver
    participant Setting as NotificationSetting
    participant Consumer as NotificationSettingsConsumer

    Model->>Observer: rowsInserted / rowsRemoved / modelReset / dataChanged
    Observer->>Observer: QTimer::start()
    Note over Observer: Zero-interval timer coalesces model changes
    Observer->>Observer: appItems()
    Observer-->>Setting: snapshotChanged(apps)
    Setting->>Setting: onAppsChanged(current)
    Setting->>Setting: Update m_appItems cache
    Setting-->>Consumer: appAdded / appRemoved
    Consumer->>Setting: appItems() / appItem() / appValue()
Loading

Sequence diagram for accessor replacement and stale snapshot rejection

sequenceDiagram
    participant Caller
    participant Setting as NotificationSetting
    participant OldObserver as OldAppModelObserver
    participant NewModel as NewQAbstractItemModel
    participant NewObserver as NewAppModelObserver

    Caller->>Setting: setAppAccessor(NewModel)
    Setting->>Setting: Increment m_appAccessorGeneration
    Setting->>OldObserver: Retire observer
    Setting->>NewObserver: Create observer
    NewObserver->>NewModel: appItems()
    NewObserver-->>Setting: Initial snapshot
    Setting->>Setting: Publish initial cache
    OldObserver-->>Setting: Queued snapshotChanged(apps)
    Setting->>Setting: Compare snapshot generation
    Setting-->>OldObserver: Ignore stale snapshot
Loading

File-Level Changes

Change Details Files
Move all model reads into a model-thread observer that publishes immutable application snapshots.
  • Add a Q_OBJECT observer parented to the model and connected to structural/data change signals.
  • Build snapshots from model roles and emit them after a zero-interval coalescing timer.
  • Register AppItem and snapshot list types for queued cross-thread delivery.
  • Include the generated moc file for the implementation-local QObject class.
panels/notification/server/notificationsetting.cpp
panels/notification/server/notificationsetting.h
Make NotificationSetting rely exclusively on a mutex-protected snapshot cache for app queries and change notifications.
  • Seed the cache during accessor setup and remove the direct-reading appItemsImpl() path.
  • Publish the new cache before calculating and emitting added/removed/value-related signals.
  • Update appItems() and onAppsChanged() to operate only on cached snapshots.
panels/notification/server/notificationsetting.cpp
panels/notification/server/notificationsetting.h
Guard against stale observer events and accessor lifetime changes.
  • Track accessor generations and ignore queued snapshots from replaced models.
  • Retire observers through a lifetime object and clear the cache when the current model is destroyed.
  • Assert that accessor configuration occurs in the expected model/setting thread relationship.
panels/notification/server/notificationsetting.cpp
panels/notification/server/notificationsetting.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

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: wjyrich

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 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 ✨

@wjyrich

wjyrich commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

/retest

@18202781743

Copy link
Copy Markdown
Contributor

建议缩小这次跨线程修复的改动范围:保留 appItemsImpl(),将所有模型读取放到 model 所属的主线程执行,m_appItems 的读写统一使用现有互斥锁保护即可,不需要阻塞调用主线程。

可以按下面的方式调整:

  1. setAppAccessor() 当前在主线程构造阶段调用,此时先通过 appItemsImpl() 初始化缓存。
  2. 模型发生变化时,让读取和刷新逻辑在 model 所属线程执行。连接需要使用 model 作为执行上下文,或者对只负责读取模型、操作受锁保护缓存的处理明确使用 Qt::DirectConnection;继续以移入工作线程的 setting 为接收上下文并使用默认 AutoConnection,仍会在工作线程读取模型。
  3. onAppsChanged() 可以在模型线程完成:调用 appItemsImpl() 获取当前列表,短暂持有 m_appItemsMutex,取出旧缓存并发布新缓存;释放锁之后比较差异并发送 appAdded/appRemoved。这里无需再把快照交回工作线程处理,发送信号本身支持跨线程,默认连接会将通知排队到工作线程中的接收方。该处理应仅访问模型和受锁保护的缓存,不顺带访问工作线程中的 DConfig 等对象。
  4. appItems() 始终加锁返回缓存,删除“缓存为空时调用 appItemsImpl()”的回退路径。空列表也是有效缓存,不能因此重新从工作线程读取模型。
  5. 补齐模型变化和销毁时的缓存刷新。若使用以 model 为 context、捕获 setting 的 lambda,需要随 setting 生命周期管理连接,避免 setting 销毁后继续使用捕获的 this。

这样保留 appItemsImpl() 和原有缓存逻辑即可解决当前模型跨线程访问。当前业务只在构造时配置一次 accessor;若明确保持这个约定,专门的 AppModelObserver、generation 和 lifetime 对象机制可以精简。

零间隔合并刷新可以保留:应用模型初始化时会逐条插入,合并后能避免每插入一条就全量扫描。modelReset、dataChanged 等刷新覆盖也可以保留;dataChanged 可只响应空 roles(全部变化)或 ID/名称/图标相关 roles。

1. Add stopAppMonitoring() to NotificationSetting and
NotificationManager to disconnect model signals before deletion
2. Use DirectConnection for model signal handlers so onAppsChanged runs
in the model's emitting thread
3. Cache app items lazily in setAppAccessor and update cache atomically
in onAppsChanged
4. Add thread assertions and make appItems() a pure cache reader
5. Call stopAppMonitoring() in NotifyServerApplet destructor before
deleteLater
6. Add NotificationSetting destructor to ensure monitoring is stopped
7. Remove unused QTimer include and update copyright year

Log: Fixed thread-safety issues in notification app monitoring

Influence:
1. Test notification settings app list loading and display
2. Verify app install/uninstall triggers correct appAdded/appRemoved
signals
3. Test notification server shutdown and restart for crashes or hangs
4. Verify app-specific notification settings persist correctly
5. Test with rapid app list changes to check for race conditions
6. Confirm no deadlocks occur during server exit

fix: 修复通知应用监控中的跨线程访问问题

1. 为 NotificationSetting 和 NotificationManager 添加
stopAppMonitoring(),在删除前断开模型信号
2. 模型信号处理器使用 DirectConnection,使 onAppsChanged 在模型发射线程
中运行
3. 在 setAppAccessor 中惰性缓存应用项,并在 onAppsChanged 中原子更新缓存
4. 添加线程断言,使 appItems() 成为纯缓存读取
5. 在 NotifyServerApplet 析构函数中、deleteLater 之前调用
stopAppMonitoring()
6. 添加 NotificationSetting 析构函数以确保停止监控
7. 移除未使用的 QTimer 头文件并更新版权年份

Log: 修复通知应用监控的线程安全问题

Influence:
1. 测试通知设置应用列表的加载和显示
2. 验证应用安装/卸载是否正确触发 appAdded/appRemoved 信号
3. 测试通知服务关闭和重启,检查是否崩溃或卡死
4. 验证应用专属通知设置是否正确持久化
5. 测试应用列表快速变化以检查竞态条件
6. 确认服务退出过程中不会发生死锁

PMS: BUG-379243
@wjyrich

wjyrich commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

/forcemerge

@deepin-bot

deepin-bot Bot commented Oct 13, 2026

Copy link
Copy Markdown

This pr force merged! (status: blocked)

@deepin-bot
deepin-bot Bot merged commit 2a3a80d into linuxdeepin:master Oct 13, 2026
5 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