From f346110a8af9b42de7b2d094d33a6e13cb8cfccb Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 13:17:52 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat(cqrs-benchmarks):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=20stream=20startup=20=E7=9A=84=20Mediator=20=E5=AF=B9=E7=85=A7?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 StreamStartupBenchmarks 的 Mediator 初始化与 cold-start benchmark。 - 更新 stream request 与 handler 契约以接入 NuGet Mediator source-generated concrete path。 --- .../Messaging/StreamStartupBenchmarks.cs | 60 +++++++++++++++++-- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs index 26947bbd..29a5cfb8 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs @@ -19,6 +19,7 @@ using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; using MediatR; using Microsoft.Extensions.DependencyInjection; +using GeneratedMediator = Mediator.Mediator; [assembly: GFramework.Cqrs.CqrsHandlerRegistryAttribute( typeof(GFramework.Cqrs.Benchmarks.Messaging.StreamStartupBenchmarks.GeneratedRegistry))] @@ -26,7 +27,7 @@ using Microsoft.Extensions.DependencyInjection; namespace GFramework.Cqrs.Benchmarks.Messaging; /// -/// 对比 stream 宿主在 GFramework.CQRS reflection / generated 与 MediatR 之间的初始化与首次建流命中成本。 +/// 对比 stream 宿主在 GFramework.CQRS reflection / generated、NuGet `Mediator` 与 MediatR 之间的初始化与首次建流命中成本。 /// /// /// 该场景与 保持相同的 `Initialization + ColdStart` 结构, @@ -45,7 +46,9 @@ public class StreamStartupBenchmarks private MicrosoftDiContainer _generatedContainer = null!; private ICqrsRuntime _generatedRuntime = null!; private ServiceProvider _serviceProvider = null!; + private ServiceProvider _mediatorServiceProvider = null!; private IMediator _mediatr = null!; + private GeneratedMediator _mediator = null!; /// /// 配置 stream startup benchmark 的公共输出格式。 @@ -67,7 +70,7 @@ public class StreamStartupBenchmarks } /// - /// 构建 startup benchmark 复用的 reflection / generated / MediatR 宿主对象。 + /// 构建 startup benchmark 复用的 reflection / generated / `Mediator` / MediatR 宿主对象。 /// [GlobalSetup] public void Setup() @@ -82,6 +85,8 @@ public class StreamStartupBenchmarks _serviceProvider = CreateMediatRServiceProvider(); _mediatr = _serviceProvider.GetRequiredService(); + _mediatorServiceProvider = CreateMediatorServiceProvider(); + _mediator = _mediatorServiceProvider.GetRequiredService(); } /// @@ -99,7 +104,7 @@ public class StreamStartupBenchmarks [GlobalCleanup] public void Cleanup() { - BenchmarkCleanupHelper.DisposeAll(_reflectionContainer, _generatedContainer, _serviceProvider); + BenchmarkCleanupHelper.DisposeAll(_reflectionContainer, _generatedContainer, _serviceProvider, _mediatorServiceProvider); } /// @@ -135,6 +140,17 @@ public class StreamStartupBenchmarks return _generatedRuntime; } + /// + /// 返回已构建宿主中的 `Mediator` concrete mediator,作为 source-generated concrete path 的初始化句柄。 + /// + /// 当前 benchmark 复用的 `Mediator` concrete mediator。 + [Benchmark] + [BenchmarkCategory("Initialization")] + public GeneratedMediator Initialization_Mediator() + { + return _mediator; + } + /// /// 在新宿主上首次创建并推进 stream,作为 MediatR 的 cold-start baseline。 /// @@ -180,6 +196,19 @@ public class StreamStartupBenchmarks .ConfigureAwait(false); } + /// + /// 在新的 `Mediator` 宿主上首次创建并推进 stream,量化 source-generated concrete path 的 first-hit 成本。 + /// + /// 首个 stream 响应元素。 + [Benchmark] + [BenchmarkCategory("ColdStart")] + public async ValueTask ColdStart_Mediator() + { + using var serviceProvider = CreateMediatorServiceProvider(); + var mediator = serviceProvider.GetRequiredService(); + return await ConsumeFirstItemAsync(mediator.CreateStream(Request, CancellationToken.None), CancellationToken.None).ConfigureAwait(false); + } + /// /// 构建只承载当前 benchmark handler 的最小 reflection GFramework.CQRS 容器。 /// @@ -229,6 +258,14 @@ public class StreamStartupBenchmarks ServiceLifetime.Transient); } + /// + /// 构建只承载当前 benchmark handler 的最小 `Mediator` 对照宿主。 + /// + private static ServiceProvider CreateMediatorServiceProvider() + { + return BenchmarkHostFactory.CreateMediatorServiceProvider(configure: null); + } + /// /// 推进 stream 到首个元素,并返回该元素作为 cold-start 结果。 /// @@ -274,6 +311,7 @@ public class StreamStartupBenchmarks /// 返回元素数量。 public sealed record BenchmarkStreamRequest(Guid Id, int ItemCount) : GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest, + Mediator.IStreamRequest, MediatR.IStreamRequest; /// @@ -283,10 +321,11 @@ public class StreamStartupBenchmarks public sealed record BenchmarkResponse(Guid Id); /// - /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 stream handler。 + /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 stream handler。 /// public sealed class BenchmarkStreamHandler : GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler, + Mediator.IStreamRequestHandler, MediatR.IStreamRequestHandler { /// @@ -302,6 +341,19 @@ public class StreamStartupBenchmarks return EnumerateAsync(request, cancellationToken); } + /// + /// 处理 NuGet `Mediator` stream request。 + /// + /// 当前 stream 请求。 + /// 用于中断异步枚举的取消令牌。 + /// 按请求元素数量延迟生成的异步响应序列。 + IAsyncEnumerable Mediator.IStreamRequestHandler.Handle( + BenchmarkStreamRequest request, + CancellationToken cancellationToken) + { + return Handle(request, cancellationToken); + } + /// /// 处理 MediatR stream request。 /// From a016e3d4a4e7866be649ffe0e81dfba234bc72da Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 13:21:46 +0800 Subject: [PATCH 2/8] =?UTF-8?q?docs(cqrs-benchmarks):=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=20stream=20benchmark=20=E6=81=A2=E5=A4=8D=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 StreamingBenchmarks 的 XML 返回值注释并收口 README 中的 stream startup/gap 描述。 - 补充 cqrs-rewrite tracking 与 trace,记录 PR #349 合并后的新基线、startup parity 进展与下一恢复点。 --- .../Messaging/StreamingBenchmarks.cs | 2 + GFramework.Cqrs.Benchmarks/README.md | 4 +- .../todos/cqrs-rewrite-migration-tracking.md | 72 ++++++++----------- .../traces/cqrs-rewrite-migration-trace.md | 41 +++++++++++ 4 files changed, 76 insertions(+), 43 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs index bb3f634a..24ea79bd 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs @@ -135,6 +135,7 @@ public class StreamingBenchmarks /// /// 直接调用 handler,并按当前观测模式消费响应序列,作为 stream dispatch 额外开销的 baseline。 /// + /// 按当前观测模式完成 stream 消费后的等待句柄。 [Benchmark(Baseline = true)] public ValueTask Stream_Baseline() { @@ -158,6 +159,7 @@ public class StreamingBenchmarks /// /// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部设计对照。 /// + /// 按当前观测模式完成 stream 消费后的等待句柄。 [Benchmark] public ValueTask Stream_MediatR() { diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md index dc1e0d15..4356bde6 100644 --- a/GFramework.Cqrs.Benchmarks/README.md +++ b/GFramework.Cqrs.Benchmarks/README.md @@ -39,7 +39,7 @@ - 同时提供 `FirstItem` 与 `DrainAll` 两种观测口径 - stream startup - `Messaging/StreamStartupBenchmarks.cs` - - `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs` reflection、`GFramework.Cqrs` generated、`MediatR` + - `Initialization` 与 `ColdStart` 两组下,覆盖 `GFramework.Cqrs` reflection、`GFramework.Cqrs` generated,以及当前 benchmark 项目已接入的 stream startup 外部 mediator 对照组 - 其中 `ColdStart` 的边界是“新宿主 + 首个元素命中”,不是完整枚举整个 stream - notification steady-state - `Messaging/NotificationBenchmarks.cs` @@ -100,6 +100,6 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro ## 当前缺口 -- 当前没有 stream 生命周期与 startup 版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 与 `StreamStartupBenchmarks` 现在都只覆盖 `GFramework.Cqrs` 与 `MediatR` +- 当前没有 stream 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 现在只覆盖 `GFramework.Cqrs` 与 `MediatR` - 当前没有 request 生命周期下的 NuGet `Mediator` compile-time lifetime 矩阵;`RequestLifetimeBenchmarks` 只覆盖 `GFramework.Cqrs` 与 `MediatR` - 当前没有 notification fan-out 的生命周期矩阵;`NotificationFanOutBenchmarks` 只覆盖固定 `4 handler` 的已装配宿主 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 9e6dafdc..e7fcb3b0 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 @@ -12,80 +12,70 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-136` +- 恢复点编号:`CQRS-REWRITE-RP-137` - 当前阶段:`Phase 8` -- 当前 PR 锚点:`PR #349` +- 当前 PR 锚点:`PR #349(已于 2026-05-12 合并到 origin/main)` - 当前结论: - - 本轮先按 `$gframework-pr-review` 重新确认当前分支最新 GitHub 上下文,确认 `feat/cqrs-optimization` 在 `2026-05-12` 已切到 `PR #349`,不再沿用旧 tracking 中的 `PR #348` 锚点。 - - 随后按 `$gframework-batch-boot 50` 持续协调多波 non-conflicting subagent,基线固定为 - `origin/main @ ef4d3d5d (2026-05-11 17:33:43 +0800)`。 - - 当前 branch 相对基线的累计 diff 约为 `9 files / 1111 lines`;本轮停点由 - `context-budget / reviewability` 决定,而不是 `50 files` 阈值。 - - `PR #349` latest-head review 当前确认仍成立的项只有: - - `StreamPipelineBenchmarks` 三个公开 benchmark 方法补齐 `` XML 契约 - - `StreamingBenchmarks.Stream_Mediator` 补齐 `` XML 契约 - - `CqrsNotificationPublisherTests` 中 fallback publisher 缓存回归测试去掉误导性的“第二次解析返回其它 publisher”分支 - - active tracking / trace 的当前 PR 锚点与下一步入口同步到 `PR #349` - - tests 侧已补齐并提交: + - 本轮恢复时先按 `$gframework-pr-review` 复核 `PR #349` latest-head review,确认该 PR 已关闭且合并到 + `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`,旧 tracking 中基于 `ef4d3d5d` 的 branch-diff 度量已失效。 + - latest-head 残余 open thread 中,实际仍成立的项只剩: + - `StreamingBenchmarks.Stream_MediatR()` 缺少 `` XML 契约 + - 其余线程经本地核对已判定为 stale: + - `StreamPipelineBenchmarks.Stream_Baseline` 的 `` 已存在 + - `CqrsNotificationPublisherTests` 的 fallback publisher 缓存回归已改成“再次解析立即失败” + - active tracking / trace 已同步到 `PR #349` + - 本轮继续按 `$gframework-batch-boot 50` 协调 subagent,围绕 benchmark 文档与 startup parity 做两波窄切片: + - 已提交 `f346110a`:`StreamStartupBenchmarks` 补 `Mediator` startup parity + - 待收尾提交:`StreamingBenchmarks` 的 XML 文档补齐与 `GFramework.Cqrs.Benchmarks/README.md` 的 stream startup / gap 同步 + - tests 侧此前已补齐并提交: - `CqrsRegistrationServiceTests`:补空输入、空项过滤、稳定键排序与跨调用跳过边界 - `CqrsHandlerRegistrarTests` 与 `CqrsHandlerRegistrarFallbackFailureTests`: 补 abstract registry 与缺少无参构造器 registry 的回退 / 抛错覆盖 - `CqrsNotificationPublisherTests`:补“零 publisher 回退到默认顺序发布器并缓存”回归 - - benchmark 侧已补齐并提交: + - benchmark 侧此前已补齐并提交: - `StreamPipelineBenchmarks` - `StreamingBenchmarks` 的 steady-state `Mediator` 对照 - `GFramework.Cqrs.Benchmarks/README.md` 的 stream coverage / gap 同步 + - `StreamStartupBenchmarks` 的 `Mediator` initialization / cold-start 对照 - 本轮未修改 `GFramework.Cqrs` 运行时代码;notification fallback 与 generated registry 激活守卫均由新回归证明现有实现已满足预期。 ## 当前活跃事实 - 当前分支:`feat/cqrs-optimization` -- 当前 PR:`PR #349` +- 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)` - 当前写面: - - `GFramework.Cqrs.Benchmarks/Messaging/StreamPipelineBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/README.md` - - `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs` - - `GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs` - - `GFramework.Cqrs.Tests/Cqrs/CqrsNotificationPublisherTests.cs` - - `GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs` - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` - 当前基线: - - `origin/main @ ef4d3d5d (2026-05-11 17:33:43 +0800)` - - 本轮 batch 启动前,分支相对基线的累计 diff 为 `0 files / 0 lines` - - 当前自然停点时,累计 diff 约为 `9 files / 1111 lines` + - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` + - 当前已提交 branch diff:`1 file / 60 lines` + - 当前工作树尚有 2 个未提交收尾文件:`StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` - 本轮提交: - - `ef3cfdc4` `test(cqrs): 补充注册服务边界测试` - - `bcfecd3c` `test(cqrs): 补充 registrar 激活失败分支测试` - - `59cab567` `test(cqrs-benchmarks): 新增 stream pipeline benchmark 覆盖` - - `010b7028` `test(cqrs): 补充通知回退回归覆盖` - - `ae1c3b89` `test(cqrs-benchmarks): 补齐 stream steady-state Mediator 对照` + - `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` ## 当前风险 -- 分支已累积 5 个窄切片提交;若继续在同一 turn 扩 benchmark + docs,reviewability 会明显下降。 -- 新增 benchmark 目前只做了编译验证,尚未执行 `StreamPipelineBenchmarks` 或更新后的 `StreamingBenchmarks` 实际作业。 -- `ef3cfdc4` 的 commit body 含字面 `\n`;若后续要整理历史,需要在显式允许的前提下单独处理提交格式。 +- `StreamStartupBenchmarks` 的 `Mediator` parity 目前只做了编译验证,尚未单独执行 benchmark 作业确认 startup 矩阵运行结果。 +- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;该项涉及 `BenchmarkHostFactory` 与 compile-time lifetime 形状,不再是本轮低风险切片。 +- 当前 worktree 仍有 2 个未提交文档/注释收尾文件;若不在同轮提交,下一次 `boot` 会同时面对已提交 benchmark 扩展与未提交文档漂移。 ## 最近权威验证 - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - 结果:通过,`0 warning / 0 error` -- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsNotificationPublisherTests"` - - 结果:通过,`Passed: 9, Failed: 0` -- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsRegistrationServiceTests|FullyQualifiedName~CqrsHandlerRegistrarTests|FullyQualifiedName~CqrsHandlerRegistrarFallbackFailureTests|FullyQualifiedName~CqrsNotificationPublisherTests"` - - 结果:通过,`Passed: 36, Failed: 0` -- `python3 scripts/license-header.py --check --paths GFramework.Cqrs.Benchmarks/Messaging/StreamPipelineBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs GFramework.Cqrs.Benchmarks/README.md GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsNotificationPublisherTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` - - 结果:通过 -- `git diff --check origin/main...HEAD` +- `python3 scripts/license-header.py --check` - 结果:通过 +- `$gframework-pr-review` + - 结果:`PR #349` 已关闭;latest-head review open thread 经本地核对仅剩 `StreamingBenchmarks.Stream_MediatR()` 的 XML 文档缺口仍成立 ## 下一推荐步骤 -1. 再次运行 `$gframework-pr-review`,复核 `PR #349` latest-head open thread 是否已随着当前修复提交收敛。 -2. 若继续扩 benchmark,优先在 `StreamLifetimeBenchmarks` 或 `StreamStartupBenchmarks` 中补单文件 `Mediator` parity,而不是并行扩多个矩阵。 -3. 若切回文档收尾,把 `GFramework.Cqrs/README.md`、`docs/zh-CN/core/command.md`、`docs/zh-CN/core/query.md` 作为单独一波 docs-only 切片处理。 +1. 先提交 `StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` 与本 tracking / trace 收尾,回到干净工作树。 +2. 若继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 +3. 若后续还要扩 stream parity,把 `StreamLifetimeBenchmarks` 视为跨文件设计任务,而不是继续按“单文件低风险切片”处理。 ## 活跃文档 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 3523d2d0..81baa47e 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 @@ -7,6 +7,47 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-12 +### 阶段:stream startup parity 与文档收尾(CQRS-REWRITE-RP-137) + +- 按 `$gframework-batch-boot 50` 恢复后,先重新执行 `$gframework-pr-review`。 +- 当前 GitHub 事实: + - `PR #349` 已关闭并合并到 `origin/main` + - 基线切换为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` + - 当前分支相对新基线的已提交 diff 初始为 `0 files / 0 lines` +- latest-head open thread 本地复核: + - stale + - `StreamPipelineBenchmarks.Stream_Baseline` 的 `` 已存在 + - `CqrsNotificationPublisherTests` 的 fallback 缓存安全网已收口 + - trace 的当前 PR / 下一步已同步到 `PR #349` + - valid + - `StreamingBenchmarks.Stream_MediatR()` 仍缺 `` XML 文档 +- 第 1 波 accepted delegated scope: + - `StreamingBenchmarks.cs` + - worker 补 `Stream_Baseline()` 与 `Stream_MediatR()` 的 `` XML 契约 + - 主线程验收时确认其中 `Stream_Baseline()` 属于额外收口,不是 latest-head 必修项 + - `StreamStartupBenchmarks.cs` + - worker 在单文件 ownership 内补 `GeneratedMediator` 宿主字段、setup/cleanup、`Initialization_Mediator()`、`ColdStart_Mediator()` + - 同文件把 `BenchmarkStreamRequest` / `BenchmarkStreamHandler` 扩成同时支持 `Mediator` stream 合同 + - worker 自主完成并提交:`f346110a feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` + - `GFramework.Cqrs.Benchmarks/README.md` + - worker 只收口 `StreamStartupBenchmarks` coverage 与当前 gap 描述,不假设 `StreamLifetimeBenchmarks` 已补 parity +- 主线程验收结论: + - `StreamLifetimeBenchmarks` 的 `Mediator` parity 被判定为 hard slice,需要 `BenchmarkHostFactory` 与 compile-time lifetime 配套,不再继续作为本 turn 的低风险并行切片 + - 当前自然停点应落在: + - 已提交的 `StreamStartupBenchmarks` parity + - 未提交但已验收的 `StreamingBenchmarks.cs` / `README.md` 收尾 + - `ai-plan` 同步到新基线与新恢复点 +- 本轮权威验证里程碑: + - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:通过,`0 warning / 0 error` + - `python3 scripts/license-header.py --check` + - 结果:通过 + - `git diff --check` + - 待当前未提交收尾切片与 `ai-plan` 一并提交前再次运行 +- 当前下一步: + - 提交 `StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` 与 `ai-plan/public/cqrs-rewrite/**` 收尾 + - 如需继续 benchmark 波次,优先做 `StreamStartupBenchmarks` 的最小 smoke run,而不是直接展开 `StreamLifetimeBenchmarks` + ### 阶段:PR #349 latest-head review 收口(CQRS-REWRITE-RP-136) - 重新执行 `$gframework-pr-review`,按 GitHub 当前分支状态确认 `feat/cqrs-optimization` 在 `2026-05-12` 对应的是 `PR #349`,不再沿用 active tracking 中的 `PR #348` 锚点。 From ab422b05db26ad822b78ced25ac03734f4de65c8 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 13:33:50 +0800 Subject: [PATCH 3/8] =?UTF-8?q?docs(cqrs-benchmarks):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=20request=20benchmark=20=E8=BF=94=E5=9B=9E=E5=80=BC=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充 RequestInvokerBenchmarks 公开 benchmark 方法缺失的 XML returns 契约 - 补充 RequestLifetimeBenchmarks 公开 benchmark 方法缺失的 XML returns 契约 --- .../Messaging/RequestInvokerBenchmarks.cs | 4 ++++ .../Messaging/RequestLifetimeBenchmarks.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs index 4b8589ed..0db95ec4 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestInvokerBenchmarks.cs @@ -116,6 +116,7 @@ public class RequestInvokerBenchmarks /// /// 直接调用最小 request handler,作为 dispatch 额外开销 baseline。 /// + /// 代表基线 request handler 完成当前 request 处理的值任务。 [Benchmark(Baseline = true)] public ValueTask SendRequest_Baseline() { @@ -125,6 +126,7 @@ public class RequestInvokerBenchmarks /// /// 通过 GFramework.CQRS 反射 request binding 路径发送 request。 /// + /// 代表当前 reflection request dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_GFrameworkReflection() { @@ -134,6 +136,7 @@ public class RequestInvokerBenchmarks /// /// 通过 generated request invoker provider 预热后的 GFramework.CQRS runtime 发送 request。 /// + /// 代表当前 generated request dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_GFrameworkGenerated() { @@ -143,6 +146,7 @@ public class RequestInvokerBenchmarks /// /// 通过 MediatR 发送 request,作为外部对照。 /// + /// 代表当前 MediatR request dispatch 完成的任务。 [Benchmark] public Task SendRequest_MediatR() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs index 4c0e5306..f96deeef 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs @@ -151,6 +151,7 @@ public class RequestLifetimeBenchmarks /// /// 直接调用 handler,作为不同生命周期矩阵下的 dispatch 额外开销 baseline。 /// + /// 代表基线 request handler 完成当前 request 处理的值任务。 [Benchmark(Baseline = true)] public ValueTask SendRequest_Baseline() { @@ -160,6 +161,7 @@ public class RequestLifetimeBenchmarks /// /// 通过 GFramework.CQRS runtime 发送 request。 /// + /// 代表当前 GFramework.CQRS request dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_GFrameworkCqrs() { @@ -179,6 +181,7 @@ public class RequestLifetimeBenchmarks /// /// 通过 MediatR 发送 request,作为外部对照。 /// + /// 代表当前 MediatR request dispatch 完成的任务。 [Benchmark] public Task SendRequest_MediatR() { From 555c7c07acddbf40520e808622d2de895930fdb5 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 13:34:12 +0800 Subject: [PATCH 4/8] =?UTF-8?q?docs(cqrs-benchmarks):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?=20request=20benchmark=20=E8=BF=94=E5=9B=9E=E5=80=BC=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充 RequestBenchmarks 公开 benchmark 方法的 XML returns 契约 - 补充 RequestPipelineBenchmarks 公开 benchmark 方法的 XML returns 契约 --- GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs | 4 ++++ .../Messaging/RequestPipelineBenchmarks.cs | 3 +++ 2 files changed, 7 insertions(+) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs index 9d946e72..6e23cfe8 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs @@ -104,6 +104,7 @@ public class RequestBenchmarks /// /// 直接调用 handler,作为 dispatch 额外开销的 baseline。 /// + /// 代表基线 handler 完成当前 request 处理的值任务。 [Benchmark(Baseline = true)] public ValueTask SendRequest_Baseline() { @@ -113,6 +114,7 @@ public class RequestBenchmarks /// /// 通过 GFramework.CQRS runtime 发送 request。 /// + /// 代表当前 GFramework.CQRS request dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_GFrameworkCqrs() { @@ -122,6 +124,7 @@ public class RequestBenchmarks /// /// 通过 MediatR 发送 request,作为外部设计对照。 /// + /// 代表当前 MediatR request dispatch 完成的任务。 [Benchmark] public Task SendRequest_MediatR() { @@ -131,6 +134,7 @@ public class RequestBenchmarks /// /// 通过 `ai-libs/Mediator` 的 source-generated concrete mediator 发送 request,作为高性能对照组。 /// + /// 代表当前 `Mediator` request dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_Mediator() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs index a2883955..b97926cc 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestPipelineBenchmarks.cs @@ -113,6 +113,7 @@ public class RequestPipelineBenchmarks /// /// 直接调用 handler,作为 pipeline 编排之外的基线。 /// + /// 代表基线 handler 完成当前 request 处理的值任务。 [Benchmark(Baseline = true)] public ValueTask SendRequest_Baseline() { @@ -122,6 +123,7 @@ public class RequestPipelineBenchmarks /// /// 通过 GFramework.CQRS runtime 发送 request,并按当前矩阵配置执行 pipeline。 /// + /// 代表当前 GFramework.CQRS request pipeline dispatch 完成的值任务。 [Benchmark] public ValueTask SendRequest_GFrameworkCqrs() { @@ -131,6 +133,7 @@ public class RequestPipelineBenchmarks /// /// 通过 MediatR 发送 request,并按当前矩阵配置执行 pipeline,作为外部设计对照。 /// + /// 代表当前 MediatR request pipeline dispatch 完成的任务。 [Benchmark] public Task SendRequest_MediatR() { From c32a1ec4ae2806550adc744d5ba8297398bfed3b Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 13:36:54 +0800 Subject: [PATCH 5/8] =?UTF-8?q?docs(cqrs-benchmarks):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?stream=E4=B8=8Enotification=E5=9F=BA=E5=87=86=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 补充 stream 与 notification benchmark 公开方法缺失的 XML returns 契约 - 更新 request startup benchmark 的返回值文档说明 - 同步 cqrs-rewrite 的批处理恢复点与停点决策 --- .../Messaging/NotificationBenchmarks.cs | 3 + .../Messaging/NotificationFanOutBenchmarks.cs | 5 ++ .../Messaging/RequestStartupBenchmarks.cs | 6 ++ .../Messaging/StreamInvokerBenchmarks.cs | 4 ++ .../Messaging/StreamLifetimeBenchmarks.cs | 4 ++ .../Messaging/StreamingBenchmarks.cs | 1 + .../todos/cqrs-rewrite-migration-tracking.md | 34 +++++++--- .../traces/cqrs-rewrite-migration-trace.md | 63 +++++++++++++++++++ 8 files changed, 113 insertions(+), 7 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs index de3c7b92..80ce9b88 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs @@ -95,6 +95,7 @@ public class NotificationBenchmarks /// /// 通过 GFramework.CQRS runtime 发布 notification。 /// + /// 代表当前 GFramework.CQRS publish 完成的值任务。 [Benchmark(Baseline = true)] public ValueTask PublishNotification_GFrameworkCqrs() { @@ -104,6 +105,7 @@ public class NotificationBenchmarks /// /// 通过 MediatR 发布 notification,作为外部设计对照。 /// + /// 代表当前 MediatR publish 完成的任务。 [Benchmark] public Task PublishNotification_MediatR() { @@ -113,6 +115,7 @@ public class NotificationBenchmarks /// /// 通过 `Mediator` source-generated concrete mediator 发布 notification,作为高性能对照组。 /// + /// 代表当前 `Mediator` publish 完成的值任务。 [Benchmark] public ValueTask PublishNotification_Mediator() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs index 431fa359..0e3efb1a 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs @@ -123,6 +123,7 @@ public class NotificationFanOutBenchmarks /// /// 直接依次调用 4 个处理器,作为 fan-out dispatch 额外开销的 baseline。 /// + /// 代表基线顺序调用 4 个处理器完成当前 notification 处理的值任务。 [Benchmark(Baseline = true)] public async ValueTask PublishNotification_Baseline() { @@ -135,6 +136,7 @@ public class NotificationFanOutBenchmarks /// /// 通过默认顺序发布器的 GFramework.CQRS runtime 发布固定 4 处理器的 notification。 /// + /// 代表当前默认顺序发布器 publish 完成的值任务。 [Benchmark] public ValueTask PublishNotification_GFrameworkCqrsSequential() { @@ -144,6 +146,7 @@ public class NotificationFanOutBenchmarks /// /// 通过内置 Task.WhenAll(...) 发布器的 GFramework.CQRS runtime 发布固定 4 处理器的 notification。 /// + /// 代表当前 Task.WhenAll(...) 发布器 publish 完成的值任务。 [Benchmark] public ValueTask PublishNotification_GFrameworkCqrsTaskWhenAll() { @@ -153,6 +156,7 @@ public class NotificationFanOutBenchmarks /// /// 通过 MediatR 发布固定 4 处理器的 notification,作为外部设计对照。 /// + /// 代表当前 MediatR publish 完成的任务。 [Benchmark] public Task PublishNotification_MediatR() { @@ -162,6 +166,7 @@ public class NotificationFanOutBenchmarks /// /// 通过 `Mediator` source-generated concrete mediator 发布固定 4 处理器的 notification,作为高性能对照组。 /// + /// 代表当前 `Mediator` publish 完成的值任务。 [Benchmark] public ValueTask PublishNotification_Mediator() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs index 2c685fed..a32acc26 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs @@ -97,6 +97,7 @@ public class RequestStartupBenchmarks /// /// 返回已构建宿主中的 MediatR mediator,作为 initialization 组的句柄解析 baseline。 /// + /// 当前 benchmark 复用的 MediatR mediator。 [Benchmark(Baseline = true)] [BenchmarkCategory("Initialization")] public IMediator Initialization_MediatR() @@ -107,6 +108,7 @@ public class RequestStartupBenchmarks /// /// 返回已构建宿主中的 GFramework.CQRS runtime,确保与 MediatR baseline 处于相同初始化阶段。 /// + /// 当前 benchmark 复用的 GFramework.CQRS runtime。 [Benchmark] [BenchmarkCategory("Initialization")] public ICqrsRuntime Initialization_GFrameworkCqrs() @@ -117,6 +119,7 @@ public class RequestStartupBenchmarks /// /// 返回已构建宿主中的 `Mediator` concrete mediator,作为 source-generated 对照组的初始化句柄。 /// + /// 当前 benchmark 复用的 `Mediator` concrete mediator。 [Benchmark] [BenchmarkCategory("Initialization")] public GeneratedMediator Initialization_Mediator() @@ -127,6 +130,7 @@ public class RequestStartupBenchmarks /// /// 在新宿主上首次发送 request,作为 MediatR 的 cold-start baseline。 /// + /// 当前 request 的响应结果。 [Benchmark(Baseline = true)] [BenchmarkCategory("ColdStart")] public async Task ColdStart_MediatR() @@ -139,6 +143,7 @@ public class RequestStartupBenchmarks /// /// 在新 runtime 上首次发送 request,量化 GFramework.CQRS 的 first-hit 成本。 /// + /// 当前 request 的响应结果。 [Benchmark] [BenchmarkCategory("ColdStart")] public async ValueTask ColdStart_GFrameworkCqrs() @@ -151,6 +156,7 @@ public class RequestStartupBenchmarks /// /// 在新的 `Mediator` 宿主上首次发送 request,量化 source-generated concrete path 的 cold-start 成本。 /// + /// 当前 request 的响应结果。 [Benchmark] [BenchmarkCategory("ColdStart")] public async ValueTask ColdStart_Mediator() diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs index 34788522..76ad5831 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs @@ -143,6 +143,7 @@ public class StreamInvokerBenchmarks /// /// 直接调用最小 stream handler,并按当前观测模式消费 stream,作为 dispatch 额外开销 baseline。 /// + /// 代表基线 stream 按当前观测模式消费完成的值任务。 [Benchmark(Baseline = true)] public ValueTask Stream_Baseline() { @@ -152,6 +153,7 @@ public class StreamInvokerBenchmarks /// /// 通过 GFramework.CQRS 反射 stream binding 路径创建 stream,并按当前观测模式消费。 /// + /// 代表当前 GFramework.CQRS 反射 stream 按观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_GFrameworkReflection() { @@ -166,6 +168,7 @@ public class StreamInvokerBenchmarks /// /// 通过 generated stream invoker provider 预热后的 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。 /// + /// 代表当前 GFramework.CQRS generated stream 按观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_GFrameworkGenerated() { @@ -180,6 +183,7 @@ public class StreamInvokerBenchmarks /// /// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部对照。 /// + /// 代表当前 MediatR stream 按观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_MediatR() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs index 7dbb5d78..f1606018 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs @@ -148,6 +148,7 @@ public class StreamLifetimeBenchmarks /// /// 直接调用 handler,并按当前观测模式消费 stream,作为不同生命周期矩阵下的 dispatch 额外开销 baseline。 /// + /// 代表基线 handler stream 按当前观测模式消费完成的值任务。 [Benchmark(Baseline = true)] public ValueTask Stream_Baseline() { @@ -157,6 +158,7 @@ public class StreamLifetimeBenchmarks /// /// 通过 GFramework.CQRS reflection stream binding 路径创建 stream,并按当前观测模式消费。 /// + /// 代表当前 reflection stream 按当前观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_GFrameworkReflection() { @@ -183,6 +185,7 @@ public class StreamLifetimeBenchmarks /// /// 通过 generated stream invoker provider 预热后的 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。 /// + /// 代表当前 generated stream 按当前观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_GFrameworkGenerated() { @@ -209,6 +212,7 @@ public class StreamLifetimeBenchmarks /// /// 通过 MediatR 创建 stream,并按当前观测模式消费,作为外部对照。 /// + /// 代表当前 MediatR stream 按当前观测模式消费完成的值任务。 [Benchmark] public ValueTask Stream_MediatR() { diff --git a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs index 24ea79bd..449c1bba 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs @@ -145,6 +145,7 @@ public class StreamingBenchmarks /// /// 通过 GFramework.CQRS runtime 创建 stream,并按当前观测模式消费。 /// + /// 按当前观测模式完成 stream 消费后的等待句柄。 [Benchmark] public ValueTask Stream_GFrameworkCqrs() { 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 e7fcb3b0..eb3cd91f 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 @@ -12,7 +12,7 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-137` +- 恢复点编号:`CQRS-REWRITE-RP-138` - 当前阶段:`Phase 8` - 当前 PR 锚点:`PR #349(已于 2026-05-12 合并到 origin/main)` - 当前结论: @@ -27,6 +27,17 @@ CQRS 迁移与收敛。 - 本轮继续按 `$gframework-batch-boot 50` 协调 subagent,围绕 benchmark 文档与 startup parity 做两波窄切片: - 已提交 `f346110a`:`StreamStartupBenchmarks` 补 `Mediator` startup parity - 待收尾提交:`StreamingBenchmarks` 的 XML 文档补齐与 `GFramework.Cqrs.Benchmarks/README.md` 的 stream startup / gap 同步 + - 在当前恢复点继续推进第 2 波 benchmark XML 契约收口,范围严格限定为公开 benchmark 方法缺失的 `` 文档: + - 主线程:`StreamingBenchmarks.cs`、`NotificationBenchmarks.cs` 与 `ai-plan/public/cqrs-rewrite/**` + - worker:`StreamLifetimeBenchmarks.cs`、`StreamInvokerBenchmarks.cs`、`NotificationFanOutBenchmarks.cs` + - 启动第 2 波前,当前分支相对 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` 的已提交 branch diff 为 `5 files / 177 lines`,远低于 `$gframework-batch-boot 50` 的文件阈值;本轮是否继续主要由 context-budget / reviewability 决定,而不是 branch-size 预算。 + - 第 3 波继续沿同一模式扩到 request 系 benchmark XML 契约收口,并已由 worker 分别提交: + - `555c7c07`:`RequestBenchmarks.cs`、`RequestPipelineBenchmarks.cs` + - `ab422b05`:`RequestInvokerBenchmarks.cs`、`RequestLifetimeBenchmarks.cs` + - 另有 `RequestStartupBenchmarks.cs` 已完成 `` 收口,待与主线程未提交切片一并收尾 + - 当前已决定在第 3 波后停在自然边界,而不是继续开启第 4 波: + - branch-size 仍远低于 `50 files` + - 但剩余候选已不比当前波次更低风险,继续机械扩批会降低 reviewability,并推高当前上下文负担 - tests 侧此前已补齐并提交: - `CqrsRegistrationServiceTests`:补空输入、空项过滤、稳定键排序与跨调用跳过边界 - `CqrsHandlerRegistrarTests` 与 `CqrsHandlerRegistrarFallbackFailureTests`: @@ -44,6 +55,11 @@ CQRS 迁移与收敛。 - 当前分支:`feat/cqrs-optimization` - 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)` - 当前写面: + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/README.md` @@ -51,16 +67,19 @@ CQRS 迁移与收敛。 - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` - 当前基线: - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` - - 当前已提交 branch diff:`1 file / 60 lines` - - 当前工作树尚有 2 个未提交收尾文件:`StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` + - 当前已提交 branch diff:`9 files / 143 lines` + - 当前分支比 `origin/main` 多 `4` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07` + - 当前未提交面由 notification / stream / request-startup 的 XML 契约补齐与 `ai-plan` 恢复点更新构成 - 本轮提交: - `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` + - `ab422b05` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释` + - `555c7c07` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值文档` ## 当前风险 - `StreamStartupBenchmarks` 的 `Mediator` parity 目前只做了编译验证,尚未单独执行 benchmark 作业确认 startup 矩阵运行结果。 - `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;该项涉及 `BenchmarkHostFactory` 与 compile-time lifetime 形状,不再是本轮低风险切片。 -- 当前 worktree 仍有 2 个未提交文档/注释收尾文件;若不在同轮提交,下一次 `boot` 会同时面对已提交 benchmark 扩展与未提交文档漂移。 +- 本轮已在 request 系 benchmark XML 契约收口后主动停批次;若后续恢复,优先先提交当前未提交面,再决定是否开启 docs/README 或 smoke-run 下一阶段,而不是继续机械扩张 XML 批次。 ## 最近权威验证 @@ -73,9 +92,10 @@ CQRS 迁移与收敛。 ## 下一推荐步骤 -1. 先提交 `StreamingBenchmarks.cs`、`GFramework.Cqrs.Benchmarks/README.md` 与本 tracking / trace 收尾,回到干净工作树。 -2. 若继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 -3. 若后续还要扩 stream parity,把 `StreamLifetimeBenchmarks` 视为跨文件设计任务,而不是继续按“单文件低风险切片”处理。 +1. 串行运行 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`、`python3 scripts/license-header.py --check --paths ...` 与 `git diff --check`,作为当前自然停点的权威收尾验证。 +2. 提交当前未提交的 notification / stream / request-startup XML 契约与 `ai-plan` 更新,回到干净工作树。 +3. 若后续继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 +4. 若后续还要扩 stream parity,把 `StreamLifetimeBenchmarks` 视为跨文件设计任务,而不是继续按“单文件低风险切片”处理。 ## 活跃文档 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 81baa47e..88f20d7e 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 @@ -7,6 +7,69 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-12 +### 阶段:benchmark XML 契约第 2 波收口(CQRS-REWRITE-RP-138) + +- 延续 `$gframework-batch-boot 50`,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。 +- 第 2 波启动前,当前分支相对基线的已提交 branch diff 为 `5 files / 177 lines`,明显低于 `50 files` 阈值;本轮继续与否的主停止信号仍是 context-budget / reviewability。 +- 主线程本地盘点 `GFramework.Cqrs.Benchmarks/Messaging/*.cs` 的公开 `[Benchmark]` 方法后,确认当前仍有一批与既有收口模式一致的 XML `` 缺口: + - `StreamingBenchmarks.Stream_GFrameworkCqrs()` + - `NotificationBenchmarks` 的 3 个公开 benchmark 方法 + - `NotificationFanOutBenchmarks` 的 5 个公开 benchmark 方法 + - `StreamInvokerBenchmarks` 的 4 个公开 benchmark 方法 + - `StreamLifetimeBenchmarks` 的 4 个公开 benchmark 方法 +- 本波 accepted ownership: + - 主线程 + - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` + - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` + - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` + - worker + - `StreamLifetimeBenchmarks.cs` + - `StreamInvokerBenchmarks.cs` + - `NotificationFanOutBenchmarks.cs` +- worker 回传验收结论: + - `StreamLifetimeBenchmarks.cs` + - 只补 `Stream_Baseline`、`Stream_GFrameworkReflection`、`Stream_GFrameworkGenerated`、`Stream_MediatR` 的 `` + - worker 自报 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` 通过 + - `StreamInvokerBenchmarks.cs` + - 只补 4 个公开 benchmark 方法的 `` + - worker 自报同一条 benchmark 工程 build 通过 + - `NotificationFanOutBenchmarks.cs` + - 只补 5 个公开 benchmark 方法的 `` + - worker 自报 build 遇到 `CS2012`:`obj/Release/net10.0/GFramework.Cqrs.Benchmarks.dll` 被并发进程占用;该失败被判定为并发构建噪音,而不是代码语义问题 +- 主线程局部实施: + - `StreamingBenchmarks.cs` + - 为 `Stream_GFrameworkCqrs()` 补 `` + - `NotificationBenchmarks.cs` + - 为 `PublishNotification_GFrameworkCqrs()`、`PublishNotification_MediatR()`、`PublishNotification_Mediator()` 补 `` +- 当前下一步: + - 主线程串行执行 benchmark 工程 Release build,消除 worker 并发写 `obj/Release` 带来的验证噪音 + - 若串行验证通过,决定是在当前自然停点提交收尾,还是继续 request 侧 XML 契约的下一波低风险批处理 + +### 阶段:request benchmark XML 契约第 3 波收口后停在自然边界(CQRS-REWRITE-RP-138) + +- 第 2 波串行验证通过后,继续用 3 个 worker 扩展 request 系 benchmark 的同类 `` 收口: + - `RequestStartupBenchmarks.cs` + - `RequestBenchmarks.cs` + `RequestPipelineBenchmarks.cs` + - `RequestInvokerBenchmarks.cs` + `RequestLifetimeBenchmarks.cs` +- worker 回传与 acceptance: + - `RequestStartupBenchmarks.cs` + - 只补公开 benchmark 方法缺失的 `` + - worker 自报 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` 通过 + - `RequestBenchmarks.cs` + `RequestPipelineBenchmarks.cs` + - 只补公开 benchmark 方法缺失的 `` + - worker 自报 build 通过,并已提交:`555c7c07 docs(cqrs-benchmarks): 补齐 request benchmark 返回值文档` + - `RequestInvokerBenchmarks.cs` + `RequestLifetimeBenchmarks.cs` + - 只补公开 benchmark 方法缺失的 `` + - worker 自报 build 通过,并已提交:`ab422b05 docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释` +- 第 3 波后主线程 stop decision: + - 不再开启第 4 波 XML 契约批处理 + - 原因不是 branch-size 阈值耗尽;当前分支相对 `origin/main` 仍只有 `9 files / 143 lines` + - 停止原因是 context-budget / reviewability:剩余候选已不比当前波次更低风险,继续机械扩批收益下降 +- 当前下一步: + - 只做主线程未提交面的串行验证与收尾提交 + - 将干净工作树作为下一次 `boot` 的默认恢复目标 + ### 阶段:stream startup parity 与文档收尾(CQRS-REWRITE-RP-137) - 按 `$gframework-batch-boot 50` 恢复后,先重新执行 `$gframework-pr-review`。 From 092946e91abdfe94b301836cc3be446dd85e0fc4 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 14:18:18 +0800 Subject: [PATCH 6/8] =?UTF-8?q?docs(cqrs-benchmarks):=20=E5=90=8C=E6=AD=A5?= =?UTF-8?q?startup=E5=9F=BA=E5=87=86=E6=96=87=E6=A1=A3=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 README 中 stream/request/notification startup benchmark 的覆盖矩阵与适用边界 - 补充 cqrs-rewrite public tracking 与 trace,记录本轮误报剔除、docs-only 收尾与停止判断 --- GFramework.Cqrs.Benchmarks/README.md | 5 +- .../todos/cqrs-rewrite-migration-tracking.md | 67 +++++++++---------- .../traces/cqrs-rewrite-migration-trace.md | 29 ++++++++ 3 files changed, 63 insertions(+), 38 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md index 4356bde6..dd943daa 100644 --- a/GFramework.Cqrs.Benchmarks/README.md +++ b/GFramework.Cqrs.Benchmarks/README.md @@ -24,6 +24,7 @@ - request startup - `Messaging/RequestStartupBenchmarks.cs` - `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs`、NuGet `Mediator`、`MediatR` + - 其中 `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup/cold-start 模型,不包含更大范围的程序集扫描或完整注册协调器接线 - stream steady-state - `Messaging/StreamingBenchmarks.cs` - baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path 与 `MediatR` @@ -39,7 +40,7 @@ - 同时提供 `FirstItem` 与 `DrainAll` 两种观测口径 - stream startup - `Messaging/StreamStartupBenchmarks.cs` - - `Initialization` 与 `ColdStart` 两组下,覆盖 `GFramework.Cqrs` reflection、`GFramework.Cqrs` generated,以及当前 benchmark 项目已接入的 stream startup 外部 mediator 对照组 + - `Initialization` 与 `ColdStart` 两组下,覆盖 `MediatR`、`GFramework.Cqrs` reflection、`GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization/cold-start 对照 - 其中 `ColdStart` 的边界是“新宿主 + 首个元素命中”,不是完整枚举整个 stream - notification steady-state - `Messaging/NotificationBenchmarks.cs` @@ -51,6 +52,7 @@ - notification startup - `Messaging/NotificationStartupBenchmarks.cs` - `Initialization` 与 `ColdStart` 两组下,`GFramework.Cqrs`、NuGet `Mediator`、`MediatR` + - 其中 `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup/cold-start 模型,不包含 fan-out、发布策略变体或更大范围的注册协调逻辑 ## 最小使用方式 @@ -95,6 +97,7 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro - `FirstItem` 适合观察“建流到首个元素”的固定成本 - `DrainAll` 适合观察完整枚举整个 stream 的总成本 - `StreamStartupBenchmarks` 的 `ColdStart` 只推进到首个元素,因此它回答的是“新宿主下首次建流命中”的边界,不回答完整枚举总成本 +- `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 `GFramework.Cqrs` startup 路径都固定在单 handler、最小宿主、手工注册模型;它们回答的是首次 request / publish 命中的额外成本,不代表程序集扫描或完整注册协调器场景 - 当前 HEAD 没有单独固化的 short-job benchmark 类或 checked-in short-job 结果;如果手动使用 short job / short run 只做 smoke 复核,应把它理解为“确认矩阵与路径能跑通” - 特别是 `StreamInvokerBenchmarks` 的 `DrainAll` 在 short-job smoke 下不应直接写成 reflection、generated 或 `MediatR` 之间的稳定排序结论;若要比较名次或小幅差值,应复跑默认作业或更完整的批次 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 eb3cd91f..10260065 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 @@ -12,32 +12,27 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-138` +- 恢复点编号:`CQRS-REWRITE-RP-139` - 当前阶段:`Phase 8` - 当前 PR 锚点:`PR #349(已于 2026-05-12 合并到 origin/main)` - 当前结论: - - 本轮恢复时先按 `$gframework-pr-review` 复核 `PR #349` latest-head review,确认该 PR 已关闭且合并到 - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`,旧 tracking 中基于 `ef4d3d5d` 的 branch-diff 度量已失效。 - - latest-head 残余 open thread 中,实际仍成立的项只剩: - - `StreamingBenchmarks.Stream_MediatR()` 缺少 `` XML 契约 - - 其余线程经本地核对已判定为 stale: - - `StreamPipelineBenchmarks.Stream_Baseline` 的 `` 已存在 - - `CqrsNotificationPublisherTests` 的 fallback publisher 缓存回归已改成“再次解析立即失败” - - active tracking / trace 已同步到 `PR #349` - - 本轮继续按 `$gframework-batch-boot 50` 协调 subagent,围绕 benchmark 文档与 startup parity 做两波窄切片: - - 已提交 `f346110a`:`StreamStartupBenchmarks` 补 `Mediator` startup parity - - 待收尾提交:`StreamingBenchmarks` 的 XML 文档补齐与 `GFramework.Cqrs.Benchmarks/README.md` 的 stream startup / gap 同步 - - 在当前恢复点继续推进第 2 波 benchmark XML 契约收口,范围严格限定为公开 benchmark 方法缺失的 `` 文档: - - 主线程:`StreamingBenchmarks.cs`、`NotificationBenchmarks.cs` 与 `ai-plan/public/cqrs-rewrite/**` - - worker:`StreamLifetimeBenchmarks.cs`、`StreamInvokerBenchmarks.cs`、`NotificationFanOutBenchmarks.cs` - - 启动第 2 波前,当前分支相对 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` 的已提交 branch diff 为 `5 files / 177 lines`,远低于 `$gframework-batch-boot 50` 的文件阈值;本轮是否继续主要由 context-budget / reviewability 决定,而不是 branch-size 预算。 - - 第 3 波继续沿同一模式扩到 request 系 benchmark XML 契约收口,并已由 worker 分别提交: - - `555c7c07`:`RequestBenchmarks.cs`、`RequestPipelineBenchmarks.cs` - - `ab422b05`:`RequestInvokerBenchmarks.cs`、`RequestLifetimeBenchmarks.cs` - - 另有 `RequestStartupBenchmarks.cs` 已完成 `` 收口,待与主线程未提交切片一并收尾 - - 当前已决定在第 3 波后停在自然边界,而不是继续开启第 4 波: - - branch-size 仍远低于 `50 files` - - 但剩余候选已不比当前波次更低风险,继续机械扩批会降低 reviewability,并推高当前上下文负担 + - 本轮按 `$gframework-batch-boot 50` 恢复后,先重新确认基线仍为 + `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`,当前已提交 branch diff 为 `14 files`,仍远低于 `50 files` + 阈值;是否继续的主停止信号仍是 context-budget / reviewability,而不是 branch-size 预算。 + - 主线程结合本地抽样核对与两个 explorer 子代理的只读盘点后,确认当前不应再继续按“benchmark XML `` 批量缺口”扩批: + - README 一致性盘点成立,`GFramework.Cqrs.Benchmarks/README.md` 的 startup coverage / 解释边界仍可收紧 + - benchmark XML 缺口盘点存在明显误报;代表文件中的 class / benchmark 方法 `` 与 `` 已实际存在 + - 因此本轮不接受新的大范围 XML 收口波次,避免把上下文预算消耗在错误候选上 + - 本轮 accepted delegated scope 收敛为单文件 docs-only worker: + - `GFramework.Cqrs.Benchmarks/README.md` + - 明确 `StreamStartupBenchmarks` 现已覆盖 `MediatR`、`GFramework.Cqrs` reflection、 + `GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization / cold-start 对照 + - 补充 `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 + `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup / cold-start 模型,不外推到程序集扫描、 + 完整注册协调器、fan-out 或发布策略变体 + - 当前决定在该 docs-only 收口后停在自然边界: + - branch-size 仍低于 `50 files` + - 但下一批低风险候选已不再清晰;继续开波次的收益低于评审与上下文成本 - tests 侧此前已补齐并提交: - `CqrsRegistrationServiceTests`:补空输入、空项过滤、稳定键排序与跨调用跳过边界 - `CqrsHandlerRegistrarTests` 与 `CqrsHandlerRegistrarFallbackFailureTests`: @@ -55,31 +50,26 @@ CQRS 迁移与收敛。 - 当前分支:`feat/cqrs-optimization` - 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)` - 当前写面: - - `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/NotificationFanOutBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/RequestStartupBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/StreamInvokerBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/StreamLifetimeBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/StreamingBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/README.md` - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` - 当前基线: - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` - - 当前已提交 branch diff:`9 files / 143 lines` - - 当前分支比 `origin/main` 多 `4` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07` - - 当前未提交面由 notification / stream / request-startup 的 XML 契约补齐与 `ai-plan` 恢复点更新构成 + - 当前已提交 branch diff:`14 files` + - 当前分支比 `origin/main` 多 `5` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07`、`c32a1ec4` + - 当前未提交面由 benchmark README 的 startup 边界同步与 `ai-plan` 恢复点更新构成 - 本轮提交: - `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` - `ab422b05` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释` - `555c7c07` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值文档` + - `c32a1ec4` `docs(cqrs-benchmarks): 补齐stream与notification基准返回值文档` ## 当前风险 - `StreamStartupBenchmarks` 的 `Mediator` parity 目前只做了编译验证,尚未单独执行 benchmark 作业确认 startup 矩阵运行结果。 - `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;该项涉及 `BenchmarkHostFactory` 与 compile-time lifetime 形状,不再是本轮低风险切片。 -- 本轮已在 request 系 benchmark XML 契约收口后主动停批次;若后续恢复,优先先提交当前未提交面,再决定是否开启 docs/README 或 smoke-run 下一阶段,而不是继续机械扩张 XML 批次。 +- benchmark XML 盘点若再次依赖粗糙脚本或只读 inventory,仍有把已存在文档误记为缺口的风险;后续若再开 XML 波次,必须先用主线程抽样核对代表文件。 +- 本轮已在 README 精度同步后主动停批次;若后续恢复,优先先做 `StreamStartupBenchmarks` smoke run 或更明确的 parity / docs 候选,而不是继续机械扩张 XML 批次。 ## 最近权威验证 @@ -89,13 +79,16 @@ CQRS 迁移与收敛。 - 结果:通过 - `$gframework-pr-review` - 结果:`PR #349` 已关闭;latest-head review open thread 经本地核对仅剩 `StreamingBenchmarks.Stream_MediatR()` 的 XML 文档缺口仍成立 +- `git --git-dir=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs --work-tree=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs diff -- GFramework.Cqrs.Benchmarks/README.md` + - 结果:通过 + - 备注:确认本轮 worker 仅修改 README 的 startup coverage / 边界文案 ## 下一推荐步骤 -1. 串行运行 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`、`python3 scripts/license-header.py --check --paths ...` 与 `git diff --check`,作为当前自然停点的权威收尾验证。 -2. 提交当前未提交的 notification / stream / request-startup XML 契约与 `ai-plan` 更新,回到干净工作树。 +1. 串行运行 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`、`python3 scripts/license-header.py --check --paths ...` 与 `git diff --check`,作为本轮 docs-only 收尾的权威验证。 +2. 提交当前 README 与 `ai-plan` 更新,回到干净工作树。 3. 若后续继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 -4. 若后续还要扩 stream parity,把 `StreamLifetimeBenchmarks` 视为跨文件设计任务,而不是继续按“单文件低风险切片”处理。 +4. 若后续再开文档批次,先用主线程核对代表文件,再决定是否存在真实 XML 缺口;不要直接沿用误报 inventory 扩批。 ## 活跃文档 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 88f20d7e..30c89650 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 @@ -7,6 +7,35 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-12 +### 阶段:README startup coverage 精度同步并停在自然边界(CQRS-REWRITE-RP-139) + +- 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。 +- 本轮启动时重新测得当前已提交 branch diff 为 `14 files`,仍远低于 `50 files` 阈值;继续与否的主停止信号仍是 + context-budget / reviewability。 +- 本轮主线程先做只读盘点与抽样核对: + - README 一致性 explorer 结论成立:`GFramework.Cqrs.Benchmarks/README.md` 对 startup coverage / 边界的表述仍可更精确 + - benchmark XML 缺口 explorer 结论未直接接受;主线程抽样检查 `NotificationBenchmarks.cs`、 + `RequestBenchmarks.cs`、`StreamingBenchmarks.cs`、`NotificationStartupBenchmarks.cs` 后确认, + 其 class / benchmark 方法的 `` 与 `` 实际已存在,不能继续按“14 个门面文件普遍缺 XML” + 的假设扩批 +- 因此本轮 accepted delegated scope 缩成单文件 docs-only worker: + - `GFramework.Cqrs.Benchmarks/README.md` + - 把 `StreamStartupBenchmarks` 明确写成 `MediatR`、`GFramework.Cqrs` reflection、 + `GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization / cold-start 对照 + - 补充 `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 `GFramework.Cqrs` startup 路径是 + “单 handler 最小宿主 + 手工注册”模型,不外推到程序集扫描、完整注册协调器、fan-out 或发布策略变体 +- worker 回传验收结论: + - 改动文件未越出 ownership 边界 + - README diff 与代码事实一致,且未引入无法从当前 benchmark 实现验证的表述 +- 当前 stop decision: + - 不继续开启新的 XML 文档波次 + - 原因不是 branch-size 阈值耗尽;当前分支仍只有 `14 files` + - 停止原因是候选清晰度下降:继续追逐 explorer 误报会降低 reviewability,并无谓增加当前上下文负担 +- 当前下一步: + - 主线程更新 `ai-plan/public/cqrs-rewrite/**` + - 串行运行 benchmark 工程 build、license-header 与 `git diff --check` + - 提交 README 与 `ai-plan` 收尾 + ### 阶段:benchmark XML 契约第 2 波收口(CQRS-REWRITE-RP-138) - 延续 `$gframework-batch-boot 50`,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。 From e3532fc2c8b8156644a189b928a9108f8d3af685 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 15:44:54 +0800 Subject: [PATCH 7/8] =?UTF-8?q?feat(cqrs-benchmarks):=20=E8=A1=A5=E9=BD=90?= =?UTF-8?q?request=E7=94=9F=E5=91=BD=E5=91=A8=E6=9C=9F=E7=9A=84Mediator?= =?UTF-8?q?=E5=AF=B9=E7=85=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 RequestLifetimeBenchmarks 的 Mediator lifetime parity,并按编译期常量分支生成 Singleton、Scoped、Transient 宿主 - 更新 benchmark README 的 request lifetime coverage 与当前缺口说明,使其反映最新对照矩阵 - 修复 RequestBenchmarks 与 NotificationBenchmarks 的 XML 文档漂移,并同步 ai-plan 恢复点与验证结论 --- .../Messaging/NotificationBenchmarks.cs | 4 +- .../Messaging/RequestBenchmarks.cs | 2 +- .../Messaging/RequestLifetimeBenchmarks.cs | 115 +++++++++++++++++- GFramework.Cqrs.Benchmarks/README.md | 3 +- .../todos/cqrs-rewrite-migration-tracking.md | 39 +++--- .../traces/cqrs-rewrite-migration-trace.md | 37 ++++++ 6 files changed, 174 insertions(+), 26 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs index 80ce9b88..d7bbdfc5 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs @@ -21,7 +21,7 @@ using GeneratedMediator = Mediator.Mediator; namespace GFramework.Cqrs.Benchmarks.Messaging; /// -/// 对比单处理器 notification 在 GFramework.CQRS 与 MediatR 之间的 publish 开销。 +/// 对比单处理器 notification 在 GFramework.CQRS、NuGet `Mediator` 与 MediatR 之间的 publish 开销。 /// [Config(typeof(Config))] public class NotificationBenchmarks @@ -132,7 +132,7 @@ public class NotificationBenchmarks MediatR.INotification; /// - /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 notification handler。 + /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 notification handler。 /// public sealed class BenchmarkNotificationHandler : GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler, diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs index 6e23cfe8..ff5ca2c4 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs @@ -157,7 +157,7 @@ public class RequestBenchmarks public sealed record BenchmarkResponse(Guid Id); /// - /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。 + /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 request handler。 /// public sealed class BenchmarkRequestHandler : GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler, diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs index f96deeef..0e4affc2 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs @@ -16,6 +16,7 @@ using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; using MediatR; using Microsoft.Extensions.DependencyInjection; +using GeneratedMediator = Mediator.Mediator; namespace GFramework.Cqrs.Benchmarks.Messaging; @@ -35,7 +36,9 @@ public class RequestLifetimeBenchmarks private ScopedBenchmarkContainer? _scopedContainer; private ICqrsRuntime? _scopedRuntime; private ServiceProvider _serviceProvider = null!; + private ServiceProvider _mediatorServiceProvider = null!; private IMediator? _mediatr; + private GeneratedMediator? _mediator; private BenchmarkRequestHandler _baselineHandler = null!; private BenchmarkRequest _request = null!; private ILogger _runtimeLogger = null!; @@ -83,7 +86,7 @@ public class RequestLifetimeBenchmarks } /// - /// 构建当前生命周期下的 GFramework 与 MediatR request 对照宿主。 + /// 构建当前生命周期下的 GFramework、NuGet `Mediator` 与 MediatR request 对照宿主。 /// [GlobalSetup] public void Setup() @@ -130,6 +133,12 @@ public class RequestLifetimeBenchmarks { _mediatr = _serviceProvider.GetRequiredService(); } + + _mediatorServiceProvider = CreateMediatorServiceProvider(Lifetime); + if (Lifetime != HandlerLifetime.Scoped) + { + _mediator = _mediatorServiceProvider.GetRequiredService(); + } } /// @@ -140,7 +149,7 @@ public class RequestLifetimeBenchmarks { try { - BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider); + BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider, _mediatorServiceProvider); } finally { @@ -196,6 +205,24 @@ public class RequestLifetimeBenchmarks return _mediatr!.Send(_request, CancellationToken.None); } + /// + /// 通过 `Mediator` source-generated concrete mediator 发送 request,作为 compile-time 对照。 + /// + /// 代表当前 `Mediator` request dispatch 完成的值任务。 + [Benchmark] + public ValueTask SendRequest_Mediator() + { + if (Lifetime == HandlerLifetime.Scoped) + { + return SendScopedMediatorRequestAsync( + _mediatorServiceProvider, + _request, + CancellationToken.None); + } + + return _mediator!.Send(_request, CancellationToken.None); + } + /// /// 按生命周期把 benchmark request handler 注册到 GFramework 容器。 /// @@ -243,12 +270,83 @@ public class RequestLifetimeBenchmarks }; } + /// + /// 构建只承载当前 benchmark request handler 的最小 `Mediator` 对照宿主,并按生命周期切换生成器注册形状。 + /// + /// 待比较的 handler 生命周期。 + /// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。 + private static ServiceProvider CreateMediatorServiceProvider(HandlerLifetime lifetime) + { + return lifetime switch + { + HandlerLifetime.Singleton => CreateSingletonMediatorServiceProvider(), + HandlerLifetime.Scoped => CreateScopedMediatorServiceProvider(), + HandlerLifetime.Transient => CreateTransientMediatorServiceProvider(), + _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.") + }; + } + + /// + /// 在真实的 request 级作用域内执行一次 `Mediator` request 分发。 + /// + /// 请求响应类型。 + /// 当前 benchmark 的根 。 + /// 要发送的 request。 + /// 取消令牌。 + /// 当前 request 的响应结果。 + private static async ValueTask SendScopedMediatorRequestAsync( + ServiceProvider rootServiceProvider, + Mediator.IRequest request, + CancellationToken cancellationToken = default) + { + ArgumentNullException.ThrowIfNull(rootServiceProvider); + ArgumentNullException.ThrowIfNull(request); + + using var scope = rootServiceProvider.CreateScope(); + var mediator = scope.ServiceProvider.GetRequiredService(); + return await mediator.Send(request, cancellationToken).ConfigureAwait(false); + } + + /// + /// 构建 singleton 生命周期的 `Mediator` 对照宿主。 + /// + /// 按 singleton 形状生成 DI 注册的 `Mediator` 宿主。 + private static ServiceProvider CreateSingletonMediatorServiceProvider() + { + var services = new ServiceCollection(); + services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton); + return services.BuildServiceProvider(); + } + + /// + /// 构建 scoped 生命周期的 `Mediator` 对照宿主。 + /// + /// 按 scoped 形状生成 DI 注册的 `Mediator` 宿主。 + private static ServiceProvider CreateScopedMediatorServiceProvider() + { + var services = new ServiceCollection(); + services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Scoped); + return services.BuildServiceProvider(); + } + + /// + /// 构建 transient 生命周期的 `Mediator` 对照宿主。 + /// + /// 按 transient 形状生成 DI 注册的 `Mediator` 宿主。 + private static ServiceProvider CreateTransientMediatorServiceProvider() + { + var services = new ServiceCollection(); + services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Transient); + return services.BuildServiceProvider(); + } + /// /// Benchmark request。 /// /// 请求标识。 public sealed record BenchmarkRequest(Guid Id) : GFramework.Cqrs.Abstractions.Cqrs.IRequest, + Mediator.IRequest, MediatR.IRequest; /// @@ -258,10 +356,11 @@ public class RequestLifetimeBenchmarks public sealed record BenchmarkResponse(Guid Id); /// - /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。 + /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 request handler。 /// public sealed class BenchmarkRequestHandler : GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler, + Mediator.IRequestHandler, MediatR.IRequestHandler { /// @@ -272,6 +371,16 @@ public class RequestLifetimeBenchmarks return ValueTask.FromResult(new BenchmarkResponse(request.Id)); } + /// + /// 处理 NuGet `Mediator` request。 + /// + ValueTask Mediator.IRequestHandler.Handle( + BenchmarkRequest request, + CancellationToken cancellationToken) + { + return Handle(request, cancellationToken); + } + /// /// 处理 MediatR request。 /// diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md index dd943daa..c50f93b2 100644 --- a/GFramework.Cqrs.Benchmarks/README.md +++ b/GFramework.Cqrs.Benchmarks/README.md @@ -16,7 +16,7 @@ - `Messaging/RequestBenchmarks.cs` - direct handler、默认 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path、`MediatR` - `Messaging/RequestLifetimeBenchmarks.cs` - - `Singleton / Scoped / Transient` 三类 handler 生命周期下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime 与 `MediatR` + - `Singleton / Scoped / Transient` 三类 handler 生命周期下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path 与 `MediatR` - `Messaging/RequestPipelineBenchmarks.cs` - `0 / 1 / 4` 个 pipeline 行为下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime 与 `MediatR` - `Messaging/RequestInvokerBenchmarks.cs` @@ -104,5 +104,4 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro ## 当前缺口 - 当前没有 stream 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 现在只覆盖 `GFramework.Cqrs` 与 `MediatR` -- 当前没有 request 生命周期下的 NuGet `Mediator` compile-time lifetime 矩阵;`RequestLifetimeBenchmarks` 只覆盖 `GFramework.Cqrs` 与 `MediatR` - 当前没有 notification fan-out 的生命周期矩阵;`NotificationFanOutBenchmarks` 只覆盖固定 `4 handler` 的已装配宿主 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 10260065..9ded9794 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 @@ -12,7 +12,7 @@ CQRS 迁移与收敛。 ## 当前恢复点 -- 恢复点编号:`CQRS-REWRITE-RP-139` +- 恢复点编号:`CQRS-REWRITE-RP-140` - 当前阶段:`Phase 8` - 当前 PR 锚点:`PR #349(已于 2026-05-12 合并到 origin/main)` - 当前结论: @@ -23,14 +23,14 @@ CQRS 迁移与收敛。 - README 一致性盘点成立,`GFramework.Cqrs.Benchmarks/README.md` 的 startup coverage / 解释边界仍可收紧 - benchmark XML 缺口盘点存在明显误报;代表文件中的 class / benchmark 方法 `` 与 `` 已实际存在 - 因此本轮不接受新的大范围 XML 收口波次,避免把上下文预算消耗在错误候选上 - - 本轮 accepted delegated scope 收敛为单文件 docs-only worker: - - `GFramework.Cqrs.Benchmarks/README.md` - - 明确 `StreamStartupBenchmarks` 现已覆盖 `MediatR`、`GFramework.Cqrs` reflection、 - `GFramework.Cqrs` generated、NuGet `Mediator` 四组 initialization / cold-start 对照 - - 补充 `RequestStartupBenchmarks` 与 `NotificationStartupBenchmarks` 的 - `GFramework.Cqrs` 路径是“单 handler 最小宿主 + 手工注册”的 startup / cold-start 模型,不外推到程序集扫描、 - 完整注册协调器、fan-out 或发布策略变体 - - 当前决定在该 docs-only 收口后停在自然边界: + - 本轮主线程将 `RequestLifetimeBenchmarks` 的 request lifetime 矩阵扩展为 baseline、`GFramework.Cqrs`、NuGet `Mediator` 与 `MediatR` 四组对照: + - `BenchmarkRequest` 与 `BenchmarkRequestHandler` 增补 `Mediator` 契约实现 + - `Mediator` 宿主按 `Singleton / Scoped / Transient` 三种编译期常量分支生成,满足 source generator 的配置约束 + - `Scoped` 路径通过显式 `CreateScope()` 解析 generated `Mediator.Mediator`,保持与现有 request scoped 对照相同的作用域边界 + - 本轮同时收口两处 benchmark XML 文档漂移: + - `RequestBenchmarks` 的 handler 说明补齐 NuGet `Mediator` 契约事实 + - `NotificationBenchmarks` 的类说明与 handler 说明补齐 NuGet `Mediator` 对照事实 + - 当前决定在该 parity + docs 收口后停在自然边界: - branch-size 仍低于 `50 files` - 但下一批低风险候选已不再清晰;继续开波次的收益低于评审与上下文成本 - tests 侧此前已补齐并提交: @@ -50,6 +50,9 @@ CQRS 迁移与收敛。 - 当前分支:`feat/cqrs-optimization` - 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)` - 当前写面: + - `GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs` + - `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/README.md` - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` @@ -57,7 +60,7 @@ CQRS 迁移与收敛。 - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` - 当前已提交 branch diff:`14 files` - 当前分支比 `origin/main` 多 `5` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07`、`c32a1ec4` - - 当前未提交面由 benchmark README 的 startup 边界同步与 `ai-plan` 恢复点更新构成 + - 当前工作面已收口为 request lifetime parity、两处 benchmark XML 文档修正与对应 `README` / `ai-plan` 同步 - 本轮提交: - `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` - `ab422b05` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释` @@ -67,28 +70,28 @@ CQRS 迁移与收敛。 ## 当前风险 - `StreamStartupBenchmarks` 的 `Mediator` parity 目前只做了编译验证,尚未单独执行 benchmark 作业确认 startup 矩阵运行结果。 -- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;该项涉及 `BenchmarkHostFactory` 与 compile-time lifetime 形状,不再是本轮低风险切片。 +- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;虽然 `RequestLifetimeBenchmarks` 已证明 `Mediator` 的 compile-time lifetime 可通过常量分支接入,但 stream 侧仍需要额外处理 `IAsyncEnumerable` 与作用域覆盖整个枚举周期的组合边界。 - benchmark XML 盘点若再次依赖粗糙脚本或只读 inventory,仍有把已存在文档误记为缺口的风险;后续若再开 XML 波次,必须先用主线程抽样核对代表文件。 -- 本轮已在 README 精度同步后主动停批次;若后续恢复,优先先做 `StreamStartupBenchmarks` smoke run 或更明确的 parity / docs 候选,而不是继续机械扩张 XML 批次。 +- 本轮已在 request lifetime parity 与文档漂移收口后主动停批次;若后续恢复,优先先做 `StreamStartupBenchmarks` smoke run 或更明确的 parity / docs 候选,而不是继续机械扩张 XML 批次。 ## 最近权威验证 - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - 结果:通过,`0 warning / 0 error` + - 备注:确认 `RequestLifetimeBenchmarks` 的 NuGet `Mediator` lifetime parity 可在当前生成器约束下编译通过 - `python3 scripts/license-header.py --check` - 结果:通过 - `$gframework-pr-review` - 结果:`PR #349` 已关闭;latest-head review open thread 经本地核对仅剩 `StreamingBenchmarks.Stream_MediatR()` 的 XML 文档缺口仍成立 -- `git --git-dir=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs --work-tree=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs diff -- GFramework.Cqrs.Benchmarks/README.md` +- `git --git-dir=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs --work-tree=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs diff -- GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` - 结果:通过 - - 备注:确认本轮 worker 仅修改 README 的 startup coverage / 边界文案 + - 备注:确认本轮代码面收敛在 request lifetime parity 与两处 XML 文档漂移修正 ## 下一推荐步骤 -1. 串行运行 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release`、`python3 scripts/license-header.py --check --paths ...` 与 `git diff --check`,作为本轮 docs-only 收尾的权威验证。 -2. 提交当前 README 与 `ai-plan` 更新,回到干净工作树。 -3. 若后续继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 -4. 若后续再开文档批次,先用主线程核对代表文件,再决定是否存在真实 XML 缺口;不要直接沿用误报 inventory 扩批。 +1. 若后续继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 +2. 若后续评估 `StreamLifetimeBenchmarks` 的 `Mediator` parity,先复用本轮 request lifetime 的“编译期常量 lifetime 分支”经验,再判断是否值得引入新的 scoped stream helper。 +3. 若后续再开 XML / docs 批次,先由主线程逐文件核对代表样本,不要直接沿用误报 inventory 扩批。 ## 活跃文档 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 30c89650..9848ad42 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 @@ -7,6 +7,43 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-12 +### 阶段:request lifetime 的 Mediator parity 与文档漂移收口(CQRS-REWRITE-RP-140) + +- 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。 +- 本轮启动时重新测得当前已提交 branch diff 仍为 `14 files / 324 lines`,远低于 `50 files` 阈值;停止与否继续由 context-budget / reviewability 主导。 +- 主线程结合两个 explorer 子代理的只读盘点后,接受以下结论: + - 不再继续按 benchmark XML `` inventory 机械扩批;粗糙脚本会把“注释位于 `[Benchmark]` 之前”的现有文档误判为缺口 + - `RequestLifetimeBenchmarks` 的 NuGet `Mediator` lifetime parity 是当前仍然真实、且能保持 reviewable 的实现候选 + - `NotificationBenchmarks.cs` 与 `RequestBenchmarks.cs` 仍有两处低风险 XML 文档漂移,均只涉及 NuGet `Mediator` 事实同步 +- 本轮主线程实施: + - `RequestLifetimeBenchmarks.cs` + - 新增 `GeneratedMediator` 宿主字段、`SendRequest_Mediator()` benchmark 方法与 scoped `Mediator` request helper + - 将 `BenchmarkRequest` / `BenchmarkRequestHandler` 扩为同时实现 `Mediator` 契约 + - 为 `Mediator` 宿主改用 `Singleton / Scoped / Transient` 三个编译期常量分支,规避 `MSG0007` 对运行时 lifetime 赋值的生成器限制 + - `RequestBenchmarks.cs` + - 将 handler XML 文档说明补齐为同时实现 `GFramework.CQRS`、NuGet `Mediator` 与 `MediatR` + - `NotificationBenchmarks.cs` + - 将类说明与 handler XML 文档说明补齐为同时覆盖 `GFramework.CQRS`、NuGet `Mediator` 与 `MediatR` + - `GFramework.Cqrs.Benchmarks/README.md` + - 将 `RequestLifetimeBenchmarks` 的 coverage 更新为包含 NuGet `Mediator` source-generated concrete path + - 删除“当前没有 request 生命周期下的 NuGet `Mediator` compile-time lifetime 矩阵”这一已过时缺口 +- 验证里程碑: + - 第一次 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:失败 + - 原因:`RequestLifetimeBenchmarks.cs` 中基于运行时变量写入 `MediatorOptions.ServiceLifetime`,触发 `MSG0007` + - 主线程修正: + - 将 `CreateMediatorServiceProvider(HandlerLifetime lifetime)` 收口为 3 个常量分支工厂: + `CreateSingletonMediatorServiceProvider()`、`CreateScopedMediatorServiceProvider()`、`CreateTransientMediatorServiceProvider()` + - 第二次 `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:通过,`0 warning / 0 error` +- 当前 stop decision: + - 不继续开启新的实现波次 + - 原因不是 branch-size 阈值耗尽;当前分支仍只有 `14 files` + - 停止原因是本轮已经完成一条真实 parity 收口和两处文档漂移修正,继续扩到 `StreamLifetimeBenchmarks` 会显著提高作用域与 review 成本 +- 当前下一步: + - 主线程补跑 `python3 scripts/license-header.py --check --paths ...` 与 `git diff --check` + - 更新 active tracking / trace 后提交当前 benchmark 代码、README 与 `ai-plan` + ### 阶段:README startup coverage 精度同步并停在自然边界(CQRS-REWRITE-RP-139) - 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。 From 2dd9435ceac4556d05c27528330c127bfcf878b0 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Tue, 12 May 2026 16:41:14 +0800 Subject: [PATCH 8/8] =?UTF-8?q?fix(cqrs-benchmarks):=20=E4=BF=AE=E6=AD=A3M?= =?UTF-8?q?ediator=E5=9F=BA=E5=87=86=E8=BF=90=E8=A1=8C=E6=97=B6=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 BenchmarkHostFactory 的 Mediator 宿主接线,统一使用 Singleton 编译期 lifetime - 更新 RequestLifetimeBenchmarks 与 README,撤回当前不可运行的 Mediator 生命周期矩阵并同步覆盖边界 - 补充 cqrs-rewrite 跟踪,记录 PR #350 评审真值、故障根因与 smoke run 验证结果 --- .../Messaging/BenchmarkHostFactory.cs | 9 +- .../Messaging/RequestLifetimeBenchmarks.cs | 123 ++---------------- GFramework.Cqrs.Benchmarks/README.md | 3 +- .../todos/cqrs-rewrite-migration-tracking.md | 75 +++++------ .../traces/cqrs-rewrite-migration-trace.md | 34 +++++ 5 files changed, 82 insertions(+), 162 deletions(-) diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs index 34397dc1..1753f9aa 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs @@ -275,15 +275,16 @@ internal static class BenchmarkHostFactory /// 补充当前场景的显式服务注册。 /// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。 /// - /// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入, - /// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵, - /// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config,而不是在同一编译产物里混用多个 lifetime。 + /// `Mediator` 的 DI lifetime 由 source generator 在编译期固定到整个位于当前项目中的生成产物上。 + /// 因此 benchmark 工程必须统一使用一套 compile-time 常量配置;这里显式收敛为 `Singleton`, + /// 避免同一编译产物里混入多个 `AddMediator` lifetime 形状后,在 BenchmarkDotNet 自动生成宿主中触发 + /// “generated code lifetime 与 runtime options 不一致”的启动失败。 /// internal static ServiceProvider CreateMediatorServiceProvider(Action? configure) { var services = new ServiceCollection(); configure?.Invoke(services); - services.AddMediator(); + services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton); return services.BuildServiceProvider(); } diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs index 0e4affc2..f12f83e6 100644 --- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs +++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs @@ -16,7 +16,6 @@ using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; using MediatR; using Microsoft.Extensions.DependencyInjection; -using GeneratedMediator = Mediator.Mediator; namespace GFramework.Cqrs.Benchmarks.Messaging; @@ -27,6 +26,10 @@ namespace GFramework.Cqrs.Benchmarks.Messaging; /// 当前矩阵覆盖 `Singleton`、`Scoped` 与 `Transient`。 /// 其中 `Scoped` 会在每次 request 分发时显式创建并释放真实的 DI 作用域, /// 避免把 scoped handler 错误地压到根容器解析而扭曲生命周期对照。 +/// NuGet `Mediator` 的 DI lifetime 由 source generator 在编译期固定到整个 benchmark 项目, +/// 不能在同一份生成产物里同时切换 `Singleton`、`Scoped` 与 `Transient`。 +/// 因此该矩阵当前只比较 `GFramework.Cqrs` 与 `MediatR` 的生命周期开销;`Mediator` 仍保留在其他 +/// steady-state / startup benchmark 中作为单一 compile-time 形状对照。 /// [Config(typeof(Config))] public class RequestLifetimeBenchmarks @@ -36,9 +39,7 @@ public class RequestLifetimeBenchmarks private ScopedBenchmarkContainer? _scopedContainer; private ICqrsRuntime? _scopedRuntime; private ServiceProvider _serviceProvider = null!; - private ServiceProvider _mediatorServiceProvider = null!; private IMediator? _mediatr; - private GeneratedMediator? _mediator; private BenchmarkRequestHandler _baselineHandler = null!; private BenchmarkRequest _request = null!; private ILogger _runtimeLogger = null!; @@ -86,7 +87,7 @@ public class RequestLifetimeBenchmarks } /// - /// 构建当前生命周期下的 GFramework、NuGet `Mediator` 与 MediatR request 对照宿主。 + /// 构建当前生命周期下的 GFramework 与 MediatR request 对照宿主。 /// [GlobalSetup] public void Setup() @@ -133,12 +134,6 @@ public class RequestLifetimeBenchmarks { _mediatr = _serviceProvider.GetRequiredService(); } - - _mediatorServiceProvider = CreateMediatorServiceProvider(Lifetime); - if (Lifetime != HandlerLifetime.Scoped) - { - _mediator = _mediatorServiceProvider.GetRequiredService(); - } } /// @@ -149,7 +144,7 @@ public class RequestLifetimeBenchmarks { try { - BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider, _mediatorServiceProvider); + BenchmarkCleanupHelper.DisposeAll(_container, _serviceProvider); } finally { @@ -206,25 +201,7 @@ public class RequestLifetimeBenchmarks } /// - /// 通过 `Mediator` source-generated concrete mediator 发送 request,作为 compile-time 对照。 - /// - /// 代表当前 `Mediator` request dispatch 完成的值任务。 - [Benchmark] - public ValueTask SendRequest_Mediator() - { - if (Lifetime == HandlerLifetime.Scoped) - { - return SendScopedMediatorRequestAsync( - _mediatorServiceProvider, - _request, - CancellationToken.None); - } - - return _mediator!.Send(_request, CancellationToken.None); - } - - /// - /// 按生命周期把 benchmark request handler 注册到 GFramework 容器。 + /// 按生命周期把 benchmark request handler 注册到 GFramework 容器。 /// /// 当前 benchmark 拥有并负责释放的容器。 /// 待比较的 handler 生命周期。 @@ -271,82 +248,11 @@ public class RequestLifetimeBenchmarks } /// - /// 构建只承载当前 benchmark request handler 的最小 `Mediator` 对照宿主,并按生命周期切换生成器注册形状。 - /// - /// 待比较的 handler 生命周期。 - /// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。 - private static ServiceProvider CreateMediatorServiceProvider(HandlerLifetime lifetime) - { - return lifetime switch - { - HandlerLifetime.Singleton => CreateSingletonMediatorServiceProvider(), - HandlerLifetime.Scoped => CreateScopedMediatorServiceProvider(), - HandlerLifetime.Transient => CreateTransientMediatorServiceProvider(), - _ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.") - }; - } - - /// - /// 在真实的 request 级作用域内执行一次 `Mediator` request 分发。 - /// - /// 请求响应类型。 - /// 当前 benchmark 的根 。 - /// 要发送的 request。 - /// 取消令牌。 - /// 当前 request 的响应结果。 - private static async ValueTask SendScopedMediatorRequestAsync( - ServiceProvider rootServiceProvider, - Mediator.IRequest request, - CancellationToken cancellationToken = default) - { - ArgumentNullException.ThrowIfNull(rootServiceProvider); - ArgumentNullException.ThrowIfNull(request); - - using var scope = rootServiceProvider.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); - return await mediator.Send(request, cancellationToken).ConfigureAwait(false); - } - - /// - /// 构建 singleton 生命周期的 `Mediator` 对照宿主。 - /// - /// 按 singleton 形状生成 DI 注册的 `Mediator` 宿主。 - private static ServiceProvider CreateSingletonMediatorServiceProvider() - { - var services = new ServiceCollection(); - services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton); - return services.BuildServiceProvider(); - } - - /// - /// 构建 scoped 生命周期的 `Mediator` 对照宿主。 - /// - /// 按 scoped 形状生成 DI 注册的 `Mediator` 宿主。 - private static ServiceProvider CreateScopedMediatorServiceProvider() - { - var services = new ServiceCollection(); - services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Scoped); - return services.BuildServiceProvider(); - } - - /// - /// 构建 transient 生命周期的 `Mediator` 对照宿主。 - /// - /// 按 transient 形状生成 DI 注册的 `Mediator` 宿主。 - private static ServiceProvider CreateTransientMediatorServiceProvider() - { - var services = new ServiceCollection(); - services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Transient); - return services.BuildServiceProvider(); - } - - /// - /// Benchmark request。 + /// Benchmark request。 /// /// 请求标识。 public sealed record BenchmarkRequest(Guid Id) : GFramework.Cqrs.Abstractions.Cqrs.IRequest, - Mediator.IRequest, MediatR.IRequest; /// @@ -356,11 +262,10 @@ public class RequestLifetimeBenchmarks public sealed record BenchmarkResponse(Guid Id); /// - /// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 request handler。 + /// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。 /// public sealed class BenchmarkRequestHandler : GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler, - Mediator.IRequestHandler, MediatR.IRequestHandler { /// @@ -371,16 +276,6 @@ public class RequestLifetimeBenchmarks return ValueTask.FromResult(new BenchmarkResponse(request.Id)); } - /// - /// 处理 NuGet `Mediator` request。 - /// - ValueTask Mediator.IRequestHandler.Handle( - BenchmarkRequest request, - CancellationToken cancellationToken) - { - return Handle(request, cancellationToken); - } - /// /// 处理 MediatR request。 /// diff --git a/GFramework.Cqrs.Benchmarks/README.md b/GFramework.Cqrs.Benchmarks/README.md index c50f93b2..c4ac3315 100644 --- a/GFramework.Cqrs.Benchmarks/README.md +++ b/GFramework.Cqrs.Benchmarks/README.md @@ -16,7 +16,7 @@ - `Messaging/RequestBenchmarks.cs` - direct handler、默认 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path、`MediatR` - `Messaging/RequestLifetimeBenchmarks.cs` - - `Singleton / Scoped / Transient` 三类 handler 生命周期下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime、NuGet `Mediator` source-generated concrete path 与 `MediatR` + - `Singleton / Scoped / Transient` 三类 handler 生命周期下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime 与 `MediatR` - `Messaging/RequestPipelineBenchmarks.cs` - `0 / 1 / 4` 个 pipeline 行为下,baseline、默认 generated-provider 宿主接线的 `GFramework.Cqrs` runtime 与 `MediatR` - `Messaging/RequestInvokerBenchmarks.cs` @@ -104,4 +104,5 @@ dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.cspro ## 当前缺口 - 当前没有 stream 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`StreamLifetimeBenchmarks` 现在只覆盖 `GFramework.Cqrs` 与 `MediatR` +- 当前没有 request 生命周期版的 NuGet `Mediator` source-generated concrete path 对照;`Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定,若要比较 `Singleton / Scoped / Transient`,需要拆成独立 build config 或独立 benchmark 工程,而不是在同一份生成产物里切换 - 当前没有 notification fan-out 的生命周期矩阵;`NotificationFanOutBenchmarks` 只覆盖固定 `4 handler` 的已装配宿主 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 9ded9794..add78968 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 @@ -14,45 +14,33 @@ CQRS 迁移与收敛。 - 恢复点编号:`CQRS-REWRITE-RP-140` - 当前阶段:`Phase 8` -- 当前 PR 锚点:`PR #349(已于 2026-05-12 合并到 origin/main)` +- 当前 PR 锚点:`PR #350(OPEN,2026-05-12)` - 当前结论: - - 本轮按 `$gframework-batch-boot 50` 恢复后,先重新确认基线仍为 - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`,当前已提交 branch diff 为 `14 files`,仍远低于 `50 files` - 阈值;是否继续的主停止信号仍是 context-budget / reviewability,而不是 branch-size 预算。 - - 主线程结合本地抽样核对与两个 explorer 子代理的只读盘点后,确认当前不应再继续按“benchmark XML `` 批量缺口”扩批: - - README 一致性盘点成立,`GFramework.Cqrs.Benchmarks/README.md` 的 startup coverage / 解释边界仍可收紧 - - benchmark XML 缺口盘点存在明显误报;代表文件中的 class / benchmark 方法 `` 与 `` 已实际存在 - - 因此本轮不接受新的大范围 XML 收口波次,避免把上下文预算消耗在错误候选上 - - 本轮主线程将 `RequestLifetimeBenchmarks` 的 request lifetime 矩阵扩展为 baseline、`GFramework.Cqrs`、NuGet `Mediator` 与 `MediatR` 四组对照: - - `BenchmarkRequest` 与 `BenchmarkRequestHandler` 增补 `Mediator` 契约实现 - - `Mediator` 宿主按 `Singleton / Scoped / Transient` 三种编译期常量分支生成,满足 source generator 的配置约束 - - `Scoped` 路径通过显式 `CreateScope()` 解析 generated `Mediator.Mediator`,保持与现有 request scoped 对照相同的作用域边界 - - 本轮同时收口两处 benchmark XML 文档漂移: - - `RequestBenchmarks` 的 handler 说明补齐 NuGet `Mediator` 契约事实 - - `NotificationBenchmarks` 的类说明与 handler 说明补齐 NuGet `Mediator` 对照事实 - - 当前决定在该 parity + docs 收口后停在自然边界: - - branch-size 仍低于 `50 files` - - 但下一批低风险候选已不再清晰;继续开波次的收益低于评审与上下文成本 - - tests 侧此前已补齐并提交: - - `CqrsRegistrationServiceTests`:补空输入、空项过滤、稳定键排序与跨调用跳过边界 - - `CqrsHandlerRegistrarTests` 与 `CqrsHandlerRegistrarFallbackFailureTests`: - 补 abstract registry 与缺少无参构造器 registry 的回退 / 抛错覆盖 - - `CqrsNotificationPublisherTests`:补“零 publisher 回退到默认顺序发布器并缓存”回归 - - benchmark 侧此前已补齐并提交: - - `StreamPipelineBenchmarks` - - `StreamingBenchmarks` 的 steady-state `Mediator` 对照 - - `GFramework.Cqrs.Benchmarks/README.md` 的 stream coverage / gap 同步 - - `StreamStartupBenchmarks` 的 `Mediator` initialization / cold-start 对照 - - 本轮未修改 `GFramework.Cqrs` 运行时代码;notification fallback 与 generated registry 激活守卫均由新回归证明现有实现已满足预期。 + - 本轮按 `$gframework-pr-review` 重新抓取 GitHub 真值后,确认当前公开 PR 不是已合并的 `PR #349`,而是仍处于 `OPEN` 状态的 `PR #350`。 + - 最新 AI review 只有 1 条 Greptile open thread,关注点是: + - `StreamStartupBenchmarks.ColdStart_Mediator()` 与 `RequestLifetimeBenchmarks.SendRequest_Mediator()` 先前只做了编译验证,未实际 smoke-run + - 主线程按 review 提示执行最小 benchmark smoke run 后,确认 Greptile 线程不是误报,而是命中了真实运行时缺陷: + - `StreamStartupBenchmarks.ColdStart_Mediator()` 在 BenchmarkDotNet 自动生成宿主里抛出 + `Invalid configuration detected for Mediator. Generated code for 'Transient' lifetime, but got 'Singleton' lifetime from options.` + - `RequestLifetimeBenchmarks.SendRequest_Mediator()` 的 `Singleton / Scoped` 也抛出同类异常;只有 `Transient` 变体能跑通 + - 根因确认: + - NuGet `Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定 + - 当前工程同时存在默认 `AddMediator()` 与 request lifetime 场景下的 `AddMediator(options => options.ServiceLifetime = ...)` + - 这会让同一份生成产物在 BenchmarkDotNet 自动生成宿主中出现 compile-time 形状与 runtime options 不一致 + - 本轮收口策略: + - `BenchmarkHostFactory.CreateMediatorServiceProvider()` 统一显式固定为 `Singleton` compile-time lifetime + - `RequestLifetimeBenchmarks` 撤回当前无法真实运行的 `Mediator` 生命周期矩阵,只保留 `GFramework.Cqrs` 与 `MediatR` + - `GFramework.Cqrs.Benchmarks/README.md` 同步收窄 request lifetime coverage,并把 `Mediator` 生命周期矩阵改记为当前缺口 + - 本轮未修改 `GFramework.Cqrs` 运行时代码;修复面限定在 benchmark 宿主装配与 reader-facing docs。 ## 当前活跃事实 - 当前分支:`feat/cqrs-optimization` - 当前 PR:`PR #349(已合并;当前分支暂无新的公开 PR)` +- 当前 PR:`PR #350(OPEN)` - 当前写面: + - `GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs` - `GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs` - - `GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` - `GFramework.Cqrs.Benchmarks/README.md` - `ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md` - `ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md` @@ -60,7 +48,7 @@ CQRS 迁移与收敛。 - `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)` - 当前已提交 branch diff:`14 files` - 当前分支比 `origin/main` 多 `5` 个提交:`f346110a`、`a016e3d4`、`ab422b05`、`555c7c07`、`c32a1ec4` - - 当前工作面已收口为 request lifetime parity、两处 benchmark XML 文档修正与对应 `README` / `ai-plan` 同步 +- 当前工作面已收口为 `Mediator` benchmark runtime 配置修正、request lifetime coverage 收窄与对应 `README` / `ai-plan` 同步 - 本轮提交: - `f346110a` `feat(cqrs-benchmarks): 补齐 stream startup 的 Mediator 对照路径` - `ab422b05` `docs(cqrs-benchmarks): 补齐 request benchmark 返回值注释` @@ -69,28 +57,29 @@ CQRS 迁移与收敛。 ## 当前风险 -- `StreamStartupBenchmarks` 的 `Mediator` parity 目前只做了编译验证,尚未单独执行 benchmark 作业确认 startup 矩阵运行结果。 -- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;虽然 `RequestLifetimeBenchmarks` 已证明 `Mediator` 的 compile-time lifetime 可通过常量分支接入,但 stream 侧仍需要额外处理 `IAsyncEnumerable` 与作用域覆盖整个枚举周期的组合边界。 +- `StreamLifetimeBenchmarks` 仍缺 `Mediator` parity;如果后续要补,必须采用独立 compile-time config 或独立 benchmark 工程,而不是在当前项目里切换 runtime `ServiceLifetime`。 +- `RequestLifetimeBenchmarks` 目前不再覆盖 `Mediator`;若后续要恢复该矩阵,也必须先解决 source-generated lifetime 与 BenchmarkDotNet 自动宿主的编译期塑形边界。 - benchmark XML 盘点若再次依赖粗糙脚本或只读 inventory,仍有把已存在文档误记为缺口的风险;后续若再开 XML 波次,必须先用主线程抽样核对代表文件。 -- 本轮已在 request lifetime parity 与文档漂移收口后主动停批次;若后续恢复,优先先做 `StreamStartupBenchmarks` smoke run 或更明确的 parity / docs 候选,而不是继续机械扩张 XML 批次。 +- 当前 PR 的 Greptile open thread 在代码修正后虽已有本地验证证据,但线程本身还未在 GitHub 上回复 / resolve。 ## 最近权威验证 - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` - 结果:通过,`0 warning / 0 error` - - 备注:确认 `RequestLifetimeBenchmarks` 的 NuGet `Mediator` lifetime parity 可在当前生成器约束下编译通过 -- `python3 scripts/license-header.py --check` + - 备注:确认统一 `Mediator` compile-time lifetime 后 benchmark 工程仍可编译 +- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-stream-startup-mediator-fixed --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` - 结果:通过 + - 备注:`ColdStart_Mediator` 已在 BenchmarkDotNet 自动生成宿主中实际执行,约 `144.036 us / 69.3 KB` +- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-fixed-rerun --filter "*RequestLifetimeBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - 结果:通过 + - 备注:当前矩阵为 `9` 项(baseline / `GFramework.Cqrs` / `MediatR` * `Singleton|Scoped|Transient`),不再包含伪 `Mediator` lifetime 条目 - `$gframework-pr-review` - - 结果:`PR #349` 已关闭;latest-head review open thread 经本地核对仅剩 `StreamingBenchmarks.Stream_MediatR()` 的 XML 文档缺口仍成立 -- `git --git-dir=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework/.git/worktrees/GFramework-cqrs --work-tree=/mnt/f/gewuyou/System/Documents/WorkSpace/GameDev/GFramework-WorkTree/GFramework-cqrs diff -- GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/RequestBenchmarks.cs GFramework.Cqrs.Benchmarks/Messaging/NotificationBenchmarks.cs` - - 结果:通过 - - 备注:确认本轮代码面收敛在 request lifetime parity 与两处 XML 文档漂移修正 + - 结果:确认 `PR #350` open,CodeRabbit 已 `APPROVED`,Greptile 仍有 `1` 条 open thread 指向 `StreamStartupBenchmarks.cs` ## 下一推荐步骤 -1. 若后续继续 benchmark 波次,优先单独执行 `StreamStartupBenchmarks` 的最小 smoke run,验证新加 `Mediator` startup 路径可运行。 -2. 若后续评估 `StreamLifetimeBenchmarks` 的 `Mediator` parity,先复用本轮 request lifetime 的“编译期常量 lifetime 分支”经验,再判断是否值得引入新的 scoped stream helper。 +1. 在 GitHub `PR #350` 回应并 resolve 当前 Greptile 线程,说明 `ColdStart_Mediator` 已补 smoke-run,且 request lifetime 的 `Mediator` 矩阵已按 source-generator 真实约束撤回。 +2. 若后续评估 `StreamLifetimeBenchmarks` 或 request lifetime 的 `Mediator` parity,优先设计独立 compile-time config / 独立 benchmark 工程,而不是继续在同一项目里切换 runtime `ServiceLifetime`。 3. 若后续再开 XML / docs 批次,先由主线程逐文件核对代表样本,不要直接沿用误报 inventory 扩批。 ## 活跃文档 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 9848ad42..71c6705c 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 @@ -7,6 +7,40 @@ SPDX-License-Identifier: Apache-2.0 ## 2026-05-12 +### 阶段:PR #350 的 Mediator runtime 配置收口(CQRS-REWRITE-RP-141) + +- 使用 `$gframework-pr-review` 重新抓取当前分支 PR,确认 GitHub 真值已从 active tracking 中过期的 `PR #349` 切换为仍处于 `OPEN` 状态的 `PR #350`。 +- 最新 AI review 只剩 1 条 Greptile open thread: + - `GFramework.Cqrs.Benchmarks/Messaging/StreamStartupBenchmarks.cs:210` + - 质疑点不是文档,而是 `Mediator` startup / request lifetime 新路径仅编译通过、未实际 smoke-run +- 主线程先按 review 建议做本地 smoke 验证,而不是直接回复线程: + - `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-stream-startup-mediator --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-mediator --filter "*RequestLifetimeBenchmarks.SendRequest_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` +- 第一轮 smoke 暴露出 Greptile 线程背后的真实运行时问题,而不是 review 噪音: + - `StreamStartupBenchmarks.ColdStart_Mediator()` 在 BenchmarkDotNet 自动生成宿主里抛出 + `Invalid configuration detected for Mediator. Generated code for 'Transient' lifetime, but got 'Singleton' lifetime from options.` + - `RequestLifetimeBenchmarks.SendRequest_Mediator()` 的 `Singleton` / `Scoped` 同样抛出相同异常,只有 `Transient` 分支能实际执行 +- 根因判断: + - `Mediator` 的 DI lifetime 由 source generator 在 benchmark 项目编译期固定 + - 当前项目同时包含默认 `AddMediator()` 和 request lifetime 场景里的 `AddMediator(options => options.ServiceLifetime = ...)` + - 同一份生成产物在 BenchmarkDotNet 自动生成宿主里因此出现 compile-time lifetime 与 runtime options 不一致 +- 主线程修复: + - `BenchmarkHostFactory.CreateMediatorServiceProvider()` 统一改为显式 `ServiceLifetime.Singleton` + - `RequestLifetimeBenchmarks` 删除当前无法真实运行的 `SendRequest_Mediator()` 与相关 `Mediator` 生命周期 helper / 契约实现 + - `GFramework.Cqrs.Benchmarks/README.md` 将 request lifetime coverage 收窄为 `GFramework.Cqrs` + `MediatR`,并把 `Mediator` lifetime parity 改记为当前缺口 +- 串行验证结果: + - `dotnet build GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release` + - 结果:通过,`0 warning / 0 error` + - `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-stream-startup-mediator-fixed --filter "*StreamStartupBenchmarks.ColdStart_Mediator*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - 结果:通过 + - 关键数值:`ColdStart_Mediator ≈ 144.036 us / 69.3 KB` + - `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --artifacts-suffix pr350-request-lifetime-fixed-rerun --filter "*RequestLifetimeBenchmarks*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1` + - 结果:通过 + - 关键结论:当前 request lifetime 矩阵已收敛为 `9` 项(baseline / `GFramework.Cqrs` / `MediatR` * `Singleton|Scoped|Transient`),不再包含伪 `Mediator` lifetime 条目 +- 本轮 stop decision: + - 不继续把 `Mediator` lifetime parity 硬扩到 request 或 stream lifetime benchmark + - 原因不是 branch-size;而是 source-generator compile-time config 已明确构成真实边界,继续在同一项目里扩 runtime 切换只会制造新的伪覆盖 + ### 阶段:request lifetime 的 Mediator parity 与文档漂移收口(CQRS-REWRITE-RP-140) - 继续按 `$gframework-batch-boot 50` 推进,基线保持为 `origin/main @ 2b2bec65 (2026-05-12 11:49:39 +0800)`。