Skip to content

fix(config): 单个配置项损坏时重置为默认值而非致命崩溃#3346

Open
NoClassFoundError wants to merge 2 commits into
PCL-Community:devfrom
NoClassFoundError:fix/config-corrupt-reset
Open

fix(config): 单个配置项损坏时重置为默认值而非致命崩溃#3346
NoClassFoundError wants to merge 2 commits into
PCL-Community:devfrom
NoClassFoundError:fix/config-corrupt-reset

Conversation

@NoClassFoundError

@NoClassFoundError NoClassFoundError commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

某个配置项的值损坏为非法内容时,JsonFileProvider.Get 会抛出 JsonException;因其不是ConfigFileInitException,ConfigService 会走到 Context.Fatal,导致每次启动都在读取该项 (如 ThemeConfigGroup.DarkColor)时崩溃、无法进入界面,且损坏项不会被重置(用户重置设置 也无效)。在 FileConfigStorage 的 Get 访问中兜住读取异常:记录警告、队列删除该损坏项以持久化 重置,并返回 false 回退到编码默认值,使启动可继续。整文件损坏的场景仍由 ConfigService 既有的 .failbackup/.bak 恢复逻辑处理。

resolves #3280

本PR的提交信息生成由Claude Code Opus 4.8辅助生成,由Opus 4.8对代码进行了审查。

Summary by Sourcery

Bug Fixes:

  • 通过将读取失败视为非致命错误并将该配置项重置为默认值,防止在读取单个损坏的配置条目时导致应用程序崩溃。
Original summary in English

Summary by Sourcery

Bug Fixes:

  • Prevent application crashes when reading a single corrupted configuration entry by treating read failures as non-fatal and resetting the entry to its default value.

某个配置项的值损坏为非法内容时,JsonFileProvider.Get<T> 会抛出 JsonException;因其不是ConfigFileInitException,ConfigService 会走到 Context.Fatal,导致每次启动都在读取该项
(如 ThemeConfigGroup.DarkColor)时崩溃、无法进入界面,且损坏项不会被重置(用户重置设置
也无效)。在 FileConfigStorage 的 Get 访问中兜住读取异常:记录警告、队列删除该损坏项以持久化
重置,并返回 false 回退到编码默认值,使启动可继续。整文件损坏的场景仍由 ConfigService
既有的 .failbackup/.bak 恢复逻辑处理。
@pcl-ce-automation pcl-ce-automation Bot added 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查 size: S PR 大小评估:小型 labels Jul 5, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
审阅者指南(在小型 PR 上折叠)

审阅者指南

此 PR 在读取单个由 JSON 支持的配置条目时添加了防御性处理,使损坏的配置值不再导致致命崩溃;相反,它们会被记录日志、安排删除,并在保持现有整文件恢复逻辑不变的同时,系统回退到默认值。

FileConfigStorage.OnAccess 中处理损坏配置条目的序列图

sequenceDiagram
    actor ConfigService
    participant FileConfigStorage
    participant File
    participant LogWrapper
    participant writeActionChannelWriter

    ConfigService->>FileConfigStorage: OnAccess(StorageAction.Get, key, value)
    alt file exists
        FileConfigStorage->>File: Get(key)
        alt [no exception]
            File-->>FileConfigStorage: TValue
            FileConfigStorage-->>ConfigService: true
        else [exception thrown]
            File--xFileConfigStorage: Exception
            FileConfigStorage->>LogWrapper: Warn(ex, Config, message)
            FileConfigStorage->>writeActionChannelWriter: TryWrite((key, File.Remove))
            FileConfigStorage-->>ConfigService: false (fallback to default)
        end
    else file does not exist
        FileConfigStorage-->>ConfigService: false
    end
Loading

文件级变更

变更 详情 文件
在读取单个配置条目时添加异常处理,使损坏的值被记录日志、异步移除,并导致回退到默认值,而不是引发致命崩溃。
  • 在处理 StorageAction.Get 时,将 File.Get(strKey) 调用包裹在 try/catch 中。
  • 在读取失败时,记录一条警告,指明具体的配置键可能已损坏并将被重置。
  • 通过 _writeActionChannel.Writer.TryWrite 入队一个写操作,以删除损坏的配置条目文件。
  • 在读取失败时返回 false,以便调用方可以回退到编码的默认值而不是崩溃。
  • 保持整配置文件损坏时的行为不变,使现有的 .failbackup/.bak 恢复逻辑继续适用。
PCL.Core/App/Configuration/Storage/FileConfigStorage.cs

与关联 Issue 的对应情况评估

Issue 目标 是否解决 说明
#3280 防止在某个配置条目损坏时启动器在启动阶段崩溃,从而仍能进入 UI。
#3280 自动将损坏的配置数据重置为默认值,而不是让它们保持在异常状态(包括在用户尝试重置设置时)。

提示与命令

与 Sourcery 交互

  • 触发新审阅: 在拉取请求上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审阅评论。
  • 从审阅评论生成 GitHub Issue: 通过回复某条审阅评论让 Sourcery 从该评论创建一个 Issue。你也可以在审阅评论中回复 @sourcery-ai issue 来从这条评论创建 Issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在拉取请求上评论 @sourcery-ai title 来(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置写上 @sourcery-ai summary,即可在你希望的位置随时生成 PR 摘要。你也可以在拉取请求上评论 @sourcery-ai summary 来(重新)生成摘要。
  • 生成审阅者指南: 在拉取请求上评论 @sourcery-ai guide,即可在任何时候(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论: 在拉取请求上评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理了所有评论且不想再看到它们,这非常有用。
  • 忽略所有 Sourcery 审阅: 在拉取请求上评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审阅。如果你希望从一次全新的审阅开始,这尤其有用——别忘了评论 @sourcery-ai review 来触发新的审阅!

自定义你的体验

访问你的控制面板以:

  • 启用或禁用审阅特性,例如 Sourcery 生成的拉取请求摘要、审阅者指南等。
  • 更改审阅语言。
  • 添加、移除或编辑自定义审阅说明。
  • 调整其他审阅设置。

获取帮助

Original review guide in English
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

This PR adds defensive handling around reading individual JSON-backed config entries so that corrupted values no longer cause a fatal crash; instead, they are logged, scheduled for deletion, and the system falls back to default values while existing whole-file recovery logic remains untouched.

Sequence diagram for corrupted config entry handling in FileConfigStorage.OnAccess

sequenceDiagram
    actor ConfigService
    participant FileConfigStorage
    participant File
    participant LogWrapper
    participant writeActionChannelWriter

    ConfigService->>FileConfigStorage: OnAccess(StorageAction.Get, key, value)
    alt file exists
        FileConfigStorage->>File: Get(key)
        alt [no exception]
            File-->>FileConfigStorage: TValue
            FileConfigStorage-->>ConfigService: true
        else [exception thrown]
            File--xFileConfigStorage: Exception
            FileConfigStorage->>LogWrapper: Warn(ex, Config, message)
            FileConfigStorage->>writeActionChannelWriter: TryWrite((key, File.Remove))
            FileConfigStorage-->>ConfigService: false (fallback to default)
        end
    else file does not exist
        FileConfigStorage-->>ConfigService: false
    end
Loading

File-Level Changes

Change Details Files
Add exception handling when reading a single config entry so that corrupted values are logged, removed asynchronously, and cause a fallback to defaults instead of a fatal crash.
  • Wrap the File.Get(strKey) call in a try/catch during StorageAction.Get handling.
  • On read failure, log a warning indicating the specific config key is likely corrupted and will be reset.
  • Enqueue a write action that removes the corrupted config entry file via _writeActionChannel.Writer.TryWrite.
  • Return false when a read fails so callers can fall back to encoded default values instead of crashing.
  • Leave behavior for whole-config-file corruption unchanged so existing .failbackup/.bak recovery logic continues to apply.
PCL.Core/App/Configuration/Storage/FileConfigStorage.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#3280 Prevent the launcher from crashing on startup when a configuration entry is corrupted so that the UI can be reached.
#3280 Automatically reset corrupted configuration data to default values instead of leaving them in a bad state (including when the user attempts to reset settings).

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并提供了一些总体反馈:

  • 建议将捕获的异常从泛型 Exception 收窄为你实际预期的反序列化异常(例如 JsonException),以避免掩盖无关的 I/O 或并发问题。
  • _writeActionChannel.Writer.TryWrite(...) 的返回结果目前被忽略;如果通道已满或不可用,损坏的配置项将无法被移除,因此建议处理返回值为 false 的情况,或者使用 WriteAsync 以确保最终能够删除。
  • 由于损坏配置的删除是通过通道延迟执行的,请确认这个异步写操作与其他配置写入之间的顺序是安全的,以避免出现竞争条件,即新的配置值被排队的删除操作覆盖。
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider narrowing the catch from `Exception` to the specific deserialization exceptions you expect (e.g., `JsonException`) to avoid masking unrelated I/O or concurrency issues.
- The result of `_writeActionChannel.Writer.TryWrite(...)` is ignored; if the channel is full or unavailable the corrupted config item will not be removed, so it may be worth handling the `false` case or using `WriteAsync` to ensure eventual deletion.
- Since removal of the corrupted config is deferred via the channel, confirm that this asynchronous write is ordered safely with other config writes to avoid race conditions where a fresh value might be overwritten by the queued removal.

## Individual Comments

### Comment 1
<location path="PCL.Core/App/Configuration/Storage/FileConfigStorage.cs" line_range="111-112" />
<code_context>
+                catch (Exception ex)
+                {
+                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
+                    _writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey)));
+                    return false;
+                }
                 return true;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Ignoring the TryWrite result may lead to silent failure to clean up corrupted configs.

If the channel is full or unavailable, `TryWrite` can fail and the corrupted file will remain without any indication. Consider handling the `false` return (e.g., logging, alternative cleanup, or synchronous removal) so enqueue failures are visible and cleanup is more reliable.

Suggested implementation:

```csharp
                catch (Exception ex)
                {
                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
                    if (!_writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey))))
                    {
                        // 通知队列写入失败,进行同步清理以避免损坏配置长期存在
                        LogWrapper.Warn(null, "Config", $"配置项 {strKey} 清理任务写入队列失败,尝试同步删除文件");
                        try
                        {
                            File.Remove(strKey);
                        }
                        catch (Exception cleanupEx)
                        {
                            LogWrapper.Error(cleanupEx, "Config", $"配置项 {strKey} 同步删除失败,可能需要人工处理");
                        }
                    }
                    return false;
                }

```

1. Ensure `LogWrapper.Warn(Exception ex, string category, string message)` and `LogWrapper.Error(Exception ex, string category, string message)` overloads exist and are used consistently. If `Warn` does not allow a null `Exception`, replace `LogWrapper.Warn(null, ...)` with an appropriate overload or a simple `LogWrapper.Warn("Config", "...")`.
2. If there is a centralized error-handling or metrics mechanism for storage failures, consider also reporting the synchronous cleanup failure there to keep observability consistent.
</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.
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Consider narrowing the catch from Exception to the specific deserialization exceptions you expect (e.g., JsonException) to avoid masking unrelated I/O or concurrency issues.
  • The result of _writeActionChannel.Writer.TryWrite(...) is ignored; if the channel is full or unavailable the corrupted config item will not be removed, so it may be worth handling the false case or using WriteAsync to ensure eventual deletion.
  • Since removal of the corrupted config is deferred via the channel, confirm that this asynchronous write is ordered safely with other config writes to avoid race conditions where a fresh value might be overwritten by the queued removal.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider narrowing the catch from `Exception` to the specific deserialization exceptions you expect (e.g., `JsonException`) to avoid masking unrelated I/O or concurrency issues.
- The result of `_writeActionChannel.Writer.TryWrite(...)` is ignored; if the channel is full or unavailable the corrupted config item will not be removed, so it may be worth handling the `false` case or using `WriteAsync` to ensure eventual deletion.
- Since removal of the corrupted config is deferred via the channel, confirm that this asynchronous write is ordered safely with other config writes to avoid race conditions where a fresh value might be overwritten by the queued removal.

## Individual Comments

### Comment 1
<location path="PCL.Core/App/Configuration/Storage/FileConfigStorage.cs" line_range="111-112" />
<code_context>
+                catch (Exception ex)
+                {
+                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
+                    _writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey)));
+                    return false;
+                }
                 return true;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Ignoring the TryWrite result may lead to silent failure to clean up corrupted configs.

If the channel is full or unavailable, `TryWrite` can fail and the corrupted file will remain without any indication. Consider handling the `false` return (e.g., logging, alternative cleanup, or synchronous removal) so enqueue failures are visible and cleanup is more reliable.

Suggested implementation:

```csharp
                catch (Exception ex)
                {
                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
                    if (!_writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey))))
                    {
                        // 通知队列写入失败,进行同步清理以避免损坏配置长期存在
                        LogWrapper.Warn(null, "Config", $"配置项 {strKey} 清理任务写入队列失败,尝试同步删除文件");
                        try
                        {
                            File.Remove(strKey);
                        }
                        catch (Exception cleanupEx)
                        {
                            LogWrapper.Error(cleanupEx, "Config", $"配置项 {strKey} 同步删除失败,可能需要人工处理");
                        }
                    }
                    return false;
                }

```

1. Ensure `LogWrapper.Warn(Exception ex, string category, string message)` and `LogWrapper.Error(Exception ex, string category, string message)` overloads exist and are used consistently. If `Warn` does not allow a null `Exception`, replace `LogWrapper.Warn(null, ...)` with an appropriate overload or a simple `LogWrapper.Warn("Config", "...")`.
2. If there is a centralized error-handling or metrics mechanism for storage failures, consider also reporting the synchronous cleanup failure there to keep observability consistent.
</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 thread PCL.Core/App/Configuration/Storage/FileConfigStorage.cs Outdated
@NoClassFoundError

Copy link
Copy Markdown
Contributor Author

Hey - 我发现了 1 个问题,并提供了一些总体反馈:

  • 建议将捕获的异常从泛型 Exception 收窄为你实际预期的反序列化异常(例如 JsonException),以避免掩盖无关的 I/O 或并发问题。
  • _writeActionChannel.Writer.TryWrite(...) 的返回结果目前被忽略;如果通道已满或不可用,损坏的配置项将无法被移除,因此建议处理返回值为 false 的情况,或者使用 WriteAsync 以确保最终能够删除。
  • 由于损坏配置的删除是通过通道延迟执行的,请确认这个异步写操作与其他配置写入之间的顺序是安全的,以避免出现竞争条件,即新的配置值被排队的删除操作覆盖。

Prompt for AI Agents

Please address the comments from this code review:

## Overall Comments
- Consider narrowing the catch from `Exception` to the specific deserialization exceptions you expect (e.g., `JsonException`) to avoid masking unrelated I/O or concurrency issues.
- The result of `_writeActionChannel.Writer.TryWrite(...)` is ignored; if the channel is full or unavailable the corrupted config item will not be removed, so it may be worth handling the `false` case or using `WriteAsync` to ensure eventual deletion.
- Since removal of the corrupted config is deferred via the channel, confirm that this asynchronous write is ordered safely with other config writes to avoid race conditions where a fresh value might be overwritten by the queued removal.

## Individual Comments

### Comment 1
<location path="PCL.Core/App/Configuration/Storage/FileConfigStorage.cs" line_range="111-112" />
<code_context>
+                catch (Exception ex)
+                {
+                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
+                    _writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey)));
+                    return false;
+                }
                 return true;
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Ignoring the TryWrite result may lead to silent failure to clean up corrupted configs.

If the channel is full or unavailable, `TryWrite` can fail and the corrupted file will remain without any indication. Consider handling the `false` return (e.g., logging, alternative cleanup, or synchronous removal) so enqueue failures are visible and cleanup is more reliable.

Suggested implementation:

```csharp
                catch (Exception ex)
                {
                    LogWrapper.Warn(ex, "Config", $"配置项 {strKey} 读取失败(可能已损坏),重置为默认值");
                    if (!_writeActionChannel.Writer.TryWrite((strKey, () => File.Remove(strKey))))
                    {
                        // 通知队列写入失败,进行同步清理以避免损坏配置长期存在
                        LogWrapper.Warn(null, "Config", $"配置项 {strKey} 清理任务写入队列失败,尝试同步删除文件");
                        try
                        {
                            File.Remove(strKey);
                        }
                        catch (Exception cleanupEx)
                        {
                            LogWrapper.Error(cleanupEx, "Config", $"配置项 {strKey} 同步删除失败,可能需要人工处理");
                        }
                    }
                    return false;
                }

  1. Ensure LogWrapper.Warn(Exception ex, string category, string message) and LogWrapper.Error(Exception ex, string category, string message) overloads exist and are used consistently. If Warn does not allow a null Exception, replace LogWrapper.Warn(null, ...) with an appropriate overload or a simple LogWrapper.Warn("Config", "...").
  2. If there is a centralized error-handling or metrics mechanism for storage failures, consider also reporting the synchronous cleanup failure there to keep observability consistent.
    </issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
* [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
* [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
* [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
* [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Original comment in English

1.全局 JsonFileProvider(损坏抛 JsonException)和本地 YamlFileProvider(Get 回退分支 (T)(object)graphStr 对非字符串类型会抛 InvalidCastException),另有反序列化为 null 时的 NullReferenceException。收窄到JsonException 会漏掉 YAML 与 null 情形而重新崩溃。且此路径是对已解析入内存的节点做反序列化、不涉及 I/O,不存在掩盖 I/O 问题的风险。
2.已解决
3.该竞态在实际中基本不可达。

{
value = File.Get<TValue>(strKey);
}
catch (Exception ex)

@RyogiMutsuki RyogiMutsuki Jul 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

应当只捕获相关错误,而不是 catch Exception

与配置系统不相关的错误不应当导致设置项被重置

@RyogiMutsuki

RyogiMutsuki commented Jul 6, 2026

Copy link
Copy Markdown
Member

另,根据 #3340 ,应当在提交 PR 时声明你已经对 AI 输出进行了审查

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size: S PR 大小评估:小型 🛠️ 等待审查 Pull Request 已完善,等待维护者或负责人进行代码审查

Projects

None yet

Development

Successfully merging this pull request may close these issues.

配置文件损坏时未正确重置

3 participants