From 6d619b9a1fd642cb47c0cda6f88caa3b8935a2e7 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Wed, 6 May 2026 12:57:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(cqrs):=20=E6=94=B6=E6=95=9B=20benchmark=20r?= =?UTF-8?q?eview=20=E6=94=B6=E5=B0=BE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 benchmark workflow 过滤器输入的 shell 注入风险 - 统一 request 与 stream invoker 基准中 MediatR handler 的生命周期基线 - 更新 request pipeline benchmark 的缓存清理与空行为类型声明 - 压缩 cqrs-rewrite active 跟踪与 trace,记录本轮 PR review 收尾结论 --- .github/workflows/benchmark.yml | 4 +- .../Messaging/RequestInvokerBenchmarks.cs | 2 +- .../Messaging/RequestPipelineBenchmarks.cs | 18 ++++- .../Messaging/StreamInvokerBenchmarks.cs | 2 +- .../todos/cqrs-rewrite-migration-tracking.md | 76 ++----------------- .../traces/cqrs-rewrite-migration-trace.md | 25 +++++- 6 files changed, 50 insertions(+), 77 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 76b32324..c89b2382 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -53,10 +53,12 @@ jobs: - name: Run filtered benchmarks if: ${{ inputs.benchmark_filter != '' }} + env: + BENCHMARK_FILTER: ${{ inputs.benchmark_filter }} run: | set -euo pipefail dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- \ - --filter "${{ inputs.benchmark_filter }}" + --filter "$BENCHMARK_FILTER" - name: Upload BenchmarkDotNet artifacts if: ${{ always() && inputs.benchmark_filter != '' }} diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs index d026ff25..a943e095 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs @@ -93,7 +93,7 @@ public class RequestInvokerBenchmarks configure: null, typeof(RequestInvokerBenchmarks), static candidateType => candidateType == typeof(MediatRBenchmarkRequestHandler), - ServiceLifetime.Singleton); + ServiceLifetime.Transient); _mediatr = _serviceProvider.GetRequiredService(); } diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs index 058019cd..ece2b977 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs @@ -64,6 +64,7 @@ public class RequestPipelineBenchmarks MinLevel = LogLevel.Fatal }; Fixture.Setup("RequestPipeline", handlerCount: 1, pipelineCount: PipelineCount); + BenchmarkDispatcherCacheHelper.ClearDispatcherCaches(); _baselineHandler = new BenchmarkRequestHandler(); _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => @@ -101,6 +102,7 @@ public class RequestPipelineBenchmarks public void Cleanup() { _serviceProvider.Dispose(); + BenchmarkDispatcherCacheHelper.ClearDispatcherCaches(); } /// @@ -261,20 +263,28 @@ public class RequestPipelineBenchmarks /// /// pipeline 行为槽位 1。 /// - public sealed class BenchmarkPipelineBehavior1 : BenchmarkPipelineBehaviorBase; + public sealed class BenchmarkPipelineBehavior1 : BenchmarkPipelineBehaviorBase + { + } /// /// pipeline 行为槽位 2。 /// - public sealed class BenchmarkPipelineBehavior2 : BenchmarkPipelineBehaviorBase; + public sealed class BenchmarkPipelineBehavior2 : BenchmarkPipelineBehaviorBase + { + } /// /// pipeline 行为槽位 3。 /// - public sealed class BenchmarkPipelineBehavior3 : BenchmarkPipelineBehaviorBase; + public sealed class BenchmarkPipelineBehavior3 : BenchmarkPipelineBehaviorBase + { + } /// /// pipeline 行为槽位 4。 /// - public sealed class BenchmarkPipelineBehavior4 : BenchmarkPipelineBehaviorBase; + public sealed class BenchmarkPipelineBehavior4 : BenchmarkPipelineBehaviorBase + { + } } diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs index bfec83f0..d6ce01ba 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs @@ -93,7 +93,7 @@ public class StreamInvokerBenchmarks configure: null, typeof(StreamInvokerBenchmarks), static candidateType => candidateType == typeof(MediatRBenchmarkStreamHandler), - ServiceLifetime.Singleton); + ServiceLifetime.Transient); _mediatr = _serviceProvider.GetRequiredService(); } diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md index ddef2523..f7b6f7b9 100644 --- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md +++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md @@ -31,10 +31,10 @@ CQRS 迁移与收敛。 ## 当前活跃事实 - 当前分支对应 `PR #326`,状态为 `OPEN` -- latest-head review 已从 benchmark 运行级缺陷收敛到剩余文档入口与是否继续接受 benchmark 语义细化的判断 +- latest-head review 现仍有少量 open thread,但本地复核后,仍成立的问题已收敛到 benchmark 对照公平性、workflow 输入安全性与 active 文档压缩 - benchmark 场景现统一通过 `BenchmarkHostFactory` 构建最小宿主:GFramework 侧在 runtime 分发前显式 `Freeze()` 容器,MediatR 侧只扫描当前场景需要的 handler / behavior 类型 - `RequestStartupBenchmarks` 已恢复 `ColdStart_GFrameworkCqrs` 结果产出,不再命中 `No CQRS request handler registered` -- 已新增手动触发的 benchmark workflow;默认只验证 benchmark 项目 Release build,只有显式提供过滤器时才执行 BenchmarkDotNet 运行 +- 已新增手动触发的 benchmark workflow;默认只验证 benchmark 项目 Release build,只有显式提供过滤器时才执行 BenchmarkDotNet 运行;过滤器输入现通过环境变量传入 shell,避免 workflow_dispatch 输入直接插值到命令行 - 远端 `CTRF` 最新汇总为 `2274/2274` passed - `MegaLinter` 当前只暴露 `dotnet-format` 的 `Restore operation failed` 环境噪音,尚未提供本地仍成立的文件级格式诊断 @@ -50,74 +50,12 @@ CQRS 迁移与收敛。 - `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestStartupBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` - 结果:通过 - 备注:`ColdStart_GFrameworkCqrs` 已恢复出数,最新本地输出约 `220-292 us`,MediatR 对照约 `575-616 us`;当前仅剩 BenchmarkDotNet 对单次 cold-start 场景的 `MinIterationTime` 提示 -- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` - - 结果:通过 - - 备注:确认冻结后的 GFramework 最小宿主与受限扫描的 MediatR 最小宿主均可完成 steady-state request 对照 - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - 结果:通过,`0 warning / 0 error` - - 备注:用于验证新增手动 benchmark workflow 依赖的 benchmark 项目入口仍可在 Release 下编译 + - 备注:用于验证本轮 request invoker / pipeline / stream invoker 调整与 benchmark workflow 改动后的 Release 编译结果 - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output ` - 结果:通过 - - 备注:确认当前分支对应 `PR #326`,本轮剩余 open AI feedback 主要集中在 benchmark 对照语义与 `ai-plan` 结构收敛 -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 -- `git diff --check` - - 结果:通过 -- `dotnet build GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release` - - 结果:通过,`0 warning / 0 error` -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Request_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_In_Stable_Order_For_Mixed_Direct_And_Reflected_Implementations"` - - 结果:通过,`2/2` passed -- `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - - 结果:通过,`0 warning / 0 error` - - 备注:先后覆盖 `StreamingBenchmarks`、`RequestPipelineBenchmarks`、`RequestStartupBenchmarks`、`RequestInvokerBenchmarks` 与 `StreamInvokerBenchmarks` 的引入后复核 -- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release -- --filter "*RequestStartupBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` - - 结果:部分通过 - - 备注:`Initialization_MediatR` 与 `ColdStart_MediatR` 已可实际运行;`ColdStart_GFrameworkCqrs` 仍因 `No CQRS request handler registered` 无法产出完整对照 -- `GIT_DIR= GIT_WORK_TREE= python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行,避免脚本内部 plain `git ls-files` 误判仓库上下文 -- `git diff --check` - - 结果:通过 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Enumerator|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Stream_Invoker_Provider_Metadata_When_Runtime_Lacks_Stream_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_Stream_Invoker_Provider_Metadata_When_Runtime_Contract_Is_Available"` - - 结果:通过,`5/5` passed -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Entry_Type|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Provider_Interface|FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Emit_Request_Invoker_Provider_Metadata_When_Runtime_Lacks_Request_Descriptor_Enumerator"` - - 结果:通过,`4/4` passed -- `dotnet build GFramework.Cqrs.SourceGenerators/GFramework.Cqrs.SourceGenerators.csproj -c Release` - - 结果:通过,`0 warning / 0 error` -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行,避免脚本内部 plain `git ls-files` 误判仓库上下文 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Emits_String_Fallback_Metadata_For_Mixed_Fallback_When_Runtime_Disallows_Multiple_Fallback_Attributes"` - - 结果:通过,`1/1` passed -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 -- `git diff --check` - - 结果:通过 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Handler_Registry_Interface"` - - 结果:通过,`1/1` passed -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 -- `git diff --check` - - 结果:通过 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"` - - 结果:通过,`4/4` passed -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 -- `git diff --check` - - 结果:通过 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"` - - 结果:通过,`6/6` passed -- `python3 scripts/license-header.py --check` - - 结果:通过 - - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 -- `git diff --check` - - 结果:通过 -- `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests.Does_Not_Generate_Registry_When_Runtime_Lacks_Required_Generation_Contract"` - - 结果:通过,`7/7` passed + - 备注:确认当前分支对应 `PR #326`,本轮剩余 open AI feedback 以 workflow 输入安全、benchmark 对照公平性与 active 文档压缩为主 - `python3 scripts/license-header.py --check` - 结果:通过 - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 @@ -126,9 +64,9 @@ CQRS 迁移与收敛。 ## 下一推荐步骤 -1. 继续处理 `PR #326` 的剩余 review 收尾,优先保持 benchmark 对照语义与 `ai-plan` active 入口一致 -2. 决定是否继续细化 `RequestStartupBenchmarks` 的 cold-start harness,降低 `InvocationCount=1` 带来的 `MinIterationTime` 提示噪音 -3. 若需要在 CI 中手动复核 benchmark,优先使用新增 workflow 的 `benchmark_filter` 输入按场景筛选,避免默认运行整个 benchmark 矩阵 +1. 重新运行 `$gframework-pr-review`,确认本轮 workflow / benchmark / active 文档修复是否已消化当前 latest-head open threads +2. 若 `PR #326` 仍剩基准语义类反馈,优先判断它们属于真实对照偏差还是有意保留的 benchmark 设计取舍 +3. 若需要在 CI 中手动复核 benchmark,继续使用 workflow 的 `benchmark_filter` 输入按场景筛选,避免默认运行整个 benchmark 矩阵 ## 活跃文档 diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md index f3274372..4c0fb380 100644 --- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md +++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md @@ -44,6 +44,29 @@ - 结果:通过 - 备注:steady-state request 对照可正常运行,未再触发 MediatR 重复注册或 GFramework 首次解析失败 +### 阶段:PR #326 review 收尾补丁(CQRS-REWRITE-RP-090) + +- 再次使用 `$gframework-pr-review` 复核 `PR #326` latest-head open threads 后,主线程确认本轮仍成立且适合在当前 PR 内收敛的问题集中在四类: + - `.github/workflows/benchmark.yml` 的 `benchmark_filter` 直接插值到 shell,存在 workflow_dispatch 输入注入风险 + - `RequestInvokerBenchmarks` 与 `StreamInvokerBenchmarks` 的 MediatR handler 生命周期仍为 `Singleton`,与 GFramework 反射 / generated 路径的 transient 语义不一致 + - `RequestPipelineBenchmarks` 未在场景切换前后清理 dispatcher 缓存,且四个空 pipeline behavior 类型仍使用非法的分号类声明 + - `ai-plan/public/cqrs-rewrite` active 文档仍保留旧失败结论与重复日期标题,和“active 入口只保留最新权威恢复点”的约束不一致 +- 本轮刻意未扩展处理的 review: + - `MicrosoftDiContainer` 的释放契约建议会扩大到核心 Ioc 接口与全仓库生命周期语义,不适合作为 benchmark review 顺手改动 + - `RequestStartupBenchmarks` 的“手工单点注册 vs 受限程序集扫描”差异目前属于有意保留的最小宿主模型,代码注释已明确该设计边界 +- 已修改: + - `.github/workflows/benchmark.yml` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs` + - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` + - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` +- 预期结果: + - 手动 benchmark workflow 的过滤器输入不再直接参与 shell 解析 + - request / stream invoker 三路对照的 handler 生命周期重新回到同一基线 + - request pipeline benchmark 在 `0 / 1 / 4` 场景切换时不再复用旧 dispatcher cache + - active tracking / trace 更符合 boot 恢复入口所要求的“只保留最新权威结论”形状 + ## 2026-04-30 ### 阶段:历史 PR #307 active 入口收敛(CQRS-REWRITE-RP-076) @@ -257,7 +280,7 @@ 1. 若 review 重新触发后仍有 latest-head open thread,继续以 `PR #323` 为当前唯一 PR 恢复锚点复核 2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支 -## 2026-05-06 +## 2026-05-06(RP-083 ~ RP-089) ### 阶段:mixed invoker provider 排序回归(CQRS-REWRITE-RP-083)