mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-10 02:59:02 +08:00
fix(cqrs): 缓存零管道请求的行为判定
- 新增 dispatcher 实例级 request behavior presence cache,减少零管道请求 steady-state 的容器查询开销 - 补充 dispatcher cache 回归并更新 cqrs-rewrite active tracking 与 trace,记录 request benchmark 和 lifetime benchmark 结果
This commit is contained in:
parent
4ccc36aac9
commit
56dc4fd343
@ -160,6 +160,53 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 request 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
|
||||
/// 让零行为请求在首次分发后不再重复查询容器,同时不同 dispatcher 不共享该实例级状态。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Dispatcher_Should_Cache_Zero_Pipeline_Request_Presence_Per_Dispatcher_Instance()
|
||||
{
|
||||
var firstContext = new ArchitectureContext(_container!);
|
||||
var secondContext = new ArchitectureContext(_container!);
|
||||
var firstDispatcher = GetDispatcherFromContext(firstContext);
|
||||
var secondDispatcher = GetDispatcherFromContext(secondContext);
|
||||
using var isolatedContainer = CreateFrozenContainer();
|
||||
var isolatedContext = new ArchitectureContext(isolatedContainer);
|
||||
var isolatedDispatcher = GetDispatcherFromContext(isolatedContext);
|
||||
|
||||
AssertRequestBehaviorPresenceIsUnset(firstDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||||
AssertRequestBehaviorPresenceIsUnset(secondDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||||
AssertRequestBehaviorPresenceIsUnset(isolatedDispatcher, typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||||
AssertRequestBehaviorPresenceIsUnset(
|
||||
firstDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherPipelineCacheRequest, int>));
|
||||
|
||||
await firstContext.SendRequestAsync(new DispatcherCacheRequest());
|
||||
await firstContext.SendRequestAsync(new DispatcherPipelineCacheRequest());
|
||||
|
||||
var zeroPipelinePresence = GetRequestBehaviorPresenceCacheValue(
|
||||
firstDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||||
var onePipelinePresence = GetRequestBehaviorPresenceCacheValue(
|
||||
firstDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherPipelineCacheRequest, int>));
|
||||
|
||||
AssertSharedDispatcherCacheState(
|
||||
firstDispatcher,
|
||||
secondDispatcher,
|
||||
isolatedDispatcher,
|
||||
zeroPipelinePresence,
|
||||
onePipelinePresence);
|
||||
|
||||
await isolatedContext.SendRequestAsync(new DispatcherCacheRequest());
|
||||
|
||||
AssertRequestBehaviorPresenceEquals(
|
||||
isolatedDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>),
|
||||
false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 request pipeline executor 会按行为数量在 binding 内首次创建并在后续分发中复用。
|
||||
/// </summary>
|
||||
@ -565,6 +612,57 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
$"Dispatcher cache field {fieldName} returned null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从架构上下文中解析当前延迟创建的 dispatcher 实例,便于验证其实例级热路径缓存。
|
||||
/// </summary>
|
||||
private static object GetDispatcherFromContext(ArchitectureContext context)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var lazyRuntimeField = typeof(ArchitectureContext).GetField(
|
||||
"_cqrsRuntime",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(lazyRuntimeField, Is.Not.Null, "Missing ArchitectureContext._cqrsRuntime field.");
|
||||
|
||||
var lazyRuntime = lazyRuntimeField!.GetValue(context)
|
||||
?? throw new InvalidOperationException(
|
||||
"ArchitectureContext._cqrsRuntime returned null.");
|
||||
var lazyValueProperty = lazyRuntime.GetType().GetProperty(
|
||||
"Value",
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
Assert.That(lazyValueProperty, Is.Not.Null, "Missing Lazy<ICqrsRuntime>.Value accessor.");
|
||||
|
||||
return lazyValueProperty!.GetValue(lazyRuntime)
|
||||
?? throw new InvalidOperationException("Resolved CQRS runtime instance was null.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建与当前 fixture 注册形状一致、但拥有独立 runtime 实例的冻结容器,
|
||||
/// 用于验证 dispatcher 的实例级缓存不会跨容器共享。
|
||||
/// </summary>
|
||||
private static MicrosoftDiContainer CreateFrozenContainer()
|
||||
{
|
||||
var container = new MicrosoftDiContainer();
|
||||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineCacheBehavior>();
|
||||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineContextRefreshBehavior>();
|
||||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineOrderOuterBehavior>();
|
||||
container.RegisterCqrsPipelineBehavior<DispatcherPipelineOrderInnerBehavior>();
|
||||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineCacheBehavior>();
|
||||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineContextRefreshBehavior>();
|
||||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineOrderOuterBehavior>();
|
||||
container.RegisterCqrsStreamPipelineBehavior<DispatcherStreamPipelineOrderInnerBehavior>();
|
||||
|
||||
CqrsTestRuntime.RegisterHandlers(
|
||||
container,
|
||||
typeof(CqrsDispatcherCacheTests).Assembly,
|
||||
typeof(ArchitectureContext).Assembly);
|
||||
|
||||
container.Freeze();
|
||||
return container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空本测试依赖的 dispatcher 静态缓存,避免跨用例共享进程级状态导致断言漂移。
|
||||
/// </summary>
|
||||
@ -591,6 +689,74 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", primaryType, secondaryType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定 dispatcher 实例中当前保存的 request behavior presence 缓存项。
|
||||
/// </summary>
|
||||
private static object? GetRequestBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
|
||||
{
|
||||
var field = dispatcher.GetType().GetField(
|
||||
"_requestBehaviorPresenceCache",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(field, Is.Not.Null, "Missing dispatcher request behavior presence cache field.");
|
||||
|
||||
var cache = field!.GetValue(dispatcher)
|
||||
?? throw new InvalidOperationException(
|
||||
"Dispatcher request behavior presence cache returned null.");
|
||||
var tryGetValueMethod = cache.GetType().GetMethod(
|
||||
"TryGetValue",
|
||||
BindingFlags.Instance | BindingFlags.Public);
|
||||
|
||||
Assert.That(tryGetValueMethod, Is.Not.Null, "Missing ConcurrentDictionary.TryGetValue accessor.");
|
||||
|
||||
object?[] arguments = [behaviorType, null];
|
||||
var found = (bool)(tryGetValueMethod!.Invoke(cache, arguments)
|
||||
?? throw new InvalidOperationException(
|
||||
"ConcurrentDictionary.TryGetValue returned null."));
|
||||
return found ? arguments[1] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项尚未建立。
|
||||
/// </summary>
|
||||
private static void AssertRequestBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
|
||||
{
|
||||
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项等于预期值。
|
||||
/// </summary>
|
||||
private static void AssertRequestBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
|
||||
{
|
||||
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言同一容器解析出的 dispatcher 会共享实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
|
||||
/// </summary>
|
||||
private static void AssertSharedDispatcherCacheState(
|
||||
object firstDispatcher,
|
||||
object secondDispatcher,
|
||||
object isolatedDispatcher,
|
||||
object? zeroPipelinePresence,
|
||||
object? onePipelinePresence)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
|
||||
Assert.That(zeroPipelinePresence, Is.EqualTo(false));
|
||||
Assert.That(onePipelinePresence, Is.EqualTo(true));
|
||||
AssertRequestBehaviorPresenceEquals(
|
||||
secondDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>),
|
||||
false);
|
||||
AssertRequestBehaviorPresenceIsUnset(
|
||||
isolatedDispatcher,
|
||||
typeof(IPipelineBehavior<DispatcherCacheRequest, int>));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 request dispatch binding 中指定行为数量的 pipeline executor 缓存项。
|
||||
/// </summary>
|
||||
|
||||
@ -21,6 +21,11 @@ internal sealed class CqrsDispatcher(
|
||||
ILogger logger,
|
||||
INotificationPublisher? notificationPublisher) : ICqrsRuntime
|
||||
{
|
||||
// 实例级热路径缓存:默认 runtime 在容器冻结前创建,但请求/stream 行为注册在架构生命周期内保持稳定。
|
||||
// 因此这里按 behavior service type 记住“当前 dispatcher 对应容器里是否存在该行为”,避免 0-pipeline steady-state
|
||||
// 每次 SendAsync 都重复询问容器。缓存值只反映当前 dispatcher 持有容器的注册可见性,不跨 runtime 共享。
|
||||
private readonly ConcurrentDictionary<Type, bool> _requestBehaviorPresenceCache = new();
|
||||
|
||||
// 卸载安全的进程级缓存:当 generated registry 提供 request invoker 元数据时,
|
||||
// registrar 会按请求/响应类型对把它们写入这里;若类型被卸载,条目会自然失效。
|
||||
private static readonly WeakTypePairCache<GeneratedRequestInvokerMetadata>
|
||||
@ -123,7 +128,7 @@ internal sealed class CqrsDispatcher(
|
||||
$"No CQRS request handler registered for {requestType.FullName}.");
|
||||
|
||||
PrepareHandler(handler, context);
|
||||
if (!container.HasRegistration(dispatchBinding.BehaviorType))
|
||||
if (!HasRequestBehaviorRegistration(dispatchBinding.BehaviorType))
|
||||
{
|
||||
return dispatchBinding.RequestInvoker(handler, request, cancellationToken);
|
||||
}
|
||||
@ -145,6 +150,21 @@ internal sealed class CqrsDispatcher(
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前 dispatcher 容器里是否存在指定 request pipeline 行为注册,并在首次命中后缓存结果。
|
||||
/// </summary>
|
||||
/// <param name="behaviorType">目标 pipeline 行为服务类型。</param>
|
||||
/// <returns>存在注册时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
|
||||
private bool HasRequestBehaviorRegistration(Type behaviorType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(behaviorType);
|
||||
|
||||
return _requestBehaviorPresenceCache.GetOrAdd(
|
||||
behaviorType,
|
||||
static (cachedBehaviorType, currentContainer) => currentContainer.HasRegistration(cachedBehaviorType),
|
||||
container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建流式请求并返回异步响应序列。
|
||||
/// </summary>
|
||||
|
||||
@ -7,15 +7,16 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-121`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-122`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #342`
|
||||
- 当前结论:
|
||||
- 当前 `RP-121` 延续 `$gframework-batch-boot 50`,但不再继续扩 notification runtime 语义或新内置策略,而是先把 `RP-120` 刚修复的默认接线补成更贴近生产的架构启动回归
|
||||
- `GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs` 现新增 `InitializeAsync_Should_Reuse_Custom_NotificationPublisher_From_Configurator()`:测试通过标准 `Architecture.Configurator` 注册依赖容器 probe 的自定义 `INotificationPublisher`,并在 `OnInitialize()` 显式接入额外程序集 notification handler,验证默认 `Architecture.InitializeAsync()` 路径最终 publish 时确实命中自定义策略
|
||||
- 这一批只扩 `GFramework.Core.Tests` 的集成回归,不再改动 `GFramework.Cqrs` / `GFramework.Core` 运行时代码;目的在于把“组合根声明的 publisher 不会再被默认顺序策略短路”从测试宿主路径补到真实架构启动路径
|
||||
- 当前已提交分支相对 `origin/main`(`d389eb36`, `2026-05-08 20:08:33 +0800`)的累计 branch diff 为 `9 files / 241 changed lines`;本批待提交工作树仅新增 `1 file / 122 changed lines`,提交后预计约为 `10 files / 363 changed lines`,仍明显低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- 本轮虽然仍有 branch diff 余量,但 notification 线已连续完成“组合根入口 -> 默认接线修复 -> 标准架构启动回归”三段闭环,继续下一批前更合理的是先提交并把后续选择重新收敛到“是否需要第三种内置策略”或“是否切回 request steady-state 热点”
|
||||
- 当前 `RP-122` 继续沿用 `$gframework-batch-boot 50`,并在 `RP-121` 收口 notification 线阶段性闭环后切回 request steady-state 热点;本轮不再继续压 `HasRegistration(Type)` 内部实现,而是把“是否存在 request pipeline behavior”从每次 `SendAsync(...)` 都查询容器,收口为 `CqrsDispatcher` 实例级的首次判定缓存
|
||||
- `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 现新增 `_requestBehaviorPresenceCache`,按 `IPipelineBehavior<,>` 的闭合服务类型记住当前 dispatcher 持有容器里该行为是否存在注册;零管道 request 在首次命中后会直接走缓存分支,不再重复询问容器
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs` 现新增 `Dispatcher_Should_Cache_Zero_Pipeline_Request_Presence_Per_Dispatcher_Instance()`:该回归同时锁住两件事,一是同一容器解析出的多个 `ArchitectureContext` 共享同一个 runtime/dispatcher,因此会复用同一实例级缓存;二是另一套独立容器创建的 dispatcher 不会提前共享该缓存
|
||||
- 本轮 short-job benchmark 表明这刀继续有效:默认 request steady-state 当前约为 baseline `5.876 ns / 32 B`、`Mediator` `5.275 ns / 32 B`、`GFramework.Cqrs` `51.717 ns / 32 B`、`MediatR` `56.108 ns / 232 B`;request lifetime 下 `Singleton` 约 `52.490 ns / 32 B` vs `MediatR` `56.890 ns / 232 B`,`Transient` 约 `57.746 ns / 56 B` vs `MediatR` `55.545 ns / 232 B`
|
||||
- 当前已提交分支相对 `origin/main`(`d389eb36`, `2026-05-08 20:08:33 +0800`)的累计 branch diff 为 `10 files / 377 changed lines`;本批待提交工作树只新增 `2 files / 187 changed lines`,即使提交后也仍明显低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- request 线经过这批后已经从“direct-return ValueTask”“generated provider 宿主吸收”“零管道 presence cache”三层继续下探,但 `Transient` 仍未稳定快于 `MediatR`;因此下一轮若继续压 request 热点,应继续选择真正减少 steady-state 常量路径的切片,而不是回头重试已被否决的 `IContextAware` 类型判定缓存
|
||||
- 当前 `RP-119` 继续沿用 `$gframework-batch-boot 50`,并在分支已与 `origin/main` 对齐(`d389eb36`, `2026-05-08 20:08:33 +0800`)后,重新选择 notification publisher 线上一个更小的采用面切片:补齐 `UseNotificationPublisher<TPublisher>()` 的组合根采用说明与回归,而不是提前切回 request dispatch 热路径
|
||||
- 本轮不修改 `GFramework.Cqrs` runtime 语义,只收口“泛型组合根入口是否真的可用、以及读者是否知道该在什么情况下选它”这两个采用缺口
|
||||
- `NotificationPublisherRegistrationExtensionsTests` 现额外覆盖两条行为:泛型重载会把指定 publisher 类型注册为容器内唯一的单例策略;当容器里已存在 `INotificationPublisher` 注册时,泛型重载也会像实例重载一样在组合根阶段拒绝重复声明
|
||||
@ -84,14 +85,14 @@ CQRS 迁移与收敛。
|
||||
- 当前 `RP-106` 已把同一套 generated-provider 宿主收口扩展到 `RequestPipelineBenchmarks`:新增 handwritten `GeneratedRequestPipelineBenchmarkRegistry`,并让 `RequestPipelineBenchmarks` 改走 `RegisterCqrsHandlersFromAssembly(...)` + benchmark CQRS 基础设施预接线;本轮 benchmark 表明 `0 pipeline` steady-state 进一步收敛到约 `64.755 ns / 32 B`,`1 pipeline` 约 `353.141 ns / 536 B`,`4 pipeline` 在短跑噪音下维持约 `555.083 ns / 896 B`
|
||||
- 当前 `RP-107` 已把默认 stream steady-state 宿主也切到 generated-provider 路径:新增 handwritten `GeneratedDefaultStreamingBenchmarkRegistry`,让 `StreamingBenchmarks` 改走 `RegisterCqrsHandlersFromAssembly(...)` 并在 setup/cleanup 清理 dispatcher cache;同时将 `gframework-boot` / `gframework-batch-boot` 的默认停止规则改为“AI 上下文预算优先,建议在预计接近约 80% 安全上下文占用前收口”,不再把 changed files 误当作唯一阈值
|
||||
- 当前 `RP-108` 已补齐 stream handler `Singleton / Transient` 生命周期矩阵 benchmark:新增 `StreamLifetimeBenchmarks` 与 `GeneratedStreamLifetimeBenchmarkRegistry`,让 stream 生命周期对照沿用 generated-provider 宿主接线而不是退回纯反射路径;本轮 benchmark 表明 `Singleton` 下 baseline / `GFramework.Cqrs` / `MediatR` 约 `80.144 ns / 137.515 ns / 229.242 ns`,`Transient` 下约 `77.198 ns / 144.998 ns / 228.185 ns`
|
||||
- `ai-plan` active 入口现以 `RP-121` 为最新恢复锚点;`PR #340`、`PR #339`、`PR #334`、`PR #331`、`PR #326`、`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准
|
||||
- `ai-plan` active 入口现以 `RP-122` 为最新恢复锚点;`PR #340`、`PR #339`、`PR #334`、`PR #331`、`PR #326`、`PR #323`、`PR #307` 与其他更早阶段细节均以下方归档或说明为准
|
||||
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前分支为 `feat/cqrs-optimization`
|
||||
- 本轮 `$gframework-batch-boot 50` 以 `origin/main` (`d389eb36`, 2026-05-08 20:08:33 +0800) 为基线;本地 `main` 仍落后,不作为 branch diff 基线
|
||||
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `9 files / 241 changed lines`
|
||||
- 本批待提交工作树集中在 `GFramework.Core.Tests/Architectures/ArchitectureModulesBehaviorTests.cs`
|
||||
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `10 files / 377 changed lines`
|
||||
- 本批待提交工作树集中在 `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 与 `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`
|
||||
- 当前批次后的默认停止依据已改为 AI 上下文预算:若下一轮预计会让活动对话、已加载 recovery 文档、验证输出与当前 diff 接近约 `80%` 安全上下文占用,应在当前自然批次边界停止,即使 branch diff 仍有余量
|
||||
- `GFramework.Cqrs.Benchmarks` 作为 benchmark 基础设施项目,必须持续排除在 NuGet / GitHub Packages 发布集合之外
|
||||
- `GFramework.Cqrs.Benchmarks` 现已覆盖 request steady-state、pipeline 数量矩阵、startup、request/stream generated invoker,以及 request handler `Singleton / Transient` 生命周期矩阵
|
||||
@ -147,6 +148,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前风险
|
||||
|
||||
- 当前 `_requestBehaviorPresenceCache` 依赖“同一 dispatcher 生命周期内,request pipeline 行为注册在容器冻结后保持稳定”这一约束;若未来引入运行时动态增删 request behavior 的模型,需要重新评估这类实例级 presence cache 的失效策略
|
||||
- 标准架构启动路径现在已经有“自定义 notification publisher 不被默认顺序策略短路”的集成回归;但若后续再引入第三种仓库内置策略或新的启动快捷入口,仍需要同步补这条生产路径验证,不能只看 `CqrsTestRuntime` 测试宿主
|
||||
- 顶层 `GFramework.sln` / `GFramework.csproj` 在 WSL 下仍可能受 Windows NuGet fallback 配置影响,完整 solution 级验证成本高于模块级验证
|
||||
- 若后续新增 benchmark / example / tooling 项目但未同步校验发布面,solution 级 `dotnet pack` 仍可能在 tag 发布前才暴露异常包
|
||||
@ -166,6 +168,20 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 最近权威验证
|
||||
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsDispatcherCacheTests"`
|
||||
- 结果:通过,`11/11` passed
|
||||
- `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 -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:默认 request steady-state 当前约为 baseline `5.876 ns / 32 B`、`Mediator` `5.275 ns / 32 B`、`GFramework.Cqrs` `51.717 ns / 32 B`、`MediatR` `56.108 ns / 232 B`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*RequestLifetimeBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:`Singleton` 下 baseline / `GFramework.Cqrs` / `MediatR` 约 `5.720 ns / 52.490 ns / 56.890 ns`,`Transient` 下约 `5.814 ns / 57.746 ns / 55.545 ns`
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Core.Tests/GFramework.Core.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~ArchitectureModulesBehaviorTests"`
|
||||
@ -409,10 +425,9 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 下一推荐步骤
|
||||
|
||||
1. 既然 `RP-120` 与 `RP-121` 已分别补齐默认接线修复和标准架构启动回归,下一轮若继续留在 notification 线,优先评估是否真的需要第三种仓库内置策略,而不是继续重复同层级生产路径验证
|
||||
2. 既然本轮已经补上标准架构启动回归,下一轮若继续留在 notification 线,应把问题重新收敛到“是否值得公开第三种仓库内置 publisher strategy”,而不是继续重复扩同层级回归
|
||||
3. 若后续批次切回 request dispatch 常量开销,继续避开“类型级 `IContextAware` 判定缓存”这条已验证无收益的热点假设,并优先挑选更可能影响 steady-state 的 generated/provider 吸收点
|
||||
4. 若 benchmark 对照需要继续贴近 `Mediator` 官方设计,再评估 `Mediator` 的 compile-time lifetime / stream 对照矩阵,或给 stream 引入 scoped host 基线,而不是回头重试已被 benchmark 否决的 `GetAll(Type)` 零行为探测方案
|
||||
1. 若下一轮继续压 request steady-state,优先挑选仍能减少常量热路径查询/分支的切片;继续避开“类型级 `IContextAware` 判定缓存”这条已验证无收益的热点假设
|
||||
2. 若下一轮转向 benchmark 对齐,优先评估 `request scoped host + compile-time lifetime` 对照,而不是继续并行跑多个 BenchmarkDotNet 任务去争用同一自动生成目录
|
||||
3. 若下一轮回到 notification 线,应把问题重新收敛到“是否值得公开第三种仓库内置 publisher strategy”或“是否需要 `IServiceCollection` 版本的公开入口”,而不是继续重复扩同层级回归
|
||||
|
||||
## 活跃文档
|
||||
|
||||
|
||||
@ -2,6 +2,34 @@
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### 阶段:request 零管道 behavior presence cache(CQRS-REWRITE-RP-122)
|
||||
|
||||
- 延续 `$gframework-batch-boot 50`,本轮在 `RP-121` 把 notification 线阶段性收口后,重新回到 request steady-state 常量开销,并接受并行 explorer 的共同结论:下一刀应继续减少每次 `SendAsync(...)` 必经的通用查询,而不是回头优化 `HasRegistration(Type)` 内部实现或重试已证伪的 `IContextAware` 类型缓存
|
||||
- 本轮主线程决策:
|
||||
- 只改 `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 与 `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`,不同时打开 scoped benchmark 宿主或 notification 新公开 API 两条线
|
||||
- 为 `CqrsDispatcher` 新增 `_requestBehaviorPresenceCache`,按闭合 `IPipelineBehavior<,>` 服务类型缓存“当前 dispatcher 的容器里是否存在该 request behavior 注册”
|
||||
- 保持优化面只覆盖 request `0 pipeline` 热路径;stream 对称缓存与 scoped host benchmark 继续留到后续独立批次
|
||||
- 在 `CqrsDispatcherCacheTests` 新增实例级回归,明确“同容器多个 `ArchitectureContext` 解析到同一个 runtime/dispatcher,会共享该缓存;另一独立容器创建的 dispatcher 不共享该缓存”
|
||||
- 本轮权威验证:
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsDispatcherCacheTests"`
|
||||
- 结果:通过,`11/11` passed
|
||||
- 备注:新增回归首轮曾因错误假设“不同 `ArchitectureContext` 必定对应不同 dispatcher”而失败;修正为“同容器共享 runtime、独立容器不共享缓存”后稳定通过
|
||||
- `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 -- --filter "*RequestBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:默认 request steady-state 当前约为 baseline `5.876 ns / 32 B`、`Mediator` `5.275 ns / 32 B`、`GFramework.Cqrs` `51.717 ns / 32 B`、`MediatR` `56.108 ns / 232 B`
|
||||
- `dotnet run --project GFramework.Cqrs.Benchmarks/GFramework.Cqrs.Benchmarks.csproj -c Release --no-build -- --filter "*RequestLifetimeBenchmarks.SendRequest_*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:首次与 `RequestBenchmarks` 并行触发时,BenchmarkDotNet 自动生成项目目录发生 `.nuget.g.props already exists` 冲突;改为串行重跑同一命令后,`Singleton` 下 baseline / `GFramework.Cqrs` / `MediatR` 约 `5.720 ns / 52.490 ns / 56.890 ns`,`Transient` 下约 `5.814 ns / 57.746 ns / 55.545 ns`
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`
|
||||
- 结果:通过
|
||||
- 本轮结论:
|
||||
- request `0 pipeline` 常量路径再次被压短,默认 steady-state request 与 `Singleton` lifetime 均继续快于当前 `MediatR` short-job 基线
|
||||
- `Transient` 仍略慢于 `MediatR`,但相较更早轮次已明显收敛;下一轮若继续 request 热点,更值得继续减少 steady-state 必经路径,或切到 explorer 建议的 `request scoped host + compile-time lifetime` 对齐线,而不是继续打磨已收益有限的 `HasRegistration(Type)` 内部细节
|
||||
|
||||
### 阶段:标准架构启动路径 notification publisher 回归(CQRS-REWRITE-RP-121)
|
||||
|
||||
- 延续 `$gframework-batch-boot 50`,本轮没有继续扩 notification runtime 语义,而是先给 `RP-120` 刚修复的默认接线补一条更贴近生产的架构启动回归
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user