From 11a6b6abe49534d0d6e346432b60b3656af11e92 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Mon, 11 May 2026 08:03:07 +0800
Subject: [PATCH] =?UTF-8?q?test(cqrs-benchmarks):=20=E6=89=A9=E5=B1=95?=
=?UTF-8?q?=E6=B5=81=E5=BC=8F=E5=9F=BA=E5=87=86=E8=A7=82=E6=B5=8B=E5=B9=B6?=
=?UTF-8?q?=E8=A1=A5=E9=BD=90=20scoped=20=E7=94=9F=E5=91=BD=E5=91=A8?=
=?UTF-8?q?=E6=9C=9F=E7=9F=A9=E9=98=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 StreamInvokerBenchmarks 的 FirstItem 与 DrainAll 双观测口径,并补齐相关 XML 注释。
- 引入 ScopedBenchmarkContainer 与 scoped request helper,为 RequestLifetimeBenchmarks 建立真实作用域边界下的 Scoped 生命周期矩阵。
- 更新 cqrs-rewrite 的 active tracking 与 trace,记录 RP-129 的多 worker 波次、串行化 smoke 验证与新的恢复入口。
---
.../Messaging/BenchmarkHostFactory.cs | 60 +++
.../Messaging/RequestLifetimeBenchmarks.cs | 61 ++-
.../Messaging/ScopedBenchmarkContainer.cs | 361 ++++++++++++++++++
.../Messaging/StreamInvokerBenchmarks.cs | 168 ++++++--
.../todos/cqrs-rewrite-migration-tracking.md | 28 +-
.../traces/cqrs-rewrite-migration-trace.md | 32 ++
6 files changed, 648 insertions(+), 62 deletions(-)
create mode 100644 GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
index fb93cc32..57d46d23 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/BenchmarkHostFactory.cs
@@ -3,6 +3,8 @@
using System;
using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Cqrs.Abstractions.Cqrs;
@@ -158,6 +160,64 @@ internal static class BenchmarkHostFactory
return services.BuildServiceProvider();
}
+ ///
+ /// 在真实的 request 级作用域内执行一次 GFramework.CQRS request 分发。
+ ///
+ /// 请求响应类型。
+ /// 冻结后的 benchmark 根容器,用于创建 request 作用域并提供注册元数据。
+ /// 当前 request 级 runtime 复用的日志器。
+ /// 当前 CQRS 分发上下文。
+ /// 要发送的 request。
+ /// 取消令牌。
+ /// 当前 request 的响应结果。
+ ///
+ /// 该入口只服务 request lifetime benchmark:每次调用都会显式创建并释放一个新的 DI 作用域,
+ /// 让 `Scoped` handler 在真实 request 边界内解析,而不是退化为根容器解析。
+ ///
+ internal static async ValueTask SendScopedGFrameworkRequestAsync(
+ MicrosoftDiContainer rootContainer,
+ ILogger runtimeLogger,
+ ICqrsContext context,
+ GFramework.Cqrs.Abstractions.Cqrs.IRequest request,
+ CancellationToken cancellationToken = default)
+ {
+ ArgumentNullException.ThrowIfNull(rootContainer);
+ ArgumentNullException.ThrowIfNull(runtimeLogger);
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(request);
+
+ using var scope = rootContainer.CreateScope();
+ var scopedContainer = new ScopedBenchmarkContainer(rootContainer, scope);
+ var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
+ scopedContainer,
+ runtimeLogger);
+ return await runtime.SendAsync(context, request, cancellationToken).ConfigureAwait(false);
+ }
+
+ ///
+ /// 在真实的 request 级作用域内执行一次 MediatR request 分发。
+ ///
+ /// 请求响应类型。
+ /// 当前 benchmark 的根 。
+ /// 要发送的 request。
+ /// 取消令牌。
+ /// 当前 request 的响应结果。
+ ///
+ /// 这里显式从新的 scope 解析 ,确保 `Scoped` handler 与其依赖绑定到 request 边界。
+ ///
+ internal static async Task SendScopedMediatRRequestAsync(
+ ServiceProvider rootServiceProvider,
+ MediatR.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);
+ }
+
///
/// 创建承载 NuGet `Mediator` source-generated concrete mediator 的最小对照宿主。
///
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
index 2e342dd6..8a9458b0 100644
--- a/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
+++ b/GFramework.Cqrs.Benchmarks/Messaging/RequestLifetimeBenchmarks.cs
@@ -23,24 +23,25 @@ namespace GFramework.Cqrs.Benchmarks.Messaging;
/// 对比 request steady-state dispatch 在不同 handler 生命周期下的额外开销。
///
///
-/// 当前矩阵只覆盖 `Singleton` 与 `Transient`。
-/// `Scoped` 在两个 runtime 中都依赖显式作用域边界,而当前 benchmark 宿主故意保持“单根容器最小宿主”模型,
-/// 直接把 scoped 解析压到根作用域会让对照语义失真,因此留到未来有真实 scoped host 基线时再扩展。
+/// 当前矩阵覆盖 `Singleton`、`Scoped` 与 `Transient`。
+/// 其中 `Scoped` 会在每次 request 分发时显式创建并释放真实的 DI 作用域,
+/// 避免把 scoped handler 错误地压到根容器解析而扭曲生命周期对照。
///
[Config(typeof(Config))]
public class RequestLifetimeBenchmarks
{
private MicrosoftDiContainer _container = null!;
- private ICqrsRuntime _runtime = null!;
+ private ICqrsRuntime? _runtime;
private ServiceProvider _serviceProvider = null!;
- private IMediator _mediatr = null!;
+ private IMediator? _mediatr;
private BenchmarkRequestHandler _baselineHandler = null!;
private BenchmarkRequest _request = null!;
+ private ILogger _runtimeLogger = null!;
///
/// 控制当前 benchmark 使用的 handler 生命周期。
///
- [Params(HandlerLifetime.Singleton, HandlerLifetime.Transient)]
+ [Params(HandlerLifetime.Singleton, HandlerLifetime.Scoped, HandlerLifetime.Transient)]
public HandlerLifetime Lifetime { get; set; }
///
@@ -53,6 +54,11 @@ public class RequestLifetimeBenchmarks
///
Singleton,
+ ///
+ /// 每次 request 在显式作用域内解析并复用 handler 实例。
+ ///
+ Scoped,
+
///
/// 每次分发都重新解析新的 handler 实例。
///
@@ -90,6 +96,8 @@ public class RequestLifetimeBenchmarks
_baselineHandler = new BenchmarkRequestHandler();
_request = new BenchmarkRequest(Guid.NewGuid());
+ _runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestLifetimeBenchmarks) + "." + Lifetime);
+
_container = BenchmarkHostFactory.CreateFrozenGFrameworkContainer(container =>
{
BenchmarkHostFactory.RegisterGeneratedBenchmarkRegistry(container);
@@ -97,16 +105,22 @@ public class RequestLifetimeBenchmarks
});
// 容器内已提前保留默认 runtime 以支撑 generated registry 接线;
// 这里额外创建带生命周期后缀的 runtime,只是为了区分不同 benchmark 矩阵的 dispatcher 日志。
- _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
- _container,
- LoggerFactoryResolver.Provider.CreateLogger(nameof(RequestLifetimeBenchmarks) + "." + Lifetime));
+ if (Lifetime != HandlerLifetime.Scoped)
+ {
+ _runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(
+ _container,
+ _runtimeLogger);
+ }
_serviceProvider = BenchmarkHostFactory.CreateMediatRServiceProvider(
configure: null,
typeof(RequestLifetimeBenchmarks),
static candidateType => candidateType == typeof(BenchmarkRequestHandler),
ResolveMediatRLifetime(Lifetime));
- _mediatr = _serviceProvider.GetRequiredService();
+ if (Lifetime != HandlerLifetime.Scoped)
+ {
+ _mediatr = _serviceProvider.GetRequiredService();
+ }
}
///
@@ -140,7 +154,17 @@ public class RequestLifetimeBenchmarks
[Benchmark]
public ValueTask SendRequest_GFrameworkCqrs()
{
- return _runtime.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
+ if (Lifetime == HandlerLifetime.Scoped)
+ {
+ return BenchmarkHostFactory.SendScopedGFrameworkRequestAsync(
+ _container,
+ _runtimeLogger,
+ BenchmarkContext.Instance,
+ _request,
+ CancellationToken.None);
+ }
+
+ return _runtime!.SendAsync(BenchmarkContext.Instance, _request, CancellationToken.None);
}
///
@@ -149,7 +173,15 @@ public class RequestLifetimeBenchmarks
[Benchmark]
public Task SendRequest_MediatR()
{
- return _mediatr.Send(_request, CancellationToken.None);
+ if (Lifetime == HandlerLifetime.Scoped)
+ {
+ return BenchmarkHostFactory.SendScopedMediatRRequestAsync(
+ _serviceProvider,
+ _request,
+ CancellationToken.None);
+ }
+
+ return _mediatr!.Send(_request, CancellationToken.None);
}
///
@@ -171,6 +203,10 @@ public class RequestLifetimeBenchmarks
container.RegisterSingleton, BenchmarkRequestHandler>();
return;
+ case HandlerLifetime.Scoped:
+ container.RegisterScoped, BenchmarkRequestHandler>();
+ return;
+
case HandlerLifetime.Transient:
container.RegisterTransient, BenchmarkRequestHandler>();
return;
@@ -189,6 +225,7 @@ public class RequestLifetimeBenchmarks
return lifetime switch
{
HandlerLifetime.Singleton => ServiceLifetime.Singleton,
+ HandlerLifetime.Scoped => ServiceLifetime.Scoped,
HandlerLifetime.Transient => ServiceLifetime.Transient,
_ => throw new ArgumentOutOfRangeException(nameof(lifetime), lifetime, "Unsupported benchmark handler lifetime.")
};
diff --git a/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
new file mode 100644
index 00000000..29c51d71
--- /dev/null
+++ b/GFramework.Cqrs.Benchmarks/Messaging/ScopedBenchmarkContainer.cs
@@ -0,0 +1,361 @@
+// Copyright (c) 2025-2026 GeWuYou
+// SPDX-License-Identifier: Apache-2.0
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using GFramework.Core.Abstractions.Bases;
+using GFramework.Core.Abstractions.Ioc;
+using GFramework.Core.Abstractions.Rule;
+using GFramework.Core.Abstractions.Systems;
+using GFramework.Core.Ioc;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace GFramework.Cqrs.Benchmarks.Messaging;
+
+///
+/// 把冻结后的 benchmark 根容器与单个 组合成 request 级解析视图。
+///
+///
+/// `CqrsDispatcher` 会直接依赖 做 handler / pipeline 解析,
+/// 因此 request lifetime benchmark 需要一个既保留根容器注册元数据,又把实例解析切换到显式作用域 provider
+/// 的最小适配层。该类型只覆盖 benchmark 当前 request 路径会使用到的解析相关入口;
+/// 任何注册、清空或冻结修改操作都应继续发生在根容器构建阶段,因此这里统一拒绝可变更 API。
+///
+internal sealed class ScopedBenchmarkContainer : IIocContainer
+{
+ private readonly MicrosoftDiContainer _rootContainer;
+ private readonly IServiceProvider _scopedProvider;
+
+ ///
+ /// 初始化一个绑定到单个 request 作用域的 benchmark 容器适配器。
+ ///
+ /// 已冻结的 benchmark 根容器。
+ /// 当前 request 独占的作用域实例。
+ internal ScopedBenchmarkContainer(MicrosoftDiContainer rootContainer, IServiceScope scope)
+ {
+ _rootContainer = rootContainer ?? throw new ArgumentNullException(nameof(rootContainer));
+ ArgumentNullException.ThrowIfNull(scope);
+ _scopedProvider = scope.ServiceProvider;
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSingleton(T instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSingleton()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterTransient()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterScoped()
+ where TImpl : class, TService
+ where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterPlurality(object instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterPlurality() where T : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterSystem(ISystem system)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void Register(T instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void Register(Type type, object instance)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterFactory(Func factory) where TService : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsPipelineBehavior() where TBehavior : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsStreamPipelineBehavior() where TBehavior : class
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsHandlersFromAssembly(System.Reflection.Assembly assembly)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持在 request 作用域内追加注册。
+ ///
+ public void RegisterCqrsHandlersFromAssemblies(IEnumerable assemblies)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 当前适配器不支持执行额外的服务配置钩子。
+ ///
+ public void ExecuteServicesHook(Action? configurator = null)
+ {
+ throw CreateMutationNotSupportedException();
+ }
+
+ ///
+ /// 从当前 request 作用域解析单个服务实例。
+ ///
+ public T? Get() where T : class
+ {
+ return _scopedProvider.GetService();
+ }
+
+ ///
+ /// 从当前 request 作用域解析单个服务实例。
+ ///
+ public object? Get(Type type)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+ return _scopedProvider.GetService(type);
+ }
+
+ ///
+ /// 从当前 request 作用域解析必需的单个服务实例。
+ ///
+ public T GetRequired() where T : class
+ {
+ return _scopedProvider.GetRequiredService();
+ }
+
+ ///
+ /// 从当前 request 作用域解析必需的单个服务实例。
+ ///
+ public object GetRequired(Type type)
+ {
+ ArgumentNullException.ThrowIfNull(type);
+ return _scopedProvider.GetRequiredService(type);
+ }
+
+ ///
+ /// 从当前 request 作用域解析全部服务实例。
+ ///
+ public IReadOnlyList GetAll() where T : class
+ {
+ return _scopedProvider.GetServices().ToList();
+ }
+
+ ///
+ /// 从当前 request 作用域解析全部服务实例。
+ ///
+ public IReadOnlyList