// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Ioc;
using GFramework.Cqrs.Abstractions.Cqrs;
using GFramework.Cqrs.Internal;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using LegacyICqrsRuntime = GFramework.Core.Abstractions.Cqrs.ICqrsRuntime;
namespace GFramework.Cqrs.Benchmarks.Messaging;
///
/// 为 benchmark 场景构建最小且可重复的 GFramework / MediatR 对照宿主。
///
///
/// 基准工程里的对照目标是“相同消息合同下的调度差异”,而不是程序集扫描量或容器生命周期差异。
/// 因此这里统一封装两类宿主的最小注册形状,确保:
/// 1. GFramework 容器在首次发送前已经冻结,可真实解析按类型注册的 handler;
/// 2. MediatR 只扫描当前 benchmark 明确拥有的 handler / behavior 类型,避免整个程序集的额外注册污染结果。
///
internal static class BenchmarkHostFactory
{
///
/// 创建一个已经冻结的 GFramework benchmark 容器。
///
/// 向容器写入 benchmark 所需 handler / pipeline 的注册动作。
/// 已冻结、可立即用于 runtime 分发的容器。
internal static MicrosoftDiContainer CreateFrozenGFrameworkContainer(Action configure)
{
ArgumentNullException.ThrowIfNull(configure);
var container = new MicrosoftDiContainer();
RegisterCqrsInfrastructure(container);
configure(container);
container.Freeze();
return container;
}
///
/// 为 benchmark 宿主补齐默认 CQRS runtime seam,确保它既能手工注册 handler,也能走真实的程序集注册入口。
///
/// 当前 benchmark 拥有的 GFramework 容器。
///
/// `RegisterCqrsHandlersFromAssembly(...)` 依赖预先可见的 runtime / registrar / registration service 实例绑定。
/// benchmark 宿主直接使用裸 ,因此需要在配置阶段先补齐这组基础设施,
/// 避免各个 benchmark 用例各自复制同一段前置接线逻辑。
///
private static void RegisterCqrsInfrastructure(MicrosoftDiContainer container)
{
ArgumentNullException.ThrowIfNull(container);
if (container.Get() is null)
{
var runtimeLogger = LoggerFactoryResolver.Provider.CreateLogger("CqrsDispatcher");
var notificationPublisher = container.Get();
var runtime = GFramework.Cqrs.CqrsRuntimeFactory.CreateRuntime(container, runtimeLogger, notificationPublisher);
container.Register(runtime);
RegisterLegacyRuntimeAlias(container, runtime);
}
else if (container.Get() is null)
{
RegisterLegacyRuntimeAlias(container, container.GetRequired());
}
if (container.Get() is null)
{
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
var registrar = GFramework.Cqrs.CqrsRuntimeFactory.CreateHandlerRegistrar(container, registrarLogger);
container.Register(registrar);
}
if (container.Get() is null)
{
var registrationLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsRegistrationService");
var registrar = container.GetRequired();
var registrationService = GFramework.Cqrs.CqrsRuntimeFactory.CreateRegistrationService(registrar, registrationLogger);
container.Register(registrationService);
}
}
///
/// 只激活当前 benchmark 场景明确拥有的 generated registry,避免同一程序集里的其他 benchmark registry
/// 扩大冻结后服务索引与 dispatcher descriptor 基线。
///
/// 当前 benchmark 需要接入的 generated registry 类型。
/// 承载 generated registry 注册结果的 GFramework benchmark 容器。
internal static void RegisterGeneratedBenchmarkRegistry(MicrosoftDiContainer container)
where TRegistry : class, GFramework.Cqrs.ICqrsHandlerRegistry
{
ArgumentNullException.ThrowIfNull(container);
var registrarLogger = LoggerFactoryResolver.Provider.CreateLogger("DefaultCqrsHandlerRegistrar");
CqrsHandlerRegistrar.RegisterGeneratedRegistry(container, typeof(TRegistry), registrarLogger);
}
///
/// 为旧命名空间下的 CQRS runtime 契约注册兼容别名。
///
/// 承载 runtime 别名的 benchmark 容器。
/// 当前正式 CQRS runtime 实例。
///
/// 未同时实现 legacy CQRS runtime 契约。
///
private static void RegisterLegacyRuntimeAlias(MicrosoftDiContainer container, ICqrsRuntime runtime)
{
ArgumentNullException.ThrowIfNull(container);
ArgumentNullException.ThrowIfNull(runtime);
if (runtime is not LegacyICqrsRuntime legacyRuntime)
{
throw new InvalidOperationException(
$"The registered {typeof(ICqrsRuntime).FullName} must also implement {typeof(LegacyICqrsRuntime).FullName}. Actual runtime type: {runtime.GetType().FullName}.");
}
container.Register(legacyRuntime);
}
///
/// 创建只承载当前 benchmark handler 集合的最小 MediatR 宿主。
///
/// 补充当前场景的显式服务注册,例如手工单例 handler 或 pipeline 行为。
/// 用于限定扫描程序集的标记类型。
///
/// 仅允许当前 benchmark 场景需要的 handler / behavior 类型通过扫描;
/// 这样可保留 `AddMediatR` 的正常装配路径,同时避免整个基准程序集里的其他 handler 被一并注册。
///
/// 当前 benchmark 希望 MediatR 使用的默认注册生命周期。
/// 只承载当前 benchmark 场景所需服务的 DI 宿主。
internal static ServiceProvider CreateMediatRServiceProvider(
Action? configure,
Type handlerAssemblyMarkerType,
Func handlerTypeFilter,
ServiceLifetime lifetime = ServiceLifetime.Transient)
{
ArgumentNullException.ThrowIfNull(handlerAssemblyMarkerType);
ArgumentNullException.ThrowIfNull(handlerTypeFilter);
var services = new ServiceCollection();
services.AddLogging(static builder =>
Microsoft.Extensions.Logging.FilterLoggingBuilderExtensions.AddFilter(
builder,
"LuckyPennySoftware.MediatR.License",
Microsoft.Extensions.Logging.LogLevel.None));
configure?.Invoke(services);
services.AddMediatR(options =>
{
options.Lifetime = lifetime;
options.TypeEvaluator = handlerTypeFilter;
options.RegisterServicesFromAssembly(handlerAssemblyMarkerType.Assembly);
});
return services.BuildServiceProvider();
}
///
/// 在真实的 request 级作用域内执行一次 GFramework.CQRS request 分发。
///
/// 请求响应类型。
/// 复用的 scoped benchmark runtime。
/// 负责为每次 request 激活独立作用域的只读容器适配层。
/// 当前 CQRS 分发上下文。
/// 要发送的 request。
/// 取消令牌。
/// 当前 request 的响应结果。
///
/// 该入口只服务 request lifetime benchmark:它会复用同一个 dispatcher/runtime 实例,
/// 但在每次调用前后显式创建并释放新的 DI 作用域,
/// 让 `Scoped` handler 在真实 request 边界内解析,而不是退化为根容器解析或额外计入 runtime 构造成本。
///
internal static async ValueTask SendScopedGFrameworkRequestAsync(
ICqrsRuntime runtime,
ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(scopedContainer);
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(request);
using var scopeLease = scopedContainer.EnterScope();
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);
}
///
/// 在真实的 request 级作用域内创建一次 GFramework.CQRS stream,并让该作用域覆盖整个异步枚举周期。
///
/// stream 响应元素类型。
/// 复用的 scoped benchmark runtime。
/// 负责为每次 stream 激活独立作用域的只读容器适配层。
/// 当前 CQRS 分发上下文。
/// 要创建 stream 的 request。
/// 取消令牌。
/// 绑定到单次显式作用域的异步响应序列。
///
/// stream 与 request 的区别在于:handler 解析发生在建流时,但 scoped 依赖必须一直存活到枚举完成。
/// 因此这里返回一个包装后的 async iterator,把 scope 的释放时机推迟到调用方结束枚举之后,
/// 避免 `Scoped` handler 退化成“建流后立刻释放 scope,再在根容器语义下继续枚举”的错误模型。
///
internal static IAsyncEnumerable CreateScopedGFrameworkStream(
ICqrsRuntime runtime,
ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(scopedContainer);
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(request);
return EnumerateScopedGFrameworkStreamAsync(runtime, scopedContainer, context, request, cancellationToken);
}
///
/// 在真实的 request 级作用域内创建一次 MediatR stream,并让该作用域覆盖整个异步枚举周期。
///
/// stream 响应元素类型。
/// 当前 benchmark 的根 。
/// 要创建 stream 的 request。
/// 取消令牌。
/// 绑定到单次显式作用域的异步响应序列。
///
/// 这里与 scoped request helper 保持同一组边界约束,但把 scope 生命周期延长到 stream 完整枚举结束,
/// 确保 `Scoped` handler 与依赖不会在首个元素产出前后被提前释放。
///
internal static IAsyncEnumerable CreateScopedMediatRStream(
ServiceProvider rootServiceProvider,
MediatR.IStreamRequest request,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(rootServiceProvider);
ArgumentNullException.ThrowIfNull(request);
return EnumerateScopedMediatRStreamAsync(rootServiceProvider, request, cancellationToken);
}
///
/// 创建承载 NuGet `Mediator` source-generated concrete mediator 的最小对照宿主。
///
/// 补充当前场景的显式服务注册。
/// 可直接解析 generated `Mediator.Mediator` 的 DI 宿主。
///
/// 当前 benchmark 只把 `Mediator` 作为单例 steady-state 对照组接入,
/// 因为它的 lifetime 由 source generator 在编译期塑形;若后续需要 `Transient` / `Scoped` 矩阵,
/// 应按 `Mediator` 官方 benchmark 的做法拆成独立 build config,而不是在同一编译产物里混用多个 lifetime。
///
internal static ServiceProvider CreateMediatorServiceProvider(Action? configure)
{
var services = new ServiceCollection();
configure?.Invoke(services);
services.AddMediator();
return services.BuildServiceProvider();
}
///
/// 判断某个类型是否正好实现了指定的闭合或开放 MediatR 合同。
///
/// 待判断类型。
/// 目标开放泛型合同,例如 。
/// 命中任一实现接口时返回 ;否则返回 。
internal static bool ImplementsOpenGenericContract(Type candidateType, Type openGenericContract)
{
ArgumentNullException.ThrowIfNull(candidateType);
ArgumentNullException.ThrowIfNull(openGenericContract);
return candidateType.GetInterfaces().Any(interfaceType =>
interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == openGenericContract);
}
///
/// 在单个显式作用域内创建并枚举 GFramework.CQRS stream。
///
private static async IAsyncEnumerable EnumerateScopedGFrameworkStreamAsync(
ICqrsRuntime runtime,
ScopedBenchmarkContainer scopedContainer,
ICqrsContext context,
GFramework.Cqrs.Abstractions.Cqrs.IStreamRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
using var scopeLease = scopedContainer.EnterScope();
var stream = runtime.CreateStream(context, request, cancellationToken);
await foreach (var response in stream.ConfigureAwait(false))
{
cancellationToken.ThrowIfCancellationRequested();
yield return response;
}
}
///
/// 在单个显式作用域内创建并枚举 MediatR stream。
///
private static async IAsyncEnumerable EnumerateScopedMediatRStreamAsync(
ServiceProvider rootServiceProvider,
MediatR.IStreamRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
using var scope = rootServiceProvider.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService();
var stream = mediator.CreateStream(request, cancellationToken);
await foreach (var response in stream.ConfigureAwait(false))
{
cancellationToken.ThrowIfCancellationRequested();
yield return response;
}
}
}