fix(cqrs-benchmarks): 修正Mediator基准运行时配置

- 修正 BenchmarkHostFactory 的 Mediator 宿主接线,统一使用 Singleton 编译期 lifetime

- 更新 RequestLifetimeBenchmarks 与 README,撤回当前不可运行的 Mediator 生命周期矩阵并同步覆盖边界

- 补充 cqrs-rewrite 跟踪,记录 PR #350 评审真值、故障根因与 smoke run 验证结果
This commit is contained in:
gewuyou 2026-05-12 16:41:14 +08:00
parent e3532fc2c8
commit 2dd9435cea
5 changed files with 82 additions and 162 deletions

View File

@ -275,15 +275,16 @@ internal static class BenchmarkHostFactory
/// <param name="configure">补充当前场景的显式服务注册。</param>
/// <returns>可直接解析 generated `Mediator.Mediator` 的 DI 宿主。</returns>
/// <remarks>
/// 当前 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 不一致”的启动失败。
/// </remarks>
internal static ServiceProvider CreateMediatorServiceProvider(Action<IServiceCollection>? configure)
{
var services = new ServiceCollection();
configure?.Invoke(services);
services.AddMediator();
services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton);
return services.BuildServiceProvider();
}

View File

@ -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 形状对照。
/// </remarks>
[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
}
/// <summary>
/// 构建当前生命周期下的 GFramework、NuGet `Mediator` 与 MediatR request 对照宿主。
/// 构建当前生命周期下的 GFramework 与 MediatR request 对照宿主。
/// </summary>
[GlobalSetup]
public void Setup()
@ -133,12 +134,6 @@ public class RequestLifetimeBenchmarks
{
_mediatr = _serviceProvider.GetRequiredService<IMediator>();
}
_mediatorServiceProvider = CreateMediatorServiceProvider(Lifetime);
if (Lifetime != HandlerLifetime.Scoped)
{
_mediator = _mediatorServiceProvider.GetRequiredService<GeneratedMediator>();
}
}
/// <summary>
@ -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
}
/// <summary>
/// 通过 `Mediator` source-generated concrete mediator 发送 request作为 compile-time 对照。
/// </summary>
/// <returns>代表当前 `Mediator` request dispatch 完成的值任务。</returns>
[Benchmark]
public ValueTask<BenchmarkResponse> SendRequest_Mediator()
{
if (Lifetime == HandlerLifetime.Scoped)
{
return SendScopedMediatorRequestAsync(
_mediatorServiceProvider,
_request,
CancellationToken.None);
}
return _mediator!.Send(_request, CancellationToken.None);
}
/// <summary>
/// 按生命周期把 benchmark request handler 注册到 GFramework 容器。
/// 按生命周期把 benchmark request handler 注册到 GFramework 容器。
/// </summary>
/// <param name="container">当前 benchmark 拥有并负责释放的容器。</param>
/// <param name="lifetime">待比较的 handler 生命周期。</param>
@ -271,82 +248,11 @@ public class RequestLifetimeBenchmarks
}
/// <summary>
/// 构建只承载当前 benchmark request handler 的最小 `Mediator` 对照宿主,并按生命周期切换生成器注册形状。
/// </summary>
/// <param name="lifetime">待比较的 handler 生命周期。</param>
/// <returns>可直接解析 generated `Mediator.Mediator` 的 DI 宿主。</returns>
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.")
};
}
/// <summary>
/// 在真实的 request 级作用域内执行一次 `Mediator` request 分发。
/// </summary>
/// <typeparam name="TResponse">请求响应类型。</typeparam>
/// <param name="rootServiceProvider">当前 benchmark 的根 <see cref="ServiceProvider" />。</param>
/// <param name="request">要发送的 request。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>当前 request 的响应结果。</returns>
private static async ValueTask<TResponse> SendScopedMediatorRequestAsync<TResponse>(
ServiceProvider rootServiceProvider,
Mediator.IRequest<TResponse> request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(rootServiceProvider);
ArgumentNullException.ThrowIfNull(request);
using var scope = rootServiceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<GeneratedMediator>();
return await mediator.Send(request, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// 构建 singleton 生命周期的 `Mediator` 对照宿主。
/// </summary>
/// <returns>按 singleton 形状生成 DI 注册的 `Mediator` 宿主。</returns>
private static ServiceProvider CreateSingletonMediatorServiceProvider()
{
var services = new ServiceCollection();
services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Singleton);
return services.BuildServiceProvider();
}
/// <summary>
/// 构建 scoped 生命周期的 `Mediator` 对照宿主。
/// </summary>
/// <returns>按 scoped 形状生成 DI 注册的 `Mediator` 宿主。</returns>
private static ServiceProvider CreateScopedMediatorServiceProvider()
{
var services = new ServiceCollection();
services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Scoped);
return services.BuildServiceProvider();
}
/// <summary>
/// 构建 transient 生命周期的 `Mediator` 对照宿主。
/// </summary>
/// <returns>按 transient 形状生成 DI 注册的 `Mediator` 宿主。</returns>
private static ServiceProvider CreateTransientMediatorServiceProvider()
{
var services = new ServiceCollection();
services.AddMediator(static options => options.ServiceLifetime = ServiceLifetime.Transient);
return services.BuildServiceProvider();
}
/// <summary>
/// Benchmark request。
/// Benchmark request。
/// </summary>
/// <param name="Id">请求标识。</param>
public sealed record BenchmarkRequest(Guid Id) :
GFramework.Cqrs.Abstractions.Cqrs.IRequest<BenchmarkResponse>,
Mediator.IRequest<BenchmarkResponse>,
MediatR.IRequest<BenchmarkResponse>;
/// <summary>
@ -356,11 +262,10 @@ public class RequestLifetimeBenchmarks
public sealed record BenchmarkResponse(Guid Id);
/// <summary>
/// 同时实现 GFramework.CQRS、NuGet `Mediator` 与 MediatR 契约的最小 request handler。
/// 同时实现 GFramework.CQRS 与 MediatR 契约的最小 request handler。
/// </summary>
public sealed class BenchmarkRequestHandler :
GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
Mediator.IRequestHandler<BenchmarkRequest, BenchmarkResponse>,
MediatR.IRequestHandler<BenchmarkRequest, BenchmarkResponse>
{
/// <summary>
@ -371,16 +276,6 @@ public class RequestLifetimeBenchmarks
return ValueTask.FromResult(new BenchmarkResponse(request.Id));
}
/// <summary>
/// 处理 NuGet `Mediator` request。
/// </summary>
ValueTask<BenchmarkResponse> Mediator.IRequestHandler<BenchmarkRequest, BenchmarkResponse>.Handle(
BenchmarkRequest request,
CancellationToken cancellationToken)
{
return Handle(request, cancellationToken);
}
/// <summary>
/// 处理 MediatR request。
/// </summary>

View File

@ -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` 的已装配宿主

View File

@ -14,45 +14,33 @@ CQRS 迁移与收敛。
- 恢复点编号:`CQRS-REWRITE-RP-140`
- 当前阶段:`Phase 8`
- 当前 PR 锚点:`PR #349已于 2026-05-12 合并到 origin/main`
- 当前 PR 锚点:`PR #350OPEN2026-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 `<returns>` 批量缺口”扩批:
- README 一致性盘点成立,`GFramework.Cqrs.Benchmarks/README.md` 的 startup coverage / 解释边界仍可收紧
- benchmark XML 缺口盘点存在明显误报;代表文件中的 class / benchmark 方法 `<summary>``<returns>` 已实际存在
- 因此本轮不接受新的大范围 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 #350OPEN`
- 当前写面:
- `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<T>` 与作用域覆盖整个枚举周期的组合边界。
- `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` openCodeRabbit 已 `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 扩批。
## 活跃文档

View File

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