fix(cqrs): 收敛 benchmark review 收尾问题

- 修复 benchmark workflow 过滤器输入的 shell 注入风险

- 统一 request 与 stream invoker 基准中 MediatR handler 的生命周期基线

- 更新 request pipeline benchmark 的缓存清理与空行为类型声明

- 压缩 cqrs-rewrite active 跟踪与 trace,记录本轮 PR review 收尾结论
This commit is contained in:
gewuyou 2026-05-06 12:57:56 +08:00
parent 2cb6216d05
commit 6d619b9a1f
6 changed files with 50 additions and 77 deletions

View File

@ -53,10 +53,12 @@ jobs:
- name: Run filtered benchmarks - name: Run filtered benchmarks
if: ${{ inputs.benchmark_filter != '' }} if: ${{ inputs.benchmark_filter != '' }}
env:
BENCHMARK_FILTER: ${{ inputs.benchmark_filter }}
run: | run: |
set -euo pipefail set -euo pipefail
dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- \ 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 - name: Upload BenchmarkDotNet artifacts
if: ${{ always() && inputs.benchmark_filter != '' }} if: ${{ always() && inputs.benchmark_filter != '' }}

View File

@ -93,7 +93,7 @@ public class RequestInvokerBenchmarks
configure: null, configure: null,
typeof(RequestInvokerBenchmarks), typeof(RequestInvokerBenchmarks),
static candidateType => candidateType == typeof(MediatRBenchmarkRequestHandler), static candidateType => candidateType == typeof(MediatRBenchmarkRequestHandler),
ServiceLifetime.Singleton); ServiceLifetime.Transient);
_mediatr = _serviceProvider.GetRequiredService<IMediator>(); _mediatr = _serviceProvider.GetRequiredService<IMediator>();
} }

View File

@ -64,6 +64,7 @@ public class RequestPipelineBenchmarks
MinLevel = LogLevel.Fatal MinLevel = LogLevel.Fatal
}; };
Fixture.Setup("RequestPipeline", handlerCount: 1, pipelineCount: PipelineCount); Fixture.Setup("RequestPipeline", handlerCount: 1, pipelineCount: PipelineCount);
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
_baselineHandler = new BenchmarkRequestHandler(); _baselineHandler = new BenchmarkRequestHandler();
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container => _container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
@ -101,6 +102,7 @@ public class RequestPipelineBenchmarks
public void Cleanup() public void Cleanup()
{ {
_serviceProvider.Dispose(); _serviceProvider.Dispose();
BenchmarkDispatcherCacheHelper.ClearDispatcherCaches();
} }
/// <summary> /// <summary>
@ -261,20 +263,28 @@ public class RequestPipelineBenchmarks
/// <summary> /// <summary>
/// pipeline 行为槽位 1。 /// pipeline 行为槽位 1。
/// </summary> /// </summary>
public sealed class BenchmarkPipelineBehavior1 : BenchmarkPipelineBehaviorBase; public sealed class BenchmarkPipelineBehavior1 : BenchmarkPipelineBehaviorBase
{
}
/// <summary> /// <summary>
/// pipeline 行为槽位 2。 /// pipeline 行为槽位 2。
/// </summary> /// </summary>
public sealed class BenchmarkPipelineBehavior2 : BenchmarkPipelineBehaviorBase; public sealed class BenchmarkPipelineBehavior2 : BenchmarkPipelineBehaviorBase
{
}
/// <summary> /// <summary>
/// pipeline 行为槽位 3。 /// pipeline 行为槽位 3。
/// </summary> /// </summary>
public sealed class BenchmarkPipelineBehavior3 : BenchmarkPipelineBehaviorBase; public sealed class BenchmarkPipelineBehavior3 : BenchmarkPipelineBehaviorBase
{
}
/// <summary> /// <summary>
/// pipeline 行为槽位 4。 /// pipeline 行为槽位 4。
/// </summary> /// </summary>
public sealed class BenchmarkPipelineBehavior4 : BenchmarkPipelineBehaviorBase; public sealed class BenchmarkPipelineBehavior4 : BenchmarkPipelineBehaviorBase
{
}
} }

View File

@ -93,7 +93,7 @@ public class StreamInvokerBenchmarks
configure: null, configure: null,
typeof(StreamInvokerBenchmarks), typeof(StreamInvokerBenchmarks),
static candidateType => candidateType == typeof(MediatRBenchmarkStreamHandler), static candidateType => candidateType == typeof(MediatRBenchmarkStreamHandler),
ServiceLifetime.Singleton); ServiceLifetime.Transient);
_mediatr = _serviceProvider.GetRequiredService<IMediator>(); _mediatr = _serviceProvider.GetRequiredService<IMediator>();
} }

View File

@ -31,10 +31,10 @@ CQRS 迁移与收敛。
## 当前活跃事实 ## 当前活跃事实
- 当前分支对应 `PR #326`,状态为 `OPEN` - 当前分支对应 `PR #326`,状态为 `OPEN`
- latest-head review 已从 benchmark 运行级缺陷收敛到剩余文档入口与是否继续接受 benchmark 语义细化的判断 - latest-head review 现仍有少量 open thread但本地复核后仍成立的问题已收敛到 benchmark 对照公平性、workflow 输入安全性与 active 文档压缩
- benchmark 场景现统一通过 `BenchmarkHostFactory` 构建最小宿主GFramework 侧在 runtime 分发前显式 `Freeze()` 容器MediatR 侧只扫描当前场景需要的 handler / behavior 类型 - benchmark 场景现统一通过 `BenchmarkHostFactory` 构建最小宿主GFramework 侧在 runtime 分发前显式 `Freeze()` 容器MediatR 侧只扫描当前场景需要的 handler / behavior 类型
- `RequestStartupBenchmarks` 已恢复 `ColdStart_GFrameworkCqrs` 结果产出,不再命中 `No CQRS request handler registered` - `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 - 远端 `CTRF` 最新汇总为 `2274/2274` passed
- `MegaLinter` 当前只暴露 `dotnet-format``Restore operation failed` 环境噪音,尚未提供本地仍成立的文件级格式诊断 - `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` - `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` 提示 - 备注:`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` - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`
- 结果:通过,`0 warning / 0 error` - 结果:通过,`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 <temporary-json-output>` - `python3 .agents/skills/gframework-pr-review/scripts/fetch_current_pr_review.py --format json --json-output <temporary-json-output>`
- 结果:通过 - 结果:通过
- 备注:确认当前分支对应 `PR #326`,本轮剩余 open AI feedback 主要集中在 benchmark 对照语义与 `ai-plan` 结构收敛 - 备注:确认当前分支对应 `PR #326`,本轮剩余 open AI feedback 以 workflow 输入安全、benchmark 对照公平性与 active 文档压缩为主
- `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=<worktree-git-dir> GIT_WORK_TREE=<worktree-root> 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
- `python3 scripts/license-header.py --check` - `python3 scripts/license-header.py --check`
- 结果:通过 - 结果:通过
- 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行 - 备注:当前 WSL worktree 需要显式绑定 `GIT_DIR` / `GIT_WORK_TREE` 后运行
@ -126,9 +64,9 @@ CQRS 迁移与收敛。
## 下一推荐步骤 ## 下一推荐步骤
1. 继续处理 `PR #326` 的剩余 review 收尾,优先保持 benchmark 对照语义与 `ai-plan` active 入口一致 1. 重新运行 `$gframework-pr-review`,确认本轮 workflow / benchmark / active 文档修复是否已消化当前 latest-head open threads
2. 决定是否继续细化 `RequestStartupBenchmarks` 的 cold-start harness降低 `InvocationCount=1` 带来的 `MinIterationTime` 提示噪音 2. `PR #326` 仍剩基准语义类反馈,优先判断它们属于真实对照偏差还是有意保留的 benchmark 设计取舍
3. 若需要在 CI 中手动复核 benchmark优先使用新增 workflow 的 `benchmark_filter` 输入按场景筛选,避免默认运行整个 benchmark 矩阵 3. 若需要在 CI 中手动复核 benchmark继续使用 workflow 的 `benchmark_filter` 输入按场景筛选,避免默认运行整个 benchmark 矩阵
## 活跃文档 ## 活跃文档

View File

@ -44,6 +44,29 @@
- 结果:通过 - 结果:通过
- 备注steady-state request 对照可正常运行,未再触发 MediatR 重复注册或 GFramework 首次解析失败 - 备注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 ## 2026-04-30
### 阶段:历史 PR #307 active 入口收敛CQRS-REWRITE-RP-076 ### 阶段:历史 PR #307 active 入口收敛CQRS-REWRITE-RP-076
@ -257,7 +280,7 @@
1. 若 review 重新触发后仍有 latest-head open thread继续以 `PR #323` 为当前唯一 PR 恢复锚点复核 1. 若 review 重新触发后仍有 latest-head open thread继续以 `PR #323` 为当前唯一 PR 恢复锚点复核
2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支 2. 后续若继续推进代码切片,优先复核基础 generation gate 之外的 runtime contract 或 fallback selection 分支
## 2026-05-06 ## 2026-05-06RP-083 ~ RP-089
### 阶段mixed invoker provider 排序回归CQRS-REWRITE-RP-083 ### 阶段mixed invoker provider 排序回归CQRS-REWRITE-RP-083