Skip to content

Conversation

@Johnson-zs
Copy link
Contributor

@Johnson-zs Johnson-zs commented Dec 31, 2025

Added index state management to track whether indexing operations completed cleanly or were interrupted. This enables intelligent recovery on service restart by avoiding unnecessary global updates when the index was properly shut down.

  1. Added IndexState enum with Clean, Dirty, and Unknown states
  2. Implemented state persistence in index status file with JSON serialization
  3. TaskManager now marks state as Dirty when starting tasks and Clean when all tasks complete successfully
  4. TextIndexDBus checks state during silent startup to skip global updates for clean shutdowns
  5. Added cleanup logic to mark state as Dirty if tasks are interrupted during service shutdown
  6. Refactored status file handling to use shared helper functions

Log: Improved indexing service reliability with crash recovery mechanism

Influence:

  1. Test service restart after clean shutdown - should skip global update
  2. Test service restart after interrupted indexing - should trigger global update
  3. Verify state persistence across service restarts
  4. Test index creation when no database exists (should proceed normally)
  5. Verify task queue management with multiple indexing operations
  6. Test service shutdown during active indexing operations

feat: 添加索引状态跟踪以支持崩溃恢复

新增索引状态管理功能,用于跟踪索引操作是否正常完成或被中断。这使服务重启
时能够智能恢复,避免在索引正常关闭时执行不必要的全局更新。

  1. 添加包含 Clean、Dirty 和 Unknown 状态的 IndexState 枚举
  2. 在索引状态文件中实现状态持久化,使用 JSON 序列化
  3. TaskManager 在启动任务时将状态标记为 Dirty,所有任务成功完成后标记 为 Clean
  4. TextIndexDBus 在静默启动时检查状态,对于干净关闭跳过全局更新
  5. 添加清理逻辑,在服务关闭时如果任务被中断则将状态标记为 Dirty
  6. 重构状态文件处理以使用共享辅助函数

Log: 通过崩溃恢复机制提升索引服务可靠性

Influence:

  1. 测试服务在干净关闭后重启 - 应跳过全局更新
  2. 测试服务在索引中断后重启 - 应触发全局更新
  3. 验证状态在服务重启间的持久性
  4. 测试不存在数据库时的索引创建(应正常进行)
  5. 验证多索引操作的任务队列管理
  6. 测试在活跃索引操作期间的服务关闭

Summary by Sourcery

Introduce persistent index state tracking to enable crash-aware recovery and avoid unnecessary global reindexing after clean shutdowns.

New Features:

  • Add an IndexState enum and helper APIs to persist and query index cleanliness in the index status file.
  • Use index state during silent startup to decide whether to run a global update or skip it when the index was cleanly shut down.

Enhancements:

  • Refactor index status file handling to use shared JSON read/write helpers and extend the status schema with a state field.
  • Update TaskManager to mark the index state as dirty when tasks start and clean when all queued tasks complete successfully.
  • Extend service cleanup to detect unfinished indexing work and mark the index state as dirty for the next startup.

Added index state management to track whether indexing operations
completed cleanly or were interrupted. This enables intelligent recovery
on service restart by avoiding unnecessary global updates when the index
was properly shut down.

1. Added IndexState enum with Clean, Dirty, and Unknown states
2. Implemented state persistence in index status file with JSON
serialization
3. TaskManager now marks state as Dirty when starting tasks and Clean
when all tasks complete successfully
4. TextIndexDBus checks state during silent startup to skip global
updates for clean shutdowns
5. Added cleanup logic to mark state as Dirty if tasks are interrupted
during service shutdown
6. Refactored status file handling to use shared helper functions

Log: Improved indexing service reliability with crash recovery mechanism

Influence:
1. Test service restart after clean shutdown - should skip global update
2. Test service restart after interrupted indexing - should trigger
global update
3. Verify state persistence across service restarts
4. Test index creation when no database exists (should proceed normally)
5. Verify task queue management with multiple indexing operations
6. Test service shutdown during active indexing operations

feat: 添加索引状态跟踪以支持崩溃恢复

新增索引状态管理功能,用于跟踪索引操作是否正常完成或被中断。这使服务重启
时能够智能恢复,避免在索引正常关闭时执行不必要的全局更新。

1. 添加包含 Clean、Dirty 和 Unknown 状态的 IndexState 枚举
2. 在索引状态文件中实现状态持久化,使用 JSON 序列化
3. TaskManager 在启动任务时将状态标记为 Dirty,所有任务成功完成后标记
为 Clean
4. TextIndexDBus 在静默启动时检查状态,对于干净关闭跳过全局更新
5. 添加清理逻辑,在服务关闭时如果任务被中断则将状态标记为 Dirty
6. 重构状态文件处理以使用共享辅助函数

Log: 通过崩溃恢复机制提升索引服务可靠性

Influence:
1. 测试服务在干净关闭后重启 - 应跳过全局更新
2. 测试服务在索引中断后重启 - 应触发全局更新
3. 验证状态在服务重启间的持久性
4. 测试不存在数据库时的索引创建(应正常进行)
5. 验证多索引操作的任务队列管理
6. 测试在活跃索引操作期间的服务关闭
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 31, 2025

Reviewer's Guide

Introduces an IndexState crash-recovery mechanism backed by the existing JSON status file, wires it into the TaskManager lifecycle and TextIndexDBus startup/shutdown paths, and refactors status-file reads/writes through shared helpers so the index can skip unnecessary global updates after a clean shutdown while forcing updates after interrupted work.

Sequence diagram for silent startup index recovery behavior

sequenceDiagram
actor Service
participant TextIndexDBus
participant Private
participant TaskManager
participant IndexUtility

Service->>TextIndexDBus: handleSlientStart()
TextIndexDBus->>Private: execute_silent_start_lambda()
Private->>TextIndexDBus: IndexDatabaseExists()
alt database does not exist
  Private->>TaskManager: startTask(Create, pathsToProcess, true)
  TaskManager->>IndexUtility: setIndexState(Dirty)
else database exists
  Private->>IndexUtility: getIndexState()
  alt state is Clean
    Private-->>Service: skip_global_update()
  else state Dirty or Unknown
    Private->>TaskManager: startTask(Update, pathsToProcess, true)
    TaskManager->>IndexUtility: setIndexState(Dirty)
  end
end
Loading

Sequence diagram for cleanup marking dirty state on unfinished work

sequenceDiagram
actor Service
participant TextIndexDBus
participant Private
participant TaskManager
participant IndexUtility

Service->>TextIndexDBus: cleanup()
TextIndexDBus->>Private: fsEventController.setEnabledNow(false)
TextIndexDBus->>TaskManager: hasRunningTask()
TaskManager-->>TextIndexDBus: runningFlag
TextIndexDBus->>TaskManager: hasQueuedTasks()
TaskManager-->>TextIndexDBus: queuedFlag
alt unfinished work
  TextIndexDBus->>IndexUtility: setIndexState(Dirty)
end
TextIndexDBus->>TextIndexDBus: StopCurrentTask()
Loading

State diagram for IndexState crash-recovery lifecycle

stateDiagram-v2
[*] --> Unknown

Unknown --> Dirty: indexing_started
Clean --> Dirty: indexing_started
Dirty --> Clean: all_tasks_completed_successfully
Dirty --> Dirty: interrupted_shutdown
Unknown --> Clean: clean_status_written
Loading

File-Level Changes

Change Details Files
Add index crash-recovery state machine persisted in the JSON status file and shared helpers for reading/writing it.
  • Introduce IndexUtility::IndexState enum (Clean/Dirty/Unknown) and related accessors in indexutility.h/.cpp.
  • Implement readStatusJson()/writeStatusJson() helpers in indexutility.cpp to centralize JSON status-file IO and directory creation.
  • Persist the new state field using Defines::kStateKey, kStateClean, and kStateDirty in service_textindex_global.h.
  • Refactor saveIndexStatus(), getLastUpdateTime(), and getIndexVersion() to use the shared JSON helpers and preserve/augment existing fields.
src/services/textindex/utils/indexutility.h
src/services/textindex/utils/indexutility.cpp
src/services/textindex/service_textindex_global.h
Wire index state transitions into task lifecycle to mark the index Dirty on work start and Clean only after all tasks finish successfully.
  • In TaskManager::startTask(), startFileListTask(), and startFileMoveTask(), set state to Dirty before launching worker-thread tasks.
  • In TaskManager::onTaskFinished(), set state to Clean only when the handler result is successful and not interrupted, and add logging for the transition.
  • Expose hasQueuedTasks() so other components can determine whether there is unfinished work.
src/services/textindex/task/taskmanager.h
src/services/textindex/task/taskmanager.cpp
Use the persisted index state during D-Bus silent startup and service shutdown to decide whether to run a global update or only create tasks.
  • In TextIndexDBusPrivate::handleSlientStart(), first check if the index database exists; if not, start a Create task and return.
  • If the index exists, read IndexUtility::IndexState to skip the global Update task when state is Clean, or trigger Update when state is Dirty or Unknown, with clearer logging for each branch.
  • In TextIndexDBus::cleanup(), before stopping tasks, mark the index state Dirty when there are running or queued tasks to reflect an unclean shutdown.
src/services/textindex/textindexdbus.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

Copy link

@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 1 issue, and left some high level feedback:

  • Consider using QSaveFile (or a similar atomic write mechanism) in writeStatusJson instead of QFile::WriteOnly to avoid leaving a partially written/invalid status file if the process crashes during the write.
  • getIndexState silently returns Unknown for missing/invalid state values; adding a debug or warning log in this path would make it easier to diagnose legacy/corrupted status files and understand why a global update is being triggered.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider using QSaveFile (or a similar atomic write mechanism) in writeStatusJson instead of QFile::WriteOnly to avoid leaving a partially written/invalid status file if the process crashes during the write.
- getIndexState silently returns Unknown for missing/invalid state values; adding a debug or warning log in this path would make it easier to diagnose legacy/corrupted status files and understand why a global update is being triggered.

## Individual Comments

### Comment 1
<location> `src/services/textindex/utils/indexutility.cpp:37-46` </location>
<code_context>
+}
+
+// Internal helper: Write status JSON object to file
+bool writeStatusJson(const QJsonObject &obj)
+{
+    QFile file(statusFilePath());
+    QDir().mkpath(QFileInfo(file).absolutePath());
+
+    if (!file.open(QIODevice::WriteOnly)) {
+        fmWarning() << "Failed to write index status to:" << file.fileName();
+        return false;
+    }
+
+    file.write(QJsonDocument(obj).toJson());
+    file.close();
+    return true;
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider handling JSON serialization/parsing errors and logging them explicitly.

Right now malformed JSON is treated as an empty object in `readStatusJson()`, and `writeStatusJson()` assumes `QJsonDocument(obj).toJson()` always succeeds. If the status file is corrupt, we silently drop all fields and overwrite the file with no diagnostics. Please consider checking `QJsonParseError` in `fromJson` and logging invalid content so operators can distinguish "file missing" from "file corrupt" and investigate accordingly.

Suggested implementation:

```cpp
    QJsonParseError parseError;
    const QByteArray data = file.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
    file.close();

    if (parseError.error != QJsonParseError::NoError) {
        fmWarning() << "Failed to parse index status JSON from:" << file.fileName()
                    << "-" << parseError.errorString();
        return QJsonObject();
    }

    if (!doc.isObject()) {
        fmWarning() << "Index status JSON root is not an object in:" << file.fileName();
        return QJsonObject();
    }

    return doc.object();
}

// Internal helper: Write status JSON object to file
bool writeStatusJson(const QJsonObject &obj)
{
    QFile file(statusFilePath());
    QDir().mkpath(QFileInfo(file).absolutePath());

    if (!file.open(QIODevice::WriteOnly)) {
        fmWarning() << "Failed to write index status to:" << file.fileName();
        return false;
    }

    const QByteArray data = QJsonDocument(obj).toJson();
    const qint64 bytesWritten = file.write(data);
    file.close();

    if (bytesWritten != data.size()) {
        fmWarning() << "Failed to fully write index status to:" << file.fileName()
                    << "- wrote" << bytesWritten << "of" << data.size() << "bytes";
        return false;
    }

    return true;
}

```

If `QJsonParseError` is not already included in this compilation unit (directly or transitively), add `#include <QJsonParseError>` near the other Qt JSON includes at the top of `indexutility.cpp`.
</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 +37 to +46
bool writeStatusJson(const QJsonObject &obj)
{
QFile file(statusFilePath());
QDir().mkpath(QFileInfo(file).absolutePath());

if (!file.open(QIODevice::WriteOnly)) {
fmWarning() << "Failed to write index status to:" << file.fileName();
return false;
}

Copy link

Choose a reason for hiding this comment

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

suggestion (bug_risk): Consider handling JSON serialization/parsing errors and logging them explicitly.

Right now malformed JSON is treated as an empty object in readStatusJson(), and writeStatusJson() assumes QJsonDocument(obj).toJson() always succeeds. If the status file is corrupt, we silently drop all fields and overwrite the file with no diagnostics. Please consider checking QJsonParseError in fromJson and logging invalid content so operators can distinguish "file missing" from "file corrupt" and investigate accordingly.

Suggested implementation:

    QJsonParseError parseError;
    const QByteArray data = file.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
    file.close();

    if (parseError.error != QJsonParseError::NoError) {
        fmWarning() << "Failed to parse index status JSON from:" << file.fileName()
                    << "-" << parseError.errorString();
        return QJsonObject();
    }

    if (!doc.isObject()) {
        fmWarning() << "Index status JSON root is not an object in:" << file.fileName();
        return QJsonObject();
    }

    return doc.object();
}

// Internal helper: Write status JSON object to file
bool writeStatusJson(const QJsonObject &obj)
{
    QFile file(statusFilePath());
    QDir().mkpath(QFileInfo(file).absolutePath());

    if (!file.open(QIODevice::WriteOnly)) {
        fmWarning() << "Failed to write index status to:" << file.fileName();
        return false;
    }

    const QByteArray data = QJsonDocument(obj).toJson();
    const qint64 bytesWritten = file.write(data);
    file.close();

    if (bytesWritten != data.size()) {
        fmWarning() << "Failed to fully write index status to:" << file.fileName()
                    << "- wrote" << bytesWritten << "of" << data.size() << "bytes";
        return false;
    }

    return true;
}

If QJsonParseError is not already included in this compilation unit (directly or transitively), add #include <QJsonParseError> near the other Qt JSON includes at the top of indexutility.cpp.

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.

1 participant