-
Notifications
You must be signed in to change notification settings - Fork 90
fix: Refactor DConfig wrapper class generation for thread safety and … #531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: Dami-star The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR refactors the DConfig C++ wrapper class generation tool (dconfig2cpp) to improve thread safety and lifecycle management. The refactor introduces a data layer separation pattern, enhances the state machine from 3 states to 5 states, and implements atomic operations for thread-safe state transitions.
Key changes:
- Introduces a Data class (TreelandUserConfigData) to separate internal data management from the public API (TreelandUserConfig)
- Expands state machine from Invalid/Succeed/Failed to Invalid/Initializing/Succeed/Failed/Destroyed with atomic CAS operations
- Separates thread responsibilities: updateValue() in worker thread, updateProperty() in main thread with QMetaObject::invokeMethod for proper thread context
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
zccrs
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
还没有看完
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| m_data->deleteLater(); | ||
| } | ||
| } else if (oldStatus == static_cast<int>(Data::Status::Failed) || oldStatus == static_cast<int>(Data::Status::Invalid)) { | ||
| m_data->deleteLater(); |
Copilot
AI
Jan 14, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The destructor has a potential memory leak scenario. When oldStatus is Initializing, m_data is not deleted. This can occur if the destructor is called while initialization is still in progress on the worker thread. The code should handle the Initializing state by marking it as Destroyed and allowing the worker thread to clean up, or explicitly delete m_data.
| m_data->deleteLater(); | |
| m_data->deleteLater(); | |
| } else if (oldStatus == static_cast<int>(Data::Status::Initializing)) { | |
| m_data->deleteLater(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
当 oldStatus 为 Initializing 时,我们不会在此处删除, 若在此处删除会导致空指针。工作线程将检测到 Destroyed 状态并自行清理 m_data。
| Q_ASSERT(!m_config.loadRelaxed()); | ||
| m_config.storeRelaxed(config); | ||
| if (testPropertySet(0)) config->setValue(QStringLiteral("array"), QVariant::fromValue(p_array)); | ||
| else updateValue(QStringLiteral("array"), QVariant::fromValue(p_array)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
之前生成的里面存在一个问题,当已经在initializeInConfigThread函数中执行时,这时候dconfig_dconf-example_meta被调用方被释放了,会存在访问config变量是野指针的问题,目前这里还是会遇到吧,重构的时候可以考虑这个bug,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
帮忙用最新代码测试下,另外linuxdeepin/treeland#680 有详细的测试用例
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
测试不是很好复现,配置项比较多的还可能容易点儿,看代码还有点问题,
在initializeInConfigThread保存了config变量这个,但这个变量会跟随dconfig_dconf-example_meta一起析构,后面继续访问config,还是会存在野指针问题吧,
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
deepin pr auto review代码审查报告整体评估这是一份由 主要改进点1. 架构设计
2. 线程安全
3. 内存管理
需要改进的地方1. 代码质量
2. 性能优化
3. 安全性改进
4. 其他建议
具体代码示例改进示例1:属性访问优化// 当前实现
bool autoDisplayFeature() const { return m_data->p_autoDisplayFeature; }
// 改进后
bool autoDisplayFeature() const {
return m_data->p_autoDisplayFeature.loadRelaxed();
}改进示例2:信号发射优化// 当前实现
Q_EMIT autoDisplayFeatureChanged();
Q_EMIT valueChanged(QStringLiteral("autoDisplayFeature"), v);
// 改进后
Q_EMIT autoDisplayFeatureChanged();
// 仅在需要时发射通用信号
if (m_emitGenericSignals) {
Q_EMIT valueChanged(QStringLiteral("autoDisplayFeature"), v);
}改进示例3:添加类型安全检查// 当前实现
bool nv = qvariant_cast<bool>(v);
// 改进后
bool nv = false;
if (v.canConvert<bool>()) {
nv = v.toBool();
} else {
qWarning() << "Invalid type for property" << key;
return;
}总结这份代码整体质量良好,特别是在线程安全和内存管理方面做了很多改进。主要改进方向是减少重复代码、提高性能和增强安全性。建议按照上述建议逐步改进,同时保持代码的向后兼容性。 |
…lifecycle management Problem: - Single-threaded design with weak state machine (Invalid -> Succeed/Failed) - No proper handling of object destruction during initialization - Signal emissions in worker thread context (incorrect thread context) - Fragile destructor unable to handle all cleanup scenarios Solution: 1. Introduce Data layer separation (TreelandUserConfigData + TreelandUserConfig) - Clear separation between internal data management and public API - Enables safer object lifecycle management 2. Enhance state machine (3-state -> 5-state model) - Add Initializing and Destroyed states - Use atomic CAS operations for thread-safe state transitions - States: Invalid -> Initializing -> (Succeed | Failed | Destroyed) 3. Improve async initialization and cleanup - Use QPointer for safe backref checks (prevent use-after-free) - Support 4 destruction paths: normal/failed/quick/mid-initialization - Atomic state transitions with proper signal emission guards 4. Separate thread responsibilities - updateValue(): Worker thread reads config values - updateProperty(): Main thread updates properties and emits signals - Use QMetaObject::invokeMethod for correct thread context Improvements: - Thread safety: Complete atomic operations coverage - Memory safety: QPointer guards prevent dangling pointers - Code clarity: Layered architecture with clear responsibilities - Backward compatibility: API unchanged
Synchronize source files from linuxdeepin/dtkcore. Source-pull-request: linuxdeepin/dtkcore#531
| class dconfig_org_deepin_dtk_preference : public QObject { | ||
| Q_OBJECT | ||
|
|
||
| public: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个改动不应该存在
| int oldStatus = m_data->m_status.fetchAndStoreOrdered(static_cast<int>(Data::Status::Destroyed)); | ||
|
|
||
| if (oldStatus == static_cast<int>(Data::Status::Succeeded)) { | ||
| if (auto config = m_data->m_config.loadRelaxed()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
对 m_config 应该是个断言,如果状态为 Succeeded,不应该出现它为空的情况。
| static dconfig_org_deepin_dtk_preference* createGenericByName(DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread()) | ||
| { return new dconfig_org_deepin_dtk_preference(thread, backend, name, {}, subpath, true, parent); } | ||
|
|
||
| ~dconfig_org_deepin_dtk_preference() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个析构里不要销毁 data 数据,只负责在Succeeded状态下释放config对象。
| QMetaObject::invokeMethod(worker, [=, this]() { | ||
|
|
||
| // Use QPointer to safely check parent existence in worker thread | ||
| QPointer<dconfig_org_deepin_dtk_preference> safeThis(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
不需要这个对象,只需要用一个QPointer保存 Data 对象即可
| // Atomically transition from Invalid to Initializing | ||
| if (!data->m_status.testAndSetOrdered(static_cast<int>(Data::Status::Invalid), | ||
| static_cast<int>(Data::Status::Initializing))) { | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
返回之前把 data->deleteLater 掉
| explicit Data() | ||
| : QObject(nullptr) {} | ||
|
|
||
| inline void markPropertySet(const int index, bool on = true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
为什么不保持相对顺序呢,应该把 initializeInConfigThread 放为第一个函数
markPropertySet 这些是在后面
| } else { | ||
| updateValue(QStringLiteral("underlineShortcut"), QVariant::fromValue(p_underlineShortcut)); | ||
|
|
||
| inline void updateValue(const QString &key, const QVariant &fallback = QVariant()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个为什么相对于原实现改了那么多?
| updateValue(QStringLiteral("underlineShortcut"), QVariant::fromValue(p_underlineShortcut)); | ||
| } | ||
| // Transition from Initializing to Succeeded | ||
| if (!m_status.testAndSetOrdered(static_cast<int>(Status::Initializing), static_cast<int>(Status::Succeeded))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
把状态的设置放到外面做(紧接着initializeInConfigThread调用之后),不在这个函数体里
| // Transition from Initializing to Succeeded | ||
| if (!m_status.testAndSetOrdered(static_cast<int>(Status::Initializing), static_cast<int>(Status::Succeeded))) { | ||
| // Validation failed, likely Destroyed | ||
| if (m_status.loadRelaxed() == static_cast<int>(Status::Destroyed)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
???前面都已经把状态设置为 Succeeded了,这里再判断还有意义吗,应该一开始在 testAndSetOrdered 中就明确判断是否为 Destroyed 状态,是的话不仅要销毁 config, 还要销毁data对象。
|
|
||
| // Value changed connection | ||
| // We use context object 'data' to ensure safety. | ||
| QObject::connect(config, &DTK_CORE_NAMESPACE::DConfig::valueChanged, data, [data](const QString &key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个信号链接应该放到 initializeInConfigThread 中进行。
| @@ -1 +1 @@ | |||
| // SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
日期要更新了
…lifecycle management
Problem:
Solution:
Introduce Data layer separation (TreelandUserConfigData + TreelandUserConfig)
Enhance state machine (3-state -> 5-state model)
Improve async initialization and cleanup
Separate thread responsibilities
Improvements: