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)`。