fix(core): 收口 PR267 事件契约遗留问题

- 修复 AsyncLogAppender 接口刷新路径重复触发完成事件,并补充单次通知回归测试
- 补充 Architecture、CoroutineExceptionEventArgs 与阶段协调器的事件契约注释
- 更新 PhaseChanged 迁移文档与 analyzer-warning-reduction recovery 记录
This commit is contained in:
GeWuYou 2026-04-21 16:50:56 +08:00
parent 8831cb42a8
commit 685897f2de
10 changed files with 96 additions and 11 deletions

View File

@ -377,7 +377,7 @@ public class CoroutineSchedulerTests
var handle = _scheduler.Run(CreateExceptionCoroutine());
_scheduler.Update();
var observation = await exceptionSource.Task;
var observation = await exceptionSource.Task.WaitAsync(TimeSpan.FromSeconds(3));
Assert.Multiple(() =>
{

View File

@ -103,6 +103,23 @@ public class AsyncLogAppenderTests
});
}
[Test]
public void ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once()
{
var innerAppender = new TestAppender();
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 10);
ILogAppender logAppender = asyncAppender;
var observedResults = new List<bool>();
asyncAppender.Append(new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", "Interface flush check", null, null));
asyncAppender.OnFlushCompleted += (_, eventArgs) => observedResults.Add(eventArgs.Success);
logAppender.Flush();
Assert.That(observedResults, Has.Count.EqualTo(1));
Assert.That(observedResults, Has.All.True);
}
[Test]
public void Dispose_ShouldProcessRemainingEntries()
{

View File

@ -99,8 +99,16 @@ public abstract class Architecture : IArchitecture
public virtual Action<IServiceCollection>? Configurator => null;
/// <summary>
/// 阶段变更事件(用于测试和扩展)
/// 在架构生命周期阶段发生变化时触发。
/// </summary>
/// <remarks>
/// <para>
/// 订阅者应通过 <see cref="ArchitecturePhaseChangedEventArgs.Phase" /> 读取当前阶段,而不是依赖内部生命周期对象。
/// </para>
/// <para>
/// 事件委托中的 <c>sender</c> 始终为当前 <see cref="Architecture" /> 实例,便于测试与外部扩展保持稳定的发布者契约。
/// </para>
/// </remarks>
public event EventHandler<ArchitecturePhaseChangedEventArgs>? PhaseChanged;
#endregion

View File

@ -39,8 +39,8 @@ internal sealed class ArchitecturePhaseCoordinator(
/// <summary>
/// 进入指定阶段并广播给所有阶段消费者。
/// 顺序保持为“更新阶段值 → 生命周期钩子 → 容器中的阶段监听器 → 外部事件”,
/// 以兼容既有调用约定
/// 顺序保持为“更新阶段值 → 生命周期钩子 → 容器中的阶段监听器”,
/// 以保证框架扩展与运行时组件看到一致的阶段视图
/// </summary>
/// <param name="next">目标阶段。</param>
public void EnterPhase(ArchitecturePhase next)

View File

@ -11,6 +11,7 @@ public sealed class CoroutineExceptionEventArgs : EventArgs
/// </summary>
/// <param name="handle">发生异常的协程句柄。</param>
/// <param name="exception">协程执行过程中抛出的异常。</param>
/// <exception cref="ArgumentNullException"><paramref name="exception" /> 为 <see langword="null" />。</exception>
public CoroutineExceptionEventArgs(CoroutineHandle handle, Exception exception)
{
Handle = handle;

View File

@ -117,8 +117,7 @@ public sealed class AsyncLogAppender : ILogAppender
/// </summary>
void ILogAppender.Flush()
{
var success = Flush();
OnFlushCompleted?.Invoke(this, new AsyncLogFlushCompletedEventArgs(success));
Flush();
}
/// <summary>

View File

@ -7,11 +7,13 @@
## 当前恢复点
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-013`
- 当前阶段:`Phase 13`
- 恢复点编号:`ANALYZER-WARNING-REDUCTION-RP-014`
- 当前阶段:`Phase 14`
- 当前焦点:
- 当前 `MA0046` 主批次已在 `Architecture``AsyncLogAppender``CoroutineScheduler` 上收口完成
- 下一轮优先从 `MA0016``MA0002` 中选择低风险批次继续推进;`MA0015``MA0077` 继续作为尾项顺手吸收
- 当前分支 PR #267 的 latest CodeRabbit unresolved threads、outside-diff comment 与 nitpick comment 已完成本地复核
- 本轮优先收口仍然成立的 follow-up`AsyncLogAppender` 接口路径重复触发 `OnFlushCompleted`、事件/XML 契约缺口、
`PhaseChanged` 迁移文档说明与 `ai-plan` 基线表述歧义
- 下一轮默认恢复到 `MA0016``MA0002` 低风险批次;`MA0015``MA0077` 继续作为尾项顺手吸收
- `GFramework.Godot``Timing.cs` 已同步适配新事件签名,但当前 worktree 的 Godot restore 资产仍受 Windows fallback package folder 干扰,独立 build 需在修复资产后补跑
- 后续继续按 warning 类型和数量批处理,而不是回退到按单文件切片推进
- 当某一轮主类型数量不足时,允许顺手合并其他低冲突 warning 类型,`MA0015``MA0077`
@ -28,6 +30,8 @@
- 已增强 `gframework-pr-review` 脚本与 skill 文档,降低超长 JSON 直出导致的 review 信号漏看风险
- 已完成 `GFramework.Core` 当前 `MA0046` 批次:将阶段、协程与异步日志事件统一迁移到 `EventHandler<TEventArgs>` 形状,
并同步更新 `GFramework.Godot` 订阅点、定向测试与 `docs/zh-CN` 示例
- 已完成当前 PR #267 review follow-up修复 `AsyncLogAppender``ILogAppender.Flush()` 双重完成通知,并补齐
`PhaseChanged` / `CoroutineExceptionEventArgs` XML 文档、`PhaseChanged` 迁移说明和 `ai-plan` 基线注释
- 当前 `GFramework.Core` `net8.0` warnings-only 基线已降到 `9` 条;剩余 warning 集中在
`MA0016` 集合抽象接口、`MA0002` comparer 重载,以及 `MA0015` / `MA0077` 两个低数量尾项
@ -56,6 +60,8 @@
让“先落盘、再定向抽取”成为默认可操作路径
- `RP-013` 已完成 `GFramework.Core` 当前 `MA0046` 批次,并以新的事件参数类型替换阶段、协程和异步日志事件的
非标准签名;`GFramework.Core` `net8.0` warnings-only 基线由 `15` 降至 `9`
- `RP-014` 使用 `gframework-pr-review` 复核当前分支 PR #267 的 latest head review threads、outside-diff comment 与
nitpick comment 后,确认 8 条高信号项中仍成立的是 1 个行为 bug 与 7 个文档/测试/跟踪缺口,并按最小改动收口
- 当前工作树分支 `fix/analyzer-warning-reduction-batch` 已在 `ai-plan/public/README.md` 建立 topic 映射
## 当前风险
@ -130,11 +136,19 @@
- 结果:`0 Warning(s)``0 Error(s)`
- `RP-013` 的定向验证结果:
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo -clp:"Summary;WarningsOnly"`
- 结果:`9 Warning(s)``0 Error(s)`;当前 `GFramework.Core` `net8.0` warnings-only 输出中已不再出现 `MA0046`
- 结果:`9 Warning(s)``0 Error(s)`;相对 `RP-009` / `RP-011` 的 warnings-only 基线 `15 Warning(s)` 已降到 `9 Warning(s)`
当前 `GFramework.Core` `net8.0` 输出中已不再出现 `MA0046`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --filter "FullyQualifiedName~ArchitectureLifecycleBehaviorTests|FullyQualifiedName~CoroutineSchedulerTests|FullyQualifiedName~AsyncLogAppenderTests" -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`50 Passed``0 Failed`
- `dotnet build GFramework.Godot/GFramework.Godot.csproj -c Release --no-restore -p:RestoreFallbackFolders="" -nologo`
- 结果:失败;当前 worktree 的 Godot restore 资产仍引用 Windows fallback package folder尚未完成独立项目编译验证
- `RP-014` 的定向验证结果:
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
- 结果通过host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48`
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
- 结果:`9 Warning(s)``0 Error(s)``AsyncLogAppender` 行为修复与 XML / 文档补充未引入新的 `GFramework.Core` `net8.0` 构建错误
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`4 Passed``0 Failed`
- active 跟踪文件只保留当前恢复点、活跃事实、风险与下一步,不再重复保存已完成阶段的长篇历史
## 下一步

View File

@ -1,5 +1,37 @@
# Analyzer Warning Reduction 追踪
## 2026-04-21 — RP-014
### 阶段PR #267 review follow-up 收口RP-014
- 使用 `gframework-pr-review` 抓取当前分支 PR #267 的 latest head review threads、outside-diff comment、nitpick comment、
MegaLinter 摘要与测试报告,并确认本轮除了 6 条 open thread 之外,还存在 1 条 outside-diff 与 1 条 nitpick 需要一并复核
- 本地复核后确认仍成立的项:
- `AsyncLogAppender` 的显式接口实现 `ILogAppender.Flush()` 会在调用 `Flush()` 后再次手动触发 `OnFlushCompleted`
导致接口路径重复通知
- `Architecture.PhaseChanged``CoroutineExceptionEventArgs``ArchitecturePhaseCoordinator.EnterPhase` 的 XML/注释契约仍未完全同步
- `CoroutineSchedulerTests` 的异常事件测试缺少测试级超时
- `docs/zh-CN/core/architecture.md``docs/zh-CN/core/lifecycle.md` 仍缺少明确的 `PhaseChanged` 迁移说明
- `ai-plan` active tracking 中 `RP-013``9 Warning(s)` 需要明确是相对 `RP-009` / `RP-011` 的 warnings-only 基线收敛
- 实施最小修复:
- 删除 `ILogAppender.Flush()` 中重复的完成事件触发,只保留 `Flush(TimeSpan?)` 内的单一通知源
- 为接口调用路径补充单次完成通知回归测试,并为协程异常事件测试增加 `WaitAsync(TimeSpan.FromSeconds(3))`
- 补齐 `Architecture.PhaseChanged``CoroutineExceptionEventArgs``ArchitecturePhaseCoordinator.EnterPhase` 的契约文档
- 在 `docs/zh-CN/core/architecture.md``docs/zh-CN/core/lifecycle.md` 中加入 `phase => ...` 迁移到 `(_, args) => ...` 的说明
- 更新 `ai-plan/public/analyzer-warning-reduction/todos/analyzer-warning-reduction-tracking.md` 的恢复点、基线描述与验证结果
- 验证结果:
- `dotnet restore GFramework.Core.Tests/GFramework.Core.Tests.csproj -p:RestoreFallbackFolders="" -nologo`
- 结果通过host Windows `dotnet` 首次验证前补齐了缺失的 `Meziantou.Analyzer 3.0.48`
- `dotnet build GFramework.Core/GFramework.Core.csproj -c Release --no-restore -p:TargetFramework=net8.0 -p:RestoreFallbackFolders="" -nologo`
- 结果:`9 Warning(s)``0 Error(s)`
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~CoroutineSchedulerTests.Scheduler_Should_Raise_OnCoroutineException_With_EventArgs|FullyQualifiedName~AsyncLogAppenderTests.Flush_Should_Raise_OnFlushCompleted_With_Sender_And_Result|FullyQualifiedName~AsyncLogAppenderTests.ILogAppender_Flush_Should_Raise_OnFlushCompleted_Only_Once|FullyQualifiedName~ArchitectureLifecycleBehaviorTests.InitializeAsync_Should_Raise_PhaseChanged_With_Sender_And_EventArgs" -m:1 -p:RestoreFallbackFolders="" -nologo`
- 结果:`4 Passed``0 Failed`
- 当前结论:
- PR #267 里当前仍成立的 CodeRabbit 高信号项已在本地收口
- 修复内容没有改变 `EventHandler<TEventArgs>` 迁移方向,只是补齐行为、文档与恢复信息
- 下一步建议:
- 恢复到 `MA0016` / `MA0002` 主批次,默认先看 `LoggingConfiguration``FilterConfiguration``CollectionExtensions`
## 2026-04-21 — RP-013
### 阶段:`MA0046` 事件签名批次收口RP-013

View File

@ -126,10 +126,21 @@ protected override void OnInitialize()
其中 `PhaseChanged` 现在遵循标准 `EventHandler<ArchitecturePhaseChangedEventArgs>` 约定,
阶段值通过 `args.Phase` 读取。
如果你正在从旧版本迁移,需要把单参数写法 `phase => ...` 改成 `(_, args) => ...`
并通过 `ArchitecturePhaseChangedEventArgs.Phase` 读取阶段值。
如果你需要在 `Ready``Destroying` 等阶段执行横切逻辑,比起把这类逻辑塞进某个具体 `System`,更适合单独实现
`IArchitectureLifecycleHook`
```csharp
architecture.PhaseChanged += (_, args) =>
{
if (args.Phase == ArchitecturePhase.Ready)
{
Console.WriteLine("Architecture ready from event.");
}
};
public sealed class MetricsHook : IArchitectureLifecycleHook
{
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)

View File

@ -138,6 +138,9 @@ architecture.RegisterLifecycleHook(new MetricsHook());
如果你只需要观察阶段变化,也可以直接订阅:
如果你从旧版本的 `PhaseChanged` 迁移过来,需要把旧写法 `phase => ...` 改成 `(_, args) => ...`
并通过 `ArchitecturePhaseChangedEventArgs.Phase` 读取阶段值。
```csharp
architecture.PhaseChanged += (_, args) =>
{