Skip to content

refactor(dde-dconfig-daemon): P0/P1/P2 全量重构——连接管理、同步策略、热重载、服务框架、安全、路径管理、CLI、测试#145

Closed
18202781743 wants to merge 7 commits into
linuxdeepin:masterfrom
18202781743:refactor/all-in-one
Closed

refactor(dde-dconfig-daemon): P0/P1/P2 全量重构——连接管理、同步策略、热重载、服务框架、安全、路径管理、CLI、测试#145
18202781743 wants to merge 7 commits into
linuxdeepin:masterfrom
18202781743:refactor/all-in-one

Conversation

@18202781743
Copy link
Copy Markdown
Contributor

@18202781743 18202781743 commented Mar 7, 2026

概述

本 PR 是对 dde-dconfig-daemon 的系统性重构,按 P0→P1→P2 优先级分阶段实现,所有改动已整合到一个分支,编译通过。


P0:核心稳定性

T04 — 连接管理与引用计数优化

  • 引入 ConnKey 类型别名,统一连接标识格式
  • DSGConfigRefManager 改为全局单定时器(60s)+ QHash 存储,降低定时器开销
  • 守护进程退出时检测连接泄漏,输出 [leak] 警告日志

T05 — 写盘策略重构

  • 抽象 ConfigSyncPolicy 接口(sync() / flush()
  • 实现 DelayedSyncPolicy:500ms 防抖延迟 + 最大 5s 强制写盘上限
  • 失败自动重试(最多 3 次)
  • 退出前 flush() 确保数据落地

T06 — inotify 驱动热重载

  • 新增 InotifyWatcher 类,替代旧的文件签名轮询方案
  • 200ms 节流定时器批量处理文件变化
  • 发出 configUpdated(path) 信号触发 reload,减少无效 I/O

P1:服务框架现代化

T01 — 服务生命周期 & 信号处理

  • 新增 ServiceLifecycle 状态机(Initializing→Running→Stopping→Stopped)
  • main.cpp 使用 socketpair 桥接 POSIX 信号(SIGTERM/SIGINT)到 Qt 事件,async-signal-safe
  • 统一日志格式:QT_MESSAGE_PATTERN 含时间戳 + PID + category

T07 — 安全增强

  • removeUserData() 添加 [AUDIT] 级别日志,记录 uid/caller/pid
  • acquireManagerV2() 添加 uid 校验,非 root 调用方不能跨 uid 访问

T03 — 路径管理

  • 新增 ConfigPathResolver 单例,支持优先级排序
  • initialize() 通过 resolver 统一注册路径(etc=200 > usr=100 > linglong=50)

P2:工具增强与测试

T09 — CLI 增强(dde-dconfig

  • --output json|text:list/get 支持 JSON 输出
  • watch 命令支持 JSON 格式(含 key/appid/resource)
  • 新增 export 子命令:将全量配置值导出为 JSON 快照文件
  • 新增 import 子命令:从 JSON 快照批量导入配置值

T08 — 配置格式扩展

  • value():检测 description 中的 [deprecated] 标记,发出 qCWarning
  • setValue():解析 description 中的 [schema:{type,min,max}] 标记,类型/范围校验失败返回 org.desktopspec.ConfigManager.InvalidValue

T11 — 单元测试覆盖率

  • 新增 ut_dconfigresource.cpp:覆盖 ServiceLifecycle 状态机转换、ConfigPathResolver 优先级/去重/路径候选、reparse 调用
  • 新增 .github/workflows/test.yml:CI 自动构建 + ctest + gcovr 覆盖率报告

测试方法

cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build -j4
cd build && ctest --output-on-failure

文件变更

文件 说明
dconfigserver.cpp/h 生命周期、inotify、路径 resolver、安全校验
dconfigconn.cpp deprecated 警告、schema 校验
configsyncpolicy.h 新增写盘策略抽象
inotifywatcher.cpp/h 新增 inotify 封装
servicelifecycle.cpp/h 新增状态机
configpathresolver.cpp/h 新增路径 resolver 单例
dde-dconfig/main.cpp CLI 增强(JSON输出/export/import)
tests/ut_dconfigresource.cpp 新增单元测试
.github/workflows/test.yml CI 配置

Summary by Sourcery

Refactor dde-dconfig-daemon’s core service, sync pipeline, and CLI to improve reliability, observability, and configurability while adding JSON-based tooling and tests.

New Features:

  • Add JSON output support to dde-dconfig list/get/watch commands and introduce export/import subcommands for JSON configuration snapshots.
  • Introduce an InotifyWatcher abstraction and ConfigPathResolver singleton to manage config directories, enabling event-driven hot reload based on filesystem changes.
  • Add a ServiceLifecycle state machine and structured signal-bridge shutdown flow for the daemon, with a unified logging format.
  • Implement schema- and deprecation-aware configuration handling based on metadata annotations to validate values and surface deprecation warnings at runtime.

Enhancements:

  • Replace per-connection QTimer-based reference release with a global timer and pending-release queue to reduce timer overhead and improve connection cleanup behavior, including leak logging on exit.
  • Ensure pending configuration writes are flushed on daemon shutdown and add retry with backoff when saving caches, improving durability of config changes.
  • Harden security by auditing removeUserData calls with caller context and enforcing uid matching in acquireManagerV2 so non-root clients cannot access other users’ configs.
  • Simplify and harden config path handling by centralizing search/override logic in ConfigPathResolver and using hash-based containers for resources, services, caches, and connections.
  • Modernize helper functions by making them inline and adjusting types/format specifiers for better performance and correctness of logging.

CI:

  • Add a GitHub Actions workflow to build, run unit tests under dbus-run-session, and generate/upload code coverage reports via gcovr.

Tests:

  • Add unit tests for DSGConfigResource, ConfigPathResolver, and ServiceLifecycle, including lifecycle transition validation and basic reparse behavior.

Changed all static function declarations to inline in helper.hpp to
resolve multiple definition warnings during linking. This ensures each
translation unit gets its own copy of the functions while maintaining
the same behavior.

Updated debug logging format specifiers from %d to %lld in multiple
files to properly handle qint64 count values, preventing format string
warnings.

Changed range-based for loops to use const references instead of copies
for QString objects to improve performance and avoid implicit copying
warnings.

These changes eliminate compilation warnings without affecting
functionality, ensuring cleaner build output and better code quality.

Influence:
1. Verify that all helper functions still work correctly after changing
from static to inline
2. Test debug logging functionality to ensure new format specifiers work
with qint64 values
3. Validate that range-based for loops with const references behave
identically to previous value copies
4. Ensure no linking errors occur due to multiple definitions
5. Confirm that all existing functionality remains intact after these
changes

fix: 将静态函数改为内联函数以解决编译警告

将 helper.hpp 中的所有静态函数声明改为内联函数,以解决链接时的多重定义警
告。这确保每个翻译单元获得自己的函数副本,同时保持相同的行为。

在多个文件中将调试日志格式说明符从 %d 更改为 %lld,以正确处理 qint64 计
数类型,防止格式字符串警告。

将基于范围的 for 循环改为使用常量引用而不是 QString 对象的副本,以提高性
能并避免隐式复制警告。

这些更改消除了编译警告而不影响功能,确保更清晰的构建输出和更好的代码
质量。

Influence:
1. 验证将静态函数改为内联后,所有辅助函数是否仍能正常工作
2. 测试调试日志功能,确保新的格式说明符适用于 qint64 值
3. 验证使用常量引用的基于范围的 for 循环是否与先前的值复制行为相同
4. 确保不会因多重定义而发生链接错误
5. 确认这些更改后所有现有功能保持完整

Change-Id: I1477fcfd4ee2af683910a4307a4606226ea4e238
T04-1: 新增 dconfig_types.h,定义类型安全 ConnKey 结构体(toString/fromString/qHash)
T04-2: RefManager 延迟释放改为单一全局定时器 + QHash<ConnKey,qint64> 到期队列
T04-3: m_resources/m_files/m_caches/m_conns 全部由 QMap 改为 QHash
T04-4: DSGConfigServer::exit() 添加连接泄漏检测 warning
Change-Id: If57d68a44f6ab48484e1b05e57414934bf3e9fce
T05-1: 新增 configsyncpolicy.h,抽象 ConfigSyncPolicy 接口(schedule/flush/clear)
T05-2: ConfigSyncRequestCache 继承 ConfigSyncPolicy,实现 schedule/flush
T05-3: DSGConfigResource::saveWithRetry() 最多3次重试,指数退避100ms
T05-4: DSGConfigServer::exit() 开头调用 flush() 确保退出前写盘
Change-Id: Icffe0acc35dfdaaff3da1e073aaf0f8741e7f298
T06-1: 新增 inotifywatcher.h/.cpp,封装 Linux inotify 监听文件变化
T06-2: DSGConfigServer::initialize() 改为 InotifyWatcher 监听三个配置目录
        移除 allConfigureFileSignatures() 轮询方案和 m_fileSignatures 成员
T06-3: 新增 m_reloadThrottle(200ms 节流)+ m_pendingReloadPaths 队列
T06-4: update() 成功后 emit configUpdated(appid, resource) 信号
       reload() 改为手动触发节流队列立即刷出
Change-Id: Ie16aeeba93e1c03843754ed5af06a2916aab8e33
T01-1: 新增 ServiceLifecycle 状态机(Initializing→Running→Stopping→Stopped)
T01-2: main.cpp 使用 socketpair 桥接 POSIX 信号到 Qt 事件,SIGTERM 有序关闭
T01-3: 启动时设置 QT_MESSAGE_PATTERN(时间戳+pid+category),支持运行时 setLogRules
T07-1: removeUserData() 添加 AUDIT 级别日志(uid/caller/pid)
T07-2: acquireManagerV2() 添加 uid 校验,非 root 只能访问自己的数据
T03-1: 新增 ConfigPathResolver 单例,支持优先级路径管理(metaPaths/overridePaths)
T03-2: initialize() 使用 ConfigPathResolver 统一注册配置搜索路径
Change-Id: I60a1bb87ea39f91627ac95407765727e9910b2c2
T09-1: dde-dconfig --output json|text,list/get 支持 JSON 输出
T09-2: watchCommand 支持 --output json,输出 {key,appid,resource} 对象
T09-3: 新增 export/import 子命令,快照导出/导入全量配置值到 JSON 文件
T08-1: DSGConfigConn::value() 检测 [deprecated] 描述标记,发出 qCWarning
T08-2: DSGConfigConn::setValue() 解析 [schema:{...}] 描述,校验 type/min/max
        校验失败返回 org.desktopspec.ConfigManager.InvalidValue D-Bus error
T11-1: 新增 ut_dconfigresource.cpp,覆盖 reparse/ServiceLifecycle/ConfigPathResolver
T11-3: 新增 .github/workflows/test.yml,CI 自动构建+测试+覆盖率报告
Change-Id: I7a870ae12078d0eb3ed8423f7e1169cf8514fb8a
T06 已用 InotifyWatcher 替代文件签名轮询,T03 cherry-pick 时
意外带入了 m_fileSignatures 旧代码,导致编译错误,现清理。

Change-Id: I2e039ddfb20669e4363288845ba548c4ea459e80
@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 7, 2026

  • 检测到敏感词export, unset变动
详情
    {
    "export": {
        "dconfig-center/dde-dconfig/main.cpp": {
            "b": [
                "    int exportCommand();   // T09-3: export snapshot",
                "    QString snapshotFile;  // T09-3: file path for export/import",
                "// T09-3: export snapshot",
                "int CommandManager::exportCommand()",
                "        outpuSTDError(\"export requires -a <appid> -r <resource> -o <file>\");",
                "        outpuSTDError(\"export requires --file/-o <snapshot.json>\");",
                "    // T09-3: --file for export/import",
                "        QCoreApplication::translate(\"main\", \"snapshot file path for export/import.\"), \"file\", QString());",
                "    QCommandLineOption exportOption(\"export\", QCoreApplication::translate(\"main\", \"export all config values to a JSON snapshot file.\"));",
                "    exportOption.setFlags(exportOption.flags() ^ QCommandLineOption::HiddenFromHelp);",
                "    parser.addOption(exportOption);",
                "    } else if (parser.isSet(exportOption) || exportOption.names().contains(subcommand)) {",
                "        return manager.exportCommand();"
            ]
        }
    },
    "unset": {
        "dconfig-center/tests/ut_dconfigresource.cpp": {
            "b": [
                "        qunsetenv(\"DSG_CONFIG_CONNECTION_DISABLE_DBUS\");",
                "        qunsetenv(\"STATE_DIRECTORY\");"
            ]
        }
    }
}

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 7, 2026

Reviewer's Guide

Systematic refactor of dde-dconfig-daemon: introduces typed connection keys and a global ref manager timer, pluggable config sync policy with flush-on-exit and retry, inotify-based hot reload plus centralized path resolution, modernized daemon lifecycle with signal bridging and unified logging, strengthened security checks and audit logs, CLI JSON/export/import enhancements, schema/deprecated metadata handling, and new unit tests plus CI coverage workflow.

Sequence diagram for POSIX signal handling and ordered shutdown

sequenceDiagram
    participant OS
    participant Kernel
    participant Daemon as dde_dconfig_daemon
    participant SigHandler as posixSignalHandler
    participant Socket as signal_socketpair
    participant Notifier as QSocketNotifier
    participant Lifecycle as ServiceLifecycle
    participant Server as DSGConfigServer
    participant ConfigSync
    participant RefManager

    OS->>Kernel: send SIGTERM/SIGINT
    Kernel-->>SigHandler: deliver signal
    SigHandler->>Socket: write(sig_byte)

    Socket-->>Notifier: fd becomes readable
    Notifier->>Daemon: activated(read)
    Daemon->>Socket: read(sig_byte)
    Daemon->>Lifecycle: transitionTo(Stopping)
    Daemon->>Server: exit()
    Server->>Server: log connection leaks
    alt has m_syncRequestCache
        Server->>ConfigSync: flush()
        ConfigSync-->>Server: all pending writes flushed
    end
    Server->>RefManager: destroy()
    Server->>Server: release resources
    Daemon->>Lifecycle: transitionTo(Stopped)
    Daemon->>Daemon: QCoreApplication::quit()
Loading

Sequence diagram for inotify-based hot reload and manual reload

sequenceDiagram
    participant FS as Filesystem
    participant Inotify as InotifyWatcher
    participant Server as DSGConfigServer
    participant Throttle as reloadThrottle(QTimer)

    FS->>Inotify: file change in config dir
    Inotify->>Server: fileChanged(path)
    alt path endsWith(".json")
        Server->>Server: enqueue path into m_pendingReloadPaths
        Server->>Throttle: start(200ms)
    end

    Throttle-->>Server: timeout()
    loop while m_pendingReloadPaths not empty
        Server->>Server: dequeue path
        Server->>Server: update(path)
        alt update succeeds
            Server-->>Clients: configUpdated(appid, resource)
        else update fails
            Server-->>DBusCaller: sendErrorReply()
        end
    end

    Note over Client,Server: Manual reload() call
    Client->>Server: reload()
    Server->>Throttle: stop()
    loop while m_pendingReloadPaths not empty
        Server->>Server: dequeue path
        Server->>Server: update(path)
    end
Loading

Updated class diagram for core daemon components

classDiagram
    class ConnKey {
        +QString appid
        +QString resource
        +QString subpath
        +uint uid
        +QString toString()
        +static ConnKey fromString(s: QString)
        +bool isValid()
        +bool operator==(o: ConnKey)
        +bool operator!=(o: ConnKey)
    }

    class ServiceLifecycle {
        -ServiceState m_state
        +ServiceLifecycle(parent: QObject*)
        +ServiceState state()
        +bool isRunning()
        +bool isStopping()
        +bool transitionTo(next: ServiceState)
        +static const char* stateName(s: ServiceState)
        +signal stateChanged(from: ServiceState, to: ServiceState)
    }

    class ConfigSyncPolicy {
        <<abstract>>
        +ConfigSyncPolicy(parent: QObject*)
        +void schedule(key: ConfigCacheKey)*
        +void flush()*
        +void clear()*
        +signal syncConfigRequest(request: ConfigSyncBatchRequest)
    }

    class ConfigSyncRequestCache {
        -QBasicTimer* m_syncTimer
        -int m_delaySyncTime
        -int m_batchCount
        -QList~ConfigCacheKey~ m_configCacheKeys
        +ConfigSyncRequestCache(parent: QObject*)
        +~ConfigSyncRequestCache()
        +void schedule(key: ConfigCacheKey)
        +void flush()
        +void clear()
        +void pushRequest(key: ConfigCacheKey)
        -void customRequest()
    }

    class RefManager {
        -QHash~ConnServiceName, ServiceRef*~ services
        -QHash~ConnKey, ResourceRef*~ resources
        -int m_delayReleaseTime
        -QTimer* m_globalReleaseTimer
        -QHash~ConnKey, qint64~ m_pendingRelease
        +RefManager(parent: QObject*)
        +~RefManager()
        +void destroy()
        +void setDelayReleaseTime(ms: int)
        +void delayDeleteResource(deleteResources: QList~ResourceRef*~)
        -void doDeleteResource(deleteResources: QList~ResourceRef*~)
    }

    class InotifyWatcher {
        -int m_fd
        -QSocketNotifier* m_notifier
        -QHash~int, QString~ m_wdToPath
        -QHash~QString, int~ m_pathToWd
        +InotifyWatcher(parent: QObject*)
        +~InotifyWatcher()
        +void addPath(path: QString)
        +void removePath(path: QString)
        +signal fileChanged(path: QString)
        +signal directoryChanged(path: QString)
        -void addWatchRecursive(path: QString)
        -void onInotifyEvent()
    }

    class ConfigPathResolver {
        -QString m_localPrefix
        -QList~QPair~int, QString~~ m_paths
        +static ConfigPathResolver& instance()
        +void setLocalPrefix(prefix: QString)
        +QString localPrefix() const
        +void addSearchPath(path: QString, priority: int)
        +void clearSearchPaths()
        +QStringList searchPaths() const
        +QStringList metaPaths(appid: QString, resource: QString) const
        +QStringList overridePaths(appid: QString, resource: QString) const
        +QStringList allWatchedDirs() const
    }

    class DSGConfigResource {
        -QString m_key
        -QString m_appid
        -QString m_subpath
        -QString m_localPrefix
        -QHash~ResourceKey, DConfigFile*~ m_files
        -QHash~ConnKey, DConfigCache*~ m_caches
        -QHash~ConnKey, DSGConfigConn*~ m_conns
        -ConfigSyncRequestCache* m_syncRequestCache
        +void save()
        +void save(appid: QString)
        +bool saveWithRetry(cache: DConfigCache*, maxRetry: int)
        +void removeConn(connKey: ConnKey)
        +bool isEmptyConn() const
    }

    class DSGConfigConn {
        +void setValue(key: QString, value: QDBusVariant)
        +QDBusVariant value(key: QString)
        -bool hasPermissionByUid(key: QString) const
        -DConfigMeta* meta() const
    }

    class DSGConfigServer {
        -QHash~GenericResourceKey, DSGConfigResource*~ m_resources
        -RefManager* m_refManager
        -ConfigSyncRequestCache* m_syncRequestCache
        -InotifyWatcher* m_inotifyWatcher
        -QTimer* m_reloadThrottle
        -QQueue~QString~ m_pendingReloadPaths
        -QString m_localPrefix
        +DSGConfigServer(QObject* parent)
        +~DSGConfigServer()
        +bool registerService()
        +void initialize()
        +void exit()
        +void reload()
        +void update(path: QString)
        +void removeUserData(uid: uint)
        +QDBusObjectPath acquireManagerV2(uid: uint, appid: QString, name: QString, subpath: QString)
        +signal configUpdated(appid: QString, resource: QString)
    }

    ConnKey <.. DSGConfigConn : uses
    ConnKey <.. DSGConfigResource : key_for
    ConnKey <.. RefManager : key_for

    ConfigSyncPolicy <|-- ConfigSyncRequestCache

    ConfigSyncRequestCache <.. DSGConfigServer : used_for_sync
    ConfigSyncRequestCache <.. DSGConfigResource : used_for_sync

    RefManager o--> DSGConfigResource : manages

    DSGConfigServer o--> RefManager : owns
    DSGConfigServer o--> DSGConfigResource : owns
    DSGConfigServer o--> InotifyWatcher : owns
    DSGConfigServer ..> ConfigPathResolver : uses_singleton

    DSGConfigResource o--> DSGConfigConn : owns

    ServiceLifecycle ..> DSGConfigServer : tracks_state_of
Loading

File-Level Changes

Change Details Files
Refactor connection/reference management and sync policy to reduce timers, centralize lifetime control, and support flush/retry semantics.
  • Replace per-connection QTimer pool with a single global release timer and pending-release queue keyed by ConnKey, including destroy-time cleanup.
  • Introduce ConnKey as a structured type (plus qHash) and migrate maps of services/resources/conns/caches from QMap to QHash for faster lookup.
  • Turn ConfigSyncRequestCache into a ConfigSyncPolicy implementation with schedule/flush/clear, add flush-on-exit in DSGConfigServer::exit, and add saveWithRetry with backoff in DSGConfigResource.
dconfig-center/dde-dconfig-daemon/dconfig_types.h
dconfig-center/dde-dconfig-daemon/dconfigrefmanager.h
dconfig-center/dde-dconfig-daemon/dconfigrefmanager.cpp
dconfig-center/dde-dconfig-daemon/dconfigresource.h
dconfig-center/dde-dconfig-daemon/dconfigresource.cpp
dconfig-center/dde-dconfig-daemon/dconfigserver.cpp
dconfig-center/dde-dconfig-daemon/configsyncpolicy.h
dconfig-center/dde-dconfig-daemon/src.cmake
Replace polling-based config reload with inotify-driven hot reload and centralized config path resolution.
  • Add InotifyWatcher wrapper around Linux inotify with recursive directory watching and fileChanged/directoryChanged signals.
  • In DSGConfigServer::initialize, set up InotifyWatcher over config directories plus a 200ms throttle timer and queue to batch updates, emitting configUpdated(appid, resource) after successful reload.
  • Introduce ConfigPathResolver singleton to manage search paths with priorities and to provide meta/override paths and watched dirs; wire it into server initialization.
dconfig-center/dde-dconfig-daemon/inotifywatcher.h
dconfig-center/dde-dconfig-daemon/inotifywatcher.cpp
dconfig-center/dde-dconfig-daemon/configpathresolver.h
dconfig-center/dde-dconfig-daemon/configpathresolver.cpp
dconfig-center/dde-dconfig-daemon/dconfigserver.h
dconfig-center/dde-dconfig-daemon/dconfigserver.cpp
dconfig-center/dde-dconfig-daemon/src.cmake
Modernize daemon lifecycle handling with an explicit ServiceLifecycle state machine, async-signal-safe POSIX signal bridging, and unified logging.
  • Add ServiceLifecycle class with explicit Initializing→Running→Stopping→Stopped transitions, signal emission, and validation of allowed transitions.
  • In main(), set up a socketpair-based bridge from SIGTERM/SIGINT to Qt via QSocketNotifier, perform ordered shutdown (transition states, call DSGConfigServer::exit, then quit), and fall back to legacy handlers on failure.
  • Initialize QT_MESSAGE_PATTERN if unset to a standard time/pid/category format and hook lifecycle transitions into service startup logging; add unit tests for lifecycle transitions.
dconfig-center/dde-dconfig-daemon/servicelifecycle.h
dconfig-center/dde-dconfig-daemon/servicelifecycle.cpp
dconfig-center/dde-dconfig-daemon/main.cpp
dconfig-center/tests/ut_dconfigresource.cpp
dconfig-center/dde-dconfig-daemon/src.cmake
Tighten security and add auditability for user-data operations and manager acquisition.
  • Log removeUserData calls with an [AUDIT] entry including uid, caller service, and PID when invoked over D-Bus.
  • Enforce uid checks in acquireManagerV2 so non-root callers cannot access other users’ configs, returning AccessDenied with an audit log on mismatch.
dconfig-center/dde-dconfig-daemon/dconfigserver.cpp
Extend config semantics to honor deprecation and simple JSON schema constraints at runtime.
  • On reads, inspect meta description for a [deprecated] marker and emit warnings when deprecated keys are accessed.
  • On writes, parse a [schema:{...}] JSON snippet from the meta description, validate type and numeric ranges against the provided variant, and on failure warn and return org.desktopspec.ConfigManager.InvalidValue over D-Bus.
dconfig-center/dde-dconfig-daemon/dconfigconn.cpp
Enhance CLI tool with JSON/text output, watch JSON events, and snapshot export/import of config values.
  • Extend CommandManager and main() to support --output json
text and --file/-o, and to add export/import subcommands to the parser and dispatch logic.
  • Update list/get/watch to honor JSON output, emitting structured QJsonDocument payloads including key/appid/resource/value where appropriate.
  • Implement exportCommand/importCommand to dump all values for a given appid/resource to a JSON snapshot file and to bulk import from such a snapshot with basic error handling.
  • Improve helper APIs, editor code quality, and log formatting/typing to support the new behavior and fix minor issues.
    • Mark various helper.hpp free functions as inline to allow header-only use and avoid ODR issues.
    • Fix range-for loops to use const auto & where appropriate and adjust qDebug/qCInfo format specifiers from %d to %lld for container sizes.
    • Minor editor tweak to iterate userInfos by const reference in MainWindow::installTranslate.
    dconfig-center/common/helper.hpp
    dconfig-center/dde-dconfig-daemon/dconfigserver.cpp
    dconfig-center/dde-dconfig-daemon/dconfigresource.cpp
    dconfig-center/dde-dconfig-editor/mainwindow.cpp
    Add unit tests for DSGConfigResource, ServiceLifecycle, and ConfigPathResolver plus a CI workflow that builds, runs tests under dbus-run-session, and generates coverage reports.
    • Introduce ut_dconfigresource.cpp covering DSGConfigResource reparse path, basic server acquireManager interaction, ServiceLifecycle transitions, and ConfigPathResolver priority/duplicate/candidate behavior.
    • Register ut_dconfigresource.cpp in tests/CMakeLists.txt so it is built into the dconfigtest test binary.
    • Add .github/workflows/test.yml to configure a Debug+coverage build on Ubuntu, run ctest under dbus-run-session, and generate and upload gcovr HTML/XML coverage reports.
    dconfig-center/tests/ut_dconfigresource.cpp
    dconfig-center/tests/CMakeLists.txt
    .github/workflows/test.yml

    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

    deepin pr auto review

    代码审查报告

    整体评价

    本次代码变更引入了多项重要改进,包括配置路径管理、文件监听机制、生命周期状态机、单元测试框架等。整体代码质量较高,但在某些方面仍有优化空间。

    详细审查意见

    1. GitHub Actions 工作流 (test.yml)

    语法与逻辑

    • ✅ 工作流配置语法正确
    • ✅ 触发条件合理,支持 push 和 pull_request
    • ✅ 测试环境配置完整

    改进建议

    1. 性能优化

      # 建议添加缓存步骤以加快构建速度
      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: |
            ~/.cache
            build
          key: ${{ runner.os }}-build-${{ hashFiles('**/CMakeLists.txt') }}
    2. 安全性增强

      # 建议添加依赖扫描
      - name: Run security scan
        run: |
          sudo apt-get install -y dependency-check
          dependency-check --scan . --format XML --out reports/dependency-check-report.xml
    3. 测试覆盖率报告

      # 建议添加覆盖率报告上传到 Codecov 或类似服务
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./build/coverage.xml

    2. helper.hpp 中的函数改进

    语法与逻辑

    • ✅ 将 static 改为 inline 是正确的,可以避免 ODR 违规
    • ✅ 函数逻辑保持不变,只是修改了存储类说明符

    改进建议

    1. 性能优化

      // 对于频繁调用的函数,建议添加 noexcept
      inline AppList applications(const QString &localPrefix = QString()) noexcept
      {
          // ...
      }
    2. 代码质量

      // 建议为这些工具函数添加文档注释
      /**
       * @brief 获取应用程序列表
       * @param localPrefix 本地路径前缀
       * @return 应用程序ID列表
       */
      inline AppList applications(const QString &localPrefix = QString()) noexcept
      {
          // ...
      }

    3. ConfigPathResolver 实现

    语法与逻辑

    • ✅ 单例模式实现正确
    • ✅ 路径优先级管理逻辑合理
    • ✅ 使用 QPair 存储优先级和路径是合理的

    改进建议

    1. 性能优化

      // 在 addSearchPath 中,使用 QSet 检查重复会更高效
      void ConfigPathResolver::addSearchPath(const QString &path, int priority)
      {
          const QString abs = m_localPrefix + path;
          // 使用 QSet 检查重复
          static QSet<QString> pathSet;
          if (pathSet.contains(abs)) return;
          pathSet.insert(abs);
          
          m_paths.append({priority, abs});
          // 按 priority 降序排列
          std::sort(m_paths.begin(), m_paths.end(), [](const auto &a, const auto &b) {
              return a.first > b.first;
          });
      }
    2. 安全性增强

      // 添加路径验证,防止路径遍历攻击
      void ConfigPathResolver::addSearchPath(const QString &path, int priority)
      {
          // 防止路径遍历攻击
          if (path.contains("..") || path.startsWith('/')) {
              qWarning() << "Invalid path detected:" << path;
              return;
          }
          // 其余代码...
      }

    4. ConfigSyncPolicy 抽象接口

    语法与逻辑

    • ✅ 接口设计合理,提供了必要的虚函数
    • ✅ 继承自 QObject,支持信号槽机制

    改进建议

    1. 代码质量
      // 为虚函数添加文档注释
      /**
       * @brief 调度一个写盘请求
       * @param key 配置缓存键
       * @note 具体执行时机由子类决定
       */
      virtual void schedule(const ConfigCacheKey &key) = 0;

    5. ConnKey 类型安全实现

    语法与逻辑

    • ✅ 类型安全设计合理
    • ✅ 提供了与旧格式的兼容性
    • ✅ 实现了必要的运算符重载

    改进建议

    1. 性能优化

      // 使用 QStringView 优化字符串操作
      static ConnKey fromString(const QString &s)
      {
          ConnKey k;
          QStringView sv(s);
          auto parts = sv.split('/', Qt::SkipEmptyParts);
          if (parts.size() < 3)
              return k;
          
          k.appid    = parts.at(0).toString();
          k.resource = parts.at(1).toString();
          // 其余代码...
      }
    2. 安全性增强

      // 添加输入验证
      static ConnKey fromString(const QString &s)
      {
          // 防止路径遍历攻击
          if (s.contains("..")) {
              qWarning() << "Invalid path detected in ConnKey:" << s;
              return ConnKey();
          }
          // 其余代码...
      }

    6. DSGConfigConn 中的 JSON Schema 校验

    语法与逻辑

    • ✅ JSON Schema 校验逻辑基本正确
    • ✅ 错误处理合理

    改进建议

    1. 性能优化

      // 缓存解析后的 schema,避免重复解析
      static QHash<QString, QJsonObject> schemaCache;
      
      const QString desc = meta()->description(key, QLocale());
      const int schemaStart = desc.indexOf(QLatin1String("[schema:"));
      const int schemaEnd   = desc.indexOf(QLatin1String("]"), schemaStart);
      if (schemaStart >= 0 && schemaEnd > schemaStart) {
          const QString schemaStr = desc.mid(schemaStart + 8, schemaEnd - schemaStart - 8);
          
          // 检查缓存
          if (schemaCache.contains(schemaStr)) {
              const QJsonObject schema = schemaCache[schemaStr];
              // 使用缓存的 schema 进行验证...
          } else {
              QJsonParseError err;
              QJsonDocument schemaDoc = QJsonDocument::fromJson(schemaStr.toUtf8(), &err);
              if (err.error == QJsonParseError::NoError && schemaDoc.isObject()) {
                  const QJsonObject schema = schemaDoc.object();
                  // 存入缓存
                  schemaCache[schemaStr] = schema;
                  // 其余代码...
              }
          }
      }
    2. 代码质量

      // 将校验逻辑提取为独立函数,提高可读性
      bool validateAgainstSchema(const QVariant &value, const QJsonObject &schema, QString &reason)
      {
          const QString type = schema["type"].toString();
          bool valid = true;
          
          if (type == "integer" || type == "number") {
              // 验证逻辑...
          } else if (type == "string") {
              // 验证逻辑...
          } else if (type == "boolean") {
              // 验证逻辑...
          }
          
          return valid;
      }
      
      // 在 setValue 中调用
      if (!validateAgainstSchema(v, schema, reason)) {
          const QString errMsg = QString("Schema validation failed for key '%1': %2").arg(key, reason);
          // 其余代码...
      }

    7. RefManager 中的延迟释放机制

    语法与逻辑

    • ✅ 使用全局定时器替代多个定时器的设计合理
    • ✅ 延迟释放逻辑正确

    改进建议

    1. 性能优化

      // 使用更高效的数据结构
      // 原代码使用 QHash<ConnKey, qint64>,可以考虑使用 QMap<qint64, ConnKey>
      // 这样可以按时间排序,便于查找过期的连接
      QMap<qint64, ConnKey> m_pendingRelease;  ///< 到期 epoch ms → key
      
      // 在定时器回调中
      QObject::connect(m_globalReleaseTimer, &QTimer::timeout, this, [this]() {
          const qint64 now = QDateTime::currentMSecsSinceEpoch();
          // 查找所有已过期的连接
          auto it = m_pendingRelease.begin();
          while (it != m_pendingRelease.end() && it.key() <= now) {
              const ConnKey &key = it.value();
              m_pendingRelease.erase(it);
              
              auto resourceRef = resources.value(key);
              if (resourceRef && resourceRef->release()) {
                  qCDebug(cfLog, "Resource[%s] removing (global timer).", qPrintable(key));
                  doDeleteResource({resourceRef});
              }
              it = m_pendingRelease.begin();
          }
          
          if (m_pendingRelease.isEmpty())
              m_globalReleaseTimer->stop();
      });
    2. 代码质量

      // 添加注释说明延迟释放机制
      /**
       * @brief 延迟删除资源
       * 
       * 使用全局定时器 + 到期时间戳队列实现延迟释放机制,
       * 替代原有的 per-conn QTimer 方案,减少资源占用。
       * 
       * @param deleteResources 需要延迟删除的资源列表
       */
      void RefManager::delayDeleteResource(const QList<ResourceRef *> &deleteResources)
      {
          // 其余代码...
      }

    8. DSGConfigResource 中的保存重试机制

    语法与逻辑

    • ✅ 保存重试机制设计合理
    • ✅ 指数退避策略正确

    改进建议

    1. 性能优化

      // 使用更精确的退避策略
      bool DSGConfigResource::saveWithRetry(DConfigCache *cache, int maxRetry)
      {
          for (int i = 0; i < maxRetry; ++i) {
              if (cache->save(m_localPrefix))
                  return true;
              
              // 使用指数退避,但限制最大等待时间
              const int maxDelay = 1000; // 最大等待1秒
              const int delay = qMin(100 * (1 << i), maxDelay);
              qCWarning(cfLog, "[sync] save failed (attempt %d/%d) for cache, retrying in %d ms...", 
                        i + 1, maxRetry, delay);
              QThread::msleep(delay);
          }
          qCCritical(cfLog, "[sync] save failed after %d retries.", maxRetry);
          return false;
      }
    2. 安全性增强

      // 添加文件系统检查
      bool DSGConfigResource::saveWithRetry(DConfigCache *cache, int maxRetry)
      {
          // 检查文件系统是否可写
          QFileInfo fileInfo(m_localPrefix);
          if (fileInfo.exists() && !fileInfo.isWritable()) {
              qCCritical(cfLog, "[sync] save failed: directory is not writable");
              return false;
          }
          
          // 其余代码...
      }

    9. DSGConfigServer 中的 inotify 监听机制

    语法与逻辑

    • ✅ inotify 监听机制实现正确
    • ✅ 节流定时器设计合理

    改进建议

    1. 性能优化

      // 使用更高效的路径匹配
      bool DSGConfigServer::isConfigurePath(const QString &path, const QString &appId) const
      {
          // 使用 QRegularExpression 进行更高效的匹配
          static QRegularExpression re(R"((^|/)configs/(overrides/)?[^/]+\.json$)");
          if (!re.match(path).hasMatch())
              return false;
          
          // 其余代码...
      }
    2. 代码质量

      // 添加注释说明 inotify 监听机制
      /**
       * @brief 初始化 inotify 监听
       * 
       * 使用 inotify 监听配置目录变化,替代原有的文件签名轮询方案,
       * 降低 CPU 开销。
       * 
       * 监听事件:IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE | IN_DELETE | IN_MOVED_FROM
       */
      void DSGConfigServer::initialize()
      {
          // 其余代码...
      }

    10. InotifyWatcher 实现

    语法与逻辑

    • ✅ inotify 封装实现正确
    • ✅ 递归监听逻辑合理

    改进建议

    1. 性能优化

      // 使用更高效的事件处理
      void InotifyWatcher::onInotifyEvent()
      {
      #ifdef Q_OS_LINUX
          // 使用更大的缓冲区
          constexpr int INOTIFY_BUF_SIZE = (16 * 1024); // 16KB
          char buf[INOTIFY_BUF_SIZE] __attribute__((aligned(__alignof__(inotify_event))));
          ssize_t len;
          
          while ((len = read(m_fd, buf, sizeof(buf))) > 0) {
              char *ptr = buf;
              while (ptr < buf + len) {
                  auto *event = reinterpret_cast<inotify_event *>(ptr);
                  
                  // 其余代码...
                  
                  ptr += sizeof(inotify_event) + event->len;
              }
          }
      #endif
      }
    2. 安全性增强

      // 添加路径验证
      void InotifyWatcher::addWatchRecursive(const QString &path)
      {
      #ifdef Q_OS_LINUX
          if (m_fd < 0 || m_pathToWd.contains(path))
              return;
          
          // 防止路径遍历攻击
          if (path.contains("..")) {
              qWarning() << "[InotifyWatcher] Invalid path detected:" << path;
              return;
          }
          
          // 其余代码...
      #endif
      }

    11. ServiceLifecycle 状态机

    语法与逻辑

    • ✅ 状态机设计合理
    • ✅ 状态转换验证正确

    改进建议

    1. 代码质量

      // 添加状态转换图注释
      /**
       * @brief 服务生命周期状态机
       * 
       * 状态转换规则:
       *   Initializing → Running → Stopping → Stopped
       * 
       * 非法转换(如 Stopped → Running)会打印 warning 并被忽略。
       * 
       * @image html lifecycle.png "服务生命周期状态转换图"
       */
      enum class ServiceState {
          Initializing,   ///< 服务正在初始化
          Running,        ///< 服务正常运行中
          Stopping,       ///< 服务正在停止(收到 SIGTERM 或 exit() 调用)
          Stopped         ///< 服务已完全停止
      };
    2. 性能优化

      // 使用 switch-case 替代 if-else 链
      bool ServiceLifecycle::isValidTransition(ServiceState from, ServiceState to)
      {
          switch (from) {
          case ServiceState::Initializing:
              return to == ServiceState::Running;
          case ServiceState::Running:
              return to == ServiceState::Stopping;
          case ServiceState::Stopping:
              return to == ServiceState::Stopped;
          case ServiceState::Stopped:
              return false;
          }
          return false;
      }

    12. main.cpp 中的信号处理

    语法与逻辑

    • ✅ POSIX 信号到 Qt 事件的桥接实现正确
    • ✅ 有序关闭逻辑合理

    改进建议

    1. 安全性增强

      // 添加信号处理错误检查
      static bool setupSignalBridge()
      {
          if (::socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, g_signalFd) != 0) {
              qWarning("socketpair() failed for signal bridge: %s", strerror(errno));
              return false;
          }
          
          struct sigaction sa;
          sa.sa_handler = posixSignalHandler;
          sigemptyset(&sa.sa_mask);
          sa.sa_flags = SA_RESTART;
          
          if (sigaction(SIGTERM, &sa, nullptr) != 0) {
              qWarning("sigaction(SIGTERM) failed: %s", strerror(errno));
              return false;
          }
          
          if (sigaction(SIGINT, &sa, nullptr) != 0) {
              qWarning("sigaction(SIGINT) failed: %s", strerror(errno));
              return false;
          }
          
          return true;
      }
    2. 代码质量

      // 添加注释说明信号处理机制
      /**
       * @brief POSIX 信号 → Qt 事件桥
       * 
       * 使用 socketpair 和 QSocketNotifier 将 POSIX 信号转换为 Qt 事件,
       * 实现在 Qt 事件循环中安全处理信号。
       * 
       * 处理的信号:SIGTERM, SIGINT
       */
      static int g_signalFd[2] = {-1, -1};

    13. 单元测试 (ut_dconfigresource.cpp)

    语法与逻辑

    • ✅ 单元测试结构合理
    • ✅ 测试用例覆盖主要功能

    改进建议

    1. 测试覆盖率

      // 添加更多边界条件测试
      TEST_F(ut_DSGConfigResource, test_reparse_with_invalid_appid)
      {
          // 测试无效 appid 的情况
          auto path = server->acquireManager("invalid.appid", FILE_NAME, "");
          EXPECT_TRUE(path.path().isEmpty());
      }
      
      TEST_F(ut_DSGConfigResource, test_reparse_with_invalid_resource)
      {
          // 测试无效资源的情况
          auto path = server->acquireManager(APP_ID, "invalid_resource", "");
          EXPECT_TRUE(path.path().isEmpty());
      }
    2. 性能测试

      // 添加性能测试
      TEST_F(ut_DSGConfigResource, test_reparse_performance)
      {
          auto path = server->acquireManager(APP_ID, FILE_NAME, "");
          ASSERT_FALSE(path.path().isEmpty());
          
          auto resource = server->resourceObject(getGenericResourceKey(FILE_NAME, QString()));
          if (!resource) {
              GTEST_SKIP() << "resource not found, skip performance test";
          }
          
          // 测试多次 reparse 的性能
          const int iterations = 100;
          QBENCHMARK {
              for (int i = 0; i < iterations; ++i) {
                  EXPECT_NO_FATAL_FAILURE(resource->reparse(APP_ID));
              }
          }
      }

    总结

    本次代码变更引入了多项重要改进,整体代码质量较高。主要改进点包括:

    1. 将静态函数改为内联函数,避免 ODR 违规
    2. 引入配置路径管理中心,统一管理配置文件搜索路径
    3. 实现了写盘策略抽象接口,支持多种写盘策略
    4. 引入类型安全的连接标识,替代原有字符串拼接方案
    5. 添加 JSON Schema 校验,增强配置安全性
    6. 使用全局定时器替代多个定时器,优化延迟释放机制
    7. 实现保存重试机制,提高配置保存可靠性
    8. 使用 inotify 监听配置文件变化,替代文件签名轮询方案
    9. 引入服务生命周期状态机,规范服务状态转换
    10. 改进信号处理机制,实现有序关闭
    11. 添加单元测试,提高代码质量

    建议在以下几个方面进一步优化:

    1. 添加缓存机制,提高性能
    2. 增强输入验证,提高安全性
    3. 提取公共函数,提高代码可读性
    4. 添加更多注释和文档,提高代码可维护性
    5. 扩展单元测试覆盖率,包括边界条件和性能测试
    6. 在 GitHub Actions 中添加依赖扫描和缓存机制

    总体而言,本次代码变更是积极的,为项目带来了多项重要改进,建议在上述建议的基础上进一步完善代码。

    Copy link
    Copy Markdown

    @sourcery-ai sourcery-ai Bot left a comment

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Hey - I've found 7 issues, and left some high level feedback:

    • In ConfigPathResolver::overridePaths the condition if (!m_localPrefix.isEmpty() || true) is always true, which likely defeats the intent of guarding the extra /etc/dsg/configs/overrides path and may generate incorrect/duplicate paths when m_localPrefix is empty; consider removing the || true or tightening this condition.
    • The daemon main.cpp now calls dsgConfig.exit() both in the signal bridge lambda and again in the aboutToQuit() handler, which can result in double cleanup (and double flush/leak logs); it would be safer to centralize the shutdown logic so DSGConfigServer::exit() is only executed once.
    • The new CLI export/import snapshot format currently serializes all values with toString(), which loses type information for booleans, numbers and structured QVariant values; consider using QJsonValue::fromVariant / QJsonDocument::fromVariant so the snapshot preserves original types and can be round-tripped more faithfully.
    Prompt for AI Agents
    Please address the comments from this code review:
    
    ## Overall Comments
    - In ConfigPathResolver::overridePaths the condition `if (!m_localPrefix.isEmpty() || true)` is always true, which likely defeats the intent of guarding the extra `/etc/dsg/configs/overrides` path and may generate incorrect/duplicate paths when `m_localPrefix` is empty; consider removing the `|| true` or tightening this condition.
    - The daemon main.cpp now calls dsgConfig.exit() both in the signal bridge lambda and again in the aboutToQuit() handler, which can result in double cleanup (and double flush/leak logs); it would be safer to centralize the shutdown logic so DSGConfigServer::exit() is only executed once.
    - The new CLI export/import snapshot format currently serializes all values with `toString()`, which loses type information for booleans, numbers and structured QVariant values; consider using QJsonValue::fromVariant / QJsonDocument::fromVariant so the snapshot preserves original types and can be round-tripped more faithfully.
    
    ## Individual Comments
    
    ### Comment 1
    <location path="dconfig-center/dde-dconfig-daemon/dconfigserver.h" line_range="51-52" />
    <code_context>
    
         void tryExit();
    
    +    /// T06-4: 配置文件更新时通知调用方(appid/resource 来自路径解析)
    +    void configUpdated(const QString &appid, const QString &resource);
    +
     public Q_SLOTS:
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** configUpdated should be declared as a Qt signal if it is emitted
    
    In dconfigserver.cpp, `configUpdated` is emitted (`emit configUpdated(...)`) but is declared here as a regular method instead of a signal. This will cause a compilation error or prevent it from working as a Qt signal. Please move this declaration into a `Q_SIGNALS:` section so `emit` is valid and it can be connected from other objects.
    </issue_to_address>
    
    ### Comment 2
    <location path="dconfig-center/dde-dconfig-daemon/inotifywatcher.cpp" line_range="10-12" />
    <code_context>
    +#include <QDir>
    +#include <QDebug>
    +
    +#ifdef Q_OS_LINUX
    +#include <sys/inotify.h>
    +#include <unistd.h>
    +#endif
    +
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** Missing errno/strerror includes for Linux branch
    
    `errno` and `strerror(errno)` are used in the Linux code paths, but the file doesn’t include `<errno.h>` or `<string.h>/<cstring>`. Please add these headers explicitly rather than relying on transitive includes, which are undefined and may fail on some toolchains.
    </issue_to_address>
    
    ### Comment 3
    <location path="dconfig-center/dde-dconfig-daemon/configpathresolver.cpp" line_range="28-33" />
    <code_context>
    +
    +void ConfigPathResolver::addSearchPath(const QString &path, int priority)
    +{
    +    const QString abs = m_localPrefix + path;
    +    // 避免重复
    +    for (const auto &p : m_paths) {
    +        if (p.second == abs) return;
    +    }
    +    m_paths.append({priority, abs});
    +    // 按 priority 降序排列
    +    std::sort(m_paths.begin(), m_paths.end(), [](const auto &a, const auto &b) {
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** addSearchPath unconditionally prefixes with m_localPrefix, which can double-prefix or break absolute paths
    
    Here `path` is always prefixed with `m_localPrefix`, but call sites already pass absolute paths (e.g. `"/usr/share/dsg/configs"`) and `linglongPath` is validated via `QDir(m_localPrefix + linglongPath).exists()` before being passed in. As a result, you can end up storing `m_localPrefix + m_localPrefix + linglongPath`, and absolute paths in general get incorrectly prefixed. Consider checking whether `path` is relative and only then prepending `m_localPrefix` (e.g. using `QDir::isRelativePath(path)`).
    </issue_to_address>
    
    ### Comment 4
    <location path="dconfig-center/dde-dconfig-daemon/configpathresolver.cpp" line_range="72-73" />
    <code_context>
    +        result << QString("%1/overrides/%2.json").arg(p.second, resource);
    +    }
    +    // 系统管理员覆盖目录
    +    if (!m_localPrefix.isEmpty() || true) {
    +        result << QString("%1/etc/dsg/configs/overrides/%2/%3.json")
    +                      .arg(m_localPrefix, appid, resource);
    +    }
    </code_context>
    <issue_to_address>
    **issue (bug_risk):** Condition `if (!m_localPrefix.isEmpty() || true)` is always true and likely not intended
    
    Because of the `|| true`, this block always executes and the `/etc/dsg/configs/overrides/...` path is always added, making the `m_localPrefix.isEmpty()` check pointless and suggesting a leftover from debugging. If this path should only be added when a local prefix exists, drop `|| true`; if it should always be added, remove the `if` entirely for clarity.
    </issue_to_address>
    
    ### Comment 5
    <location path="dconfig-center/dde-dconfig-daemon/dconfigresource.cpp" line_range="454-459" />
    <code_context>
     void DSGConfigResource::save()
     {
    -    qDebug(cfLog, "Save resource's cache for [%s], and cache count:%d", qPrintable(m_key), m_caches.count());
    +    qDebug(cfLog, "Save resource's cache for [%s], and cache count:%lld", qPrintable(m_key), m_caches.count());
         for (auto item : m_files)
             item->save(m_localPrefix);
    
         for (auto item : m_caches)
    -        item->save(m_localPrefix);
    +        saveWithRetry(item);
    +}
    +
    </code_context>
    <issue_to_address>
    **suggestion (bug_risk):** Blocking retry loop with QThread::msleep may stall the event loop under load
    
    `saveWithRetry` uses `QThread::msleep` in a loop on the main thread, where `save()` is called. This can block the event loop for hundreds of ms per cache under heavy writes, delaying DBus handling and timers. If retries are required, avoid sleeping on the main thread—e.g. just log and continue, or move retries to a worker thread / queued `QTimer` so the main event loop stays responsive.
    
    Suggested implementation:
    
    ```cpp
        for (auto item : m_caches)
            item->save(m_localPrefix);
    }
    
    ```
    
    `saveWithRetry` is still in the codebase and currently uses `QThread::msleep` in a loop. To fully address the concern:
    1. Either remove `saveWithRetry` if no longer needed, or
    2. Refactor it to run in a worker thread or use a `QTimer`/queued connection so any retries are asynchronous and do not block the main event loop.
    You’ll also want to update any other call sites of `saveWithRetry` (if they exist) to ensure they don’t block the main thread in the same way.
    </issue_to_address>
    
    ### Comment 6
    <location path="dconfig-center/tests/ut_dconfigresource.cpp" line_range="59-68" />
    <code_context>
    +    QScopedPointer<DSGConfigServer> server;
    +};
    +
    +// T11-1a: reparse() 调用后值能被正确更新(仅测试调用不崩溃)
    +TEST_F(ut_DSGConfigResource, test_reparse)
    +{
    +    // 通过 server 拿到 resource
    +    auto path = server->acquireManager(APP_ID, FILE_NAME, "");
    +    ASSERT_FALSE(path.path().isEmpty());
    +
    +    auto resource = server->resourceObject(getGenericResourceKey(FILE_NAME, QString()));
    +    if (!resource) {
    +        GTEST_SKIP() << "resource not found, skip reparse test";
    +    }
    +    // reparse 不应崩溃
    +    EXPECT_NO_FATAL_FAILURE(resource->reparse(APP_ID));
    +}
    +
    </code_context>
    <issue_to_address>
    **suggestion (testing):** reparse() test only checks that it does not crash but does not verify that parsing actually updates values
    
    Since the comment says "值能被正确更新", this test should assert at least one actual value change, not just that it doesn’t crash. For example, modify the underlying JSON (or use two variants) before calling `reparse(APP_ID)`, then verify that `resource` reads the updated value. Alternatively, read a known key before and after `reparse()` and assert the expected change to prove the reload path really works.
    </issue_to_address>
    
    ### Comment 7
    <location path="dconfig-center/dde-dconfig-daemon/main.cpp" line_range="104" />
    <code_context>
         Dtk::Core::DLogManager::registerFileAppender();
         qInfo() << "Log path is:" << Dtk::Core::DLogManager::getlogFilePath();
    +
    +    // T01-2:POSIX 信号 → Qt 事件桥(有序关闭)
    +    if (setupSignalBridge()) {
    +        auto *signalNotifier = new QSocketNotifier(g_signalFd[1], QSocketNotifier::Read, &a);
    </code_context>
    <issue_to_address>
    **issue (complexity):** Consider centralizing all shutdown behavior in the aboutToQuit handler so that signal handling only requests application quit and does not duplicate cleanup or lifecycle transitions.
    
    You can keep all new functionality (ordered shutdown, logging, lifecycle) but simplify by centralizing shutdown logic and making the signal bridge only responsible for *requesting* a quit.
    
    Right now you have:
    - `dsgConfig.exit()` called in both the signal-notifier lambda and the `aboutToQuit` handler.
    - `ServiceLifecycle` transitions duplicated in the signal-notifier lambda instead of being centralized.
    - Two different shutdown code paths (bridge vs fallback) that behave differently.
    
    You can reduce complexity by:
    1. Letting the POSIX → Qt bridge only call `QCoreApplication::quit()` and log the signal.
    2. Doing **all** cleanup (lifecycle transitions + `dsgConfig.exit()`) only in `aboutToQuit`.
    3. Making the fallback signal handler mirror the bridge behavior (log + quit only).
    
    This keeps the bridge and the lifecycle, but the entry point becomes much easier to reason about: “signals → quit request; aboutToQuit → cleanup”.
    
    ### Suggested refactor
    
    **Signal bridge: only request quit**
    
    ```cpp
    if (setupSignalBridge()) {
        auto *signalNotifier = new QSocketNotifier(g_signalFd[1],
                                                   QSocketNotifier::Read,
                                                   &a);
        QObject::connect(signalNotifier, &QSocketNotifier::activated, &a,
                         [](int) {
            char sig = 0;
            ::read(g_signalFd[1], &sig, 1);
            qInfo("[main] Received signal %d, requesting shutdown...",
                  static_cast<int>(sig));
            QCoreApplication::quit();
        });
    } else {
        // 降级:直接用旧的 signal handler(也只触发退出)
        struct sigaction sa;
        sa.sa_handler = [](int sig) {
            qInfo("Received signal %d, requesting shutdown.", sig);
            QCoreApplication::quit();
        };
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESETHAND;
        sigaction(SIGTERM, &sa, nullptr);
        sigaction(SIGINT,  &sa, nullptr);
    }
    ```
    
    **Centralize lifecycle + cleanup in one place**
    
    ```cpp
    QObject::connect(qApp, &QCoreApplication::aboutToQuit,
                     [&lifecycle, &dsgConfig]() {
        qInfo() << "[main] Exit dconfig daemon and release resources.";
        lifecycle.transitionTo(ServiceState::Stopping);
        dsgConfig.exit();
        lifecycle.transitionTo(ServiceState::Stopped);
    });
    ```
    
    This way:
    
    - There is a **single, canonical shutdown path** (`aboutToQuit`).
    - Signal handling is minimal and consistent across bridge/fallback.
    - `ServiceLifecycle` actually *centralizes* lifecycle transitions instead of being partially driven from the signal handler.
    - You avoid double-calling `dsgConfig.exit()` and double-logging shutdown.
    </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 on lines +51 to +52
    /// T06-4: 配置文件更新时通知调用方(appid/resource 来自路径解析)
    void configUpdated(const QString &appid, const QString &resource);
    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): configUpdated should be declared as a Qt signal if it is emitted

    In dconfigserver.cpp, configUpdated is emitted (emit configUpdated(...)) but is declared here as a regular method instead of a signal. This will cause a compilation error or prevent it from working as a Qt signal. Please move this declaration into a Q_SIGNALS: section so emit is valid and it can be connected from other objects.

    Comment on lines +10 to +12
    #ifdef Q_OS_LINUX
    #include <sys/inotify.h>
    #include <unistd.h>
    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): Missing errno/strerror includes for Linux branch

    errno and strerror(errno) are used in the Linux code paths, but the file doesn’t include <errno.h> or <string.h>/<cstring>. Please add these headers explicitly rather than relying on transitive includes, which are undefined and may fail on some toolchains.

    Comment on lines +28 to +33
    const QString abs = m_localPrefix + path;
    // 避免重复
    for (const auto &p : m_paths) {
    if (p.second == abs) return;
    }
    m_paths.append({priority, abs});
    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): addSearchPath unconditionally prefixes with m_localPrefix, which can double-prefix or break absolute paths

    Here path is always prefixed with m_localPrefix, but call sites already pass absolute paths (e.g. "/usr/share/dsg/configs") and linglongPath is validated via QDir(m_localPrefix + linglongPath).exists() before being passed in. As a result, you can end up storing m_localPrefix + m_localPrefix + linglongPath, and absolute paths in general get incorrectly prefixed. Consider checking whether path is relative and only then prepending m_localPrefix (e.g. using QDir::isRelativePath(path)).

    Comment on lines +72 to +73
    if (!m_localPrefix.isEmpty() || true) {
    result << QString("%1/etc/dsg/configs/overrides/%2/%3.json")
    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): Condition if (!m_localPrefix.isEmpty() || true) is always true and likely not intended

    Because of the || true, this block always executes and the /etc/dsg/configs/overrides/... path is always added, making the m_localPrefix.isEmpty() check pointless and suggesting a leftover from debugging. If this path should only be added when a local prefix exists, drop || true; if it should always be added, remove the if entirely for clarity.

    Comment on lines +454 to +459
    qDebug(cfLog, "Save resource's cache for [%s], and cache count:%lld", qPrintable(m_key), m_caches.count());
    for (auto item : m_files)
    item->save(m_localPrefix);

    for (auto item : m_caches)
    item->save(m_localPrefix);
    saveWithRetry(item);
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    suggestion (bug_risk): Blocking retry loop with QThread::msleep may stall the event loop under load

    saveWithRetry uses QThread::msleep in a loop on the main thread, where save() is called. This can block the event loop for hundreds of ms per cache under heavy writes, delaying DBus handling and timers. If retries are required, avoid sleeping on the main thread—e.g. just log and continue, or move retries to a worker thread / queued QTimer so the main event loop stays responsive.

    Suggested implementation:

        for (auto item : m_caches)
            item->save(m_localPrefix);
    }
    

    saveWithRetry is still in the codebase and currently uses QThread::msleep in a loop. To fully address the concern:

    1. Either remove saveWithRetry if no longer needed, or
    2. Refactor it to run in a worker thread or use a QTimer/queued connection so any retries are asynchronous and do not block the main event loop.
      You’ll also want to update any other call sites of saveWithRetry (if they exist) to ensure they don’t block the main thread in the same way.

    Comment on lines +59 to +68
    // T11-1a: reparse() 调用后值能被正确更新(仅测试调用不崩溃)
    TEST_F(ut_DSGConfigResource, test_reparse)
    {
    // 通过 server 拿到 resource
    auto path = server->acquireManager(APP_ID, FILE_NAME, "");
    ASSERT_FALSE(path.path().isEmpty());

    auto resource = server->resourceObject(getGenericResourceKey(FILE_NAME, QString()));
    if (!resource) {
    GTEST_SKIP() << "resource not found, skip reparse test";
    Copy link
    Copy Markdown

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    suggestion (testing): reparse() test only checks that it does not crash but does not verify that parsing actually updates values

    Since the comment says "值能被正确更新", this test should assert at least one actual value change, not just that it doesn’t crash. For example, modify the underlying JSON (or use two variants) before calling reparse(APP_ID), then verify that resource reads the updated value. Alternatively, read a known key before and after reparse() and assert the expected change to prove the reload path really works.

    Dtk::Core::DLogManager::registerFileAppender();
    qInfo() << "Log path is:" << Dtk::Core::DLogManager::getlogFilePath();

    // T01-2:POSIX 信号 → Qt 事件桥(有序关闭)
    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 (complexity): Consider centralizing all shutdown behavior in the aboutToQuit handler so that signal handling only requests application quit and does not duplicate cleanup or lifecycle transitions.

    You can keep all new functionality (ordered shutdown, logging, lifecycle) but simplify by centralizing shutdown logic and making the signal bridge only responsible for requesting a quit.

    Right now you have:

    • dsgConfig.exit() called in both the signal-notifier lambda and the aboutToQuit handler.
    • ServiceLifecycle transitions duplicated in the signal-notifier lambda instead of being centralized.
    • Two different shutdown code paths (bridge vs fallback) that behave differently.

    You can reduce complexity by:

    1. Letting the POSIX → Qt bridge only call QCoreApplication::quit() and log the signal.
    2. Doing all cleanup (lifecycle transitions + dsgConfig.exit()) only in aboutToQuit.
    3. Making the fallback signal handler mirror the bridge behavior (log + quit only).

    This keeps the bridge and the lifecycle, but the entry point becomes much easier to reason about: “signals → quit request; aboutToQuit → cleanup”.

    Suggested refactor

    Signal bridge: only request quit

    if (setupSignalBridge()) {
        auto *signalNotifier = new QSocketNotifier(g_signalFd[1],
                                                   QSocketNotifier::Read,
                                                   &a);
        QObject::connect(signalNotifier, &QSocketNotifier::activated, &a,
                         [](int) {
            char sig = 0;
            ::read(g_signalFd[1], &sig, 1);
            qInfo("[main] Received signal %d, requesting shutdown...",
                  static_cast<int>(sig));
            QCoreApplication::quit();
        });
    } else {
        // 降级:直接用旧的 signal handler(也只触发退出)
        struct sigaction sa;
        sa.sa_handler = [](int sig) {
            qInfo("Received signal %d, requesting shutdown.", sig);
            QCoreApplication::quit();
        };
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESETHAND;
        sigaction(SIGTERM, &sa, nullptr);
        sigaction(SIGINT,  &sa, nullptr);
    }

    Centralize lifecycle + cleanup in one place

    QObject::connect(qApp, &QCoreApplication::aboutToQuit,
                     [&lifecycle, &dsgConfig]() {
        qInfo() << "[main] Exit dconfig daemon and release resources.";
        lifecycle.transitionTo(ServiceState::Stopping);
        dsgConfig.exit();
        lifecycle.transitionTo(ServiceState::Stopped);
    });

    This way:

    • There is a single, canonical shutdown path (aboutToQuit).
    • Signal handling is minimal and consistent across bridge/fallback.
    • ServiceLifecycle actually centralizes lifecycle transitions instead of being partially driven from the signal handler.
    • You avoid double-calling dsgConfig.exit() and double-logging shutdown.

    @deepin-bot
    Copy link
    Copy Markdown
    Contributor

    deepin-bot Bot commented Mar 12, 2026

    TAG Bot

    New tag: 1.0.42
    DISTRIBUTION: unstable
    Suggest: synchronizing this PR through rebase #146

    @deepin-bot
    Copy link
    Copy Markdown
    Contributor

    deepin-bot Bot commented Mar 25, 2026

    TAG Bot

    New tag: 1.0.43
    DISTRIBUTION: unstable
    Suggest: synchronizing this PR through rebase #147

    @deepin-bot
    Copy link
    Copy Markdown
    Contributor

    deepin-bot Bot commented May 14, 2026

    TAG Bot

    New tag: 1.0.44
    DISTRIBUTION: unstable
    Suggest: synchronizing this PR through rebase #149

    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