mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-10 02:59:02 +08:00
perf(cqrs): 缓存 stream pipeline 存在性判定
- 优化 CqrsDispatcher 的 CreateStream 热路径,按 dispatcher 实例缓存 stream pipeline behavior 的服务可见性 - 新增 stream presence cache 回归与最小测试桩,锁住同容器共享、跨容器隔离的缓存语义 - 更新 cqrs-rewrite 恢复文档并补充本轮 stream benchmark 验证结果
This commit is contained in:
parent
d85828c533
commit
f9dd105bcc
@ -195,6 +195,55 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 stream 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
|
||||
/// 并与当前容器的实际服务可见性保持一致,同时不同 dispatcher 不共享该实例级状态。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task Dispatcher_Should_Cache_Stream_Behavior_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);
|
||||
var zeroPipelineBehaviorType = typeof(IStreamPipelineBehavior<DispatcherZeroPipelineStreamRequest, int>);
|
||||
var twoPipelineBehaviorType = typeof(IStreamPipelineBehavior<DispatcherStreamPipelineOrderRequest, int>);
|
||||
var expectedZeroPipelinePresence = _container!.HasRegistration(zeroPipelineBehaviorType);
|
||||
var expectedTwoPipelinePresence = _container.HasRegistration(twoPipelineBehaviorType);
|
||||
|
||||
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, zeroPipelineBehaviorType);
|
||||
AssertStreamBehaviorPresenceIsUnset(secondDispatcher, zeroPipelineBehaviorType);
|
||||
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
|
||||
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, twoPipelineBehaviorType);
|
||||
|
||||
await DrainAsync(firstContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
|
||||
await DrainAsync(firstContext.CreateStream(new DispatcherStreamPipelineOrderRequest()));
|
||||
|
||||
var zeroPipelinePresence = GetStreamBehaviorPresenceCacheValue(
|
||||
firstDispatcher,
|
||||
zeroPipelineBehaviorType);
|
||||
var twoPipelinePresence = GetStreamBehaviorPresenceCacheValue(
|
||||
firstDispatcher,
|
||||
twoPipelineBehaviorType);
|
||||
|
||||
AssertSharedStreamDispatcherCacheState(
|
||||
firstDispatcher,
|
||||
secondDispatcher,
|
||||
isolatedDispatcher,
|
||||
zeroPipelinePresence,
|
||||
twoPipelinePresence,
|
||||
zeroPipelineBehaviorType,
|
||||
expectedZeroPipelinePresence,
|
||||
expectedTwoPipelinePresence);
|
||||
|
||||
await DrainAsync(isolatedContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
|
||||
|
||||
AssertStreamBehaviorPresenceEquals(isolatedDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 request pipeline executor 会按行为数量在 binding 内首次创建并在后续分发中复用。
|
||||
/// </summary>
|
||||
@ -713,6 +762,33 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
return found ? arguments[1] : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定 dispatcher 实例中当前保存的 stream behavior presence 缓存项。
|
||||
/// </summary>
|
||||
private static object? GetStreamBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
|
||||
{
|
||||
var field = dispatcher.GetType().GetField(
|
||||
"_streamBehaviorPresenceCache",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
|
||||
Assert.That(field, Is.Not.Null, "Missing dispatcher stream behavior presence cache field.");
|
||||
|
||||
var cache = field!.GetValue(dispatcher)
|
||||
?? throw new InvalidOperationException(
|
||||
"Dispatcher stream 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>
|
||||
@ -729,6 +805,22 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项尚未建立。
|
||||
/// </summary>
|
||||
private static void AssertStreamBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
|
||||
{
|
||||
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项等于预期值。
|
||||
/// </summary>
|
||||
private static void AssertStreamBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
|
||||
{
|
||||
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言同一容器解析出的 dispatcher 会共享实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
|
||||
/// </summary>
|
||||
@ -754,6 +846,29 @@ internal sealed class CqrsDispatcherCacheTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断言同一容器解析出的 dispatcher 会共享 stream 的实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
|
||||
/// </summary>
|
||||
private static void AssertSharedStreamDispatcherCacheState(
|
||||
object firstDispatcher,
|
||||
object secondDispatcher,
|
||||
object isolatedDispatcher,
|
||||
object? zeroPipelinePresence,
|
||||
object? twoPipelinePresence,
|
||||
Type zeroPipelineBehaviorType,
|
||||
bool expectedZeroPipelinePresence,
|
||||
bool expectedTwoPipelinePresence)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
|
||||
Assert.That(zeroPipelinePresence, Is.EqualTo(expectedZeroPipelinePresence));
|
||||
Assert.That(twoPipelinePresence, Is.EqualTo(expectedTwoPipelinePresence));
|
||||
AssertStreamBehaviorPresenceEquals(secondDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
|
||||
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取 request dispatch binding 中指定行为数量的 pipeline executor 缓存项。
|
||||
/// </summary>
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
// Copyright (c) 2025-2026 GeWuYou
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 处理 <see cref="DispatcherZeroPipelineStreamRequest" />,用于验证零管道 stream 的 dispatcher 缓存路径。
|
||||
/// </summary>
|
||||
internal sealed class DispatcherZeroPipelineStreamHandler : IStreamRequestHandler<DispatcherZeroPipelineStreamRequest, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回一个单元素异步流,便于在缓存测试中最小化处理噪音。
|
||||
/// </summary>
|
||||
/// <param name="request">当前零管道 stream 请求。</param>
|
||||
/// <param name="cancellationToken">用于终止异步枚举的取消令牌。</param>
|
||||
/// <returns>只包含一个元素的异步响应流。</returns>
|
||||
public async IAsyncEnumerable<int> Handle(
|
||||
DispatcherZeroPipelineStreamRequest request,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(request);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
yield return 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
// Copyright (c) 2025-2026 GeWuYou
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 表示未注册任何 stream pipeline behavior 的最小缓存验证请求。
|
||||
/// </summary>
|
||||
internal sealed record DispatcherZeroPipelineStreamRequest : IStreamRequest<int>;
|
||||
@ -26,6 +26,10 @@ internal sealed class CqrsDispatcher(
|
||||
// 每次 SendAsync 都重复询问容器。缓存值只反映当前 dispatcher 持有容器的注册可见性,不跨 runtime 共享。
|
||||
private readonly ConcurrentDictionary<Type, bool> _requestBehaviorPresenceCache = new();
|
||||
|
||||
// 与 request 路径相同,stream 的 behavior 注册可见性在当前 dispatcher 生命周期内保持稳定。
|
||||
// 这里缓存 “CreateStream(...) 对应 behaviorType 是否存在注册”,避免零管道 stream 每次建流都重复询问容器。
|
||||
private readonly ConcurrentDictionary<Type, bool> _streamBehaviorPresenceCache = new();
|
||||
|
||||
// 卸载安全的进程级缓存:当 generated registry 提供 request invoker 元数据时,
|
||||
// registrar 会按请求/响应类型对把它们写入这里;若类型被卸载,条目会自然失效。
|
||||
private static readonly WeakTypePairCache<GeneratedRequestInvokerMetadata>
|
||||
@ -195,7 +199,7 @@ internal sealed class CqrsDispatcher(
|
||||
$"No CQRS stream handler registered for {requestType.FullName}.");
|
||||
|
||||
PrepareHandler(handler, context);
|
||||
if (!container.HasRegistration(dispatchBinding.BehaviorType))
|
||||
if (!HasStreamBehaviorRegistration(dispatchBinding.BehaviorType))
|
||||
{
|
||||
return (IAsyncEnumerable<TResponse>)dispatchBinding.StreamInvoker(handler, request, cancellationToken);
|
||||
}
|
||||
@ -211,6 +215,21 @@ internal sealed class CqrsDispatcher(
|
||||
.Invoke(handler, behaviors, dispatchBinding.StreamInvoker, request, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取当前 dispatcher 容器里是否存在指定 stream pipeline 行为注册,并在首次命中后缓存结果。
|
||||
/// </summary>
|
||||
/// <param name="behaviorType">目标 stream pipeline 行为服务类型。</param>
|
||||
/// <returns>存在注册时返回 <see langword="true" />;否则返回 <see langword="false" />。</returns>
|
||||
private bool HasStreamBehaviorRegistration(Type behaviorType)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(behaviorType);
|
||||
|
||||
return _streamBehaviorPresenceCache.GetOrAdd(
|
||||
behaviorType,
|
||||
static (cachedBehaviorType, currentContainer) => currentContainer.HasRegistration(cachedBehaviorType),
|
||||
container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为上下文感知处理器注入当前 CQRS 分发上下文。
|
||||
/// </summary>
|
||||
|
||||
@ -7,11 +7,18 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-123`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-124`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前 PR 锚点:`PR #344`
|
||||
- 当前结论:
|
||||
- 当前 `RP-123` 通过 `$gframework-pr-review` 重新复核 `feat/cqrs-optimization` 的 latest-head review,确认 `PR #344` 仍成立且值得在本轮一起收口的问题共有四类:`CqrsDispatcher.ResolveNotificationPublisher()` 默认路径每次 publish 都重复查容器并在零注册分支分配新的 `SequentialNotificationPublisher`;`CqrsDispatcherContextValidationTests` 与 `CqrsNotificationPublisherTests` 的 strict `IIocContainer` helper 缺少 `GetAll(typeof(INotificationPublisher))` 默认装配,导致 CI 在真正断言前就被 mock 异常短路;`NotificationPublisherRegistrationExtensionsTests` 缺少“唯一注册”断言;`CqrsDispatcherCacheTests` 的隔离容器构建复制了 `SetUp()` 的注册形状,存在后续漂移风险
|
||||
- 当前 `RP-124` 延续 `$gframework-batch-boot 50`,在 `RP-123` 收口 latest-head review 对 notification publisher 的四类问题后,回到 stream steady-state 热路径,选择与 `RP-122` request cache 对称、且仍保持小写面的一刀:把 `CreateStream(...)` 上的 behavior presence 判定收口为 dispatcher 实例级缓存
|
||||
- 本轮改动面只落在 `GFramework.Cqrs/Internal/CqrsDispatcher.cs`、`GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`、新增的 `DispatcherZeroPipelineStreamRequest/Handler` 测试桩,以及 `ai-plan/public/cqrs-rewrite` 恢复文档,不扩到新的公开 API、文档页或额外 benchmark 宿主代码
|
||||
- `CqrsDispatcher` 现新增 `_streamBehaviorPresenceCache`,按闭合 `IStreamPipelineBehavior<,>` 服务类型缓存当前 dispatcher 容器中的服务可见性;这样 `CreateStream(...)` 在 steady-state 下不会每次都重复执行 `HasRegistration(Type)`,并继续保持“同一 runtime 实例内缓存、不同容器不共享”的边界
|
||||
- `CqrsDispatcherCacheTests` 现新增 `Dispatcher_Should_Cache_Stream_Behavior_Presence_Per_Dispatcher_Instance()`,锁住两件事:同一容器解析出的多个 `ArchitectureContext` 会共享同一个 dispatcher 实例及其 stream presence cache;另一独立冻结容器创建的 dispatcher 不会提前共享该实例级状态。为避免夹具里已有 stream behavior 注册干扰零管道建流路径,本轮补入 `DispatcherZeroPipelineStreamRequest` 与 `DispatcherZeroPipelineStreamHandler` 作为最小测试桩
|
||||
- 本轮 benchmark 仅复跑最小 stream 生命周期矩阵中的 `StreamLifetimeBenchmarks.Stream_GFrameworkCqrs`,确认 benchmark 宿主在这刀之后仍能稳定产出:`Singleton` 约 `107.241 ns / 240 B`,`Transient` 约 `119.434 ns / 264 B`
|
||||
- 当前已提交分支相对 `origin/main`(`d85828c5`, `2026-05-09 12:25:41 +0800`)的累计 branch diff 仍为 `0 files / 0 changed lines`;本批待提交工作树共触达 `4 files`,其中已跟踪 diff 为 `135 insertions / 1 deletion`,另有 `2` 个新测试文件共 `43` 行,明显低于 `$gframework-batch-boot 50` 的文件阈值
|
||||
- 下一推荐步骤:提交并推送本轮 commit 后,再次运行 `$gframework-pr-review` 复核 `PR #344` latest-head thread 是否已收敛;若 review 已清空,则下一批优先比较完整 `StreamLifetimeBenchmarks` 三方对照是否仍优于 `MediatR`,再决定继续压 stream 零管道常量路径还是切回 request `Transient` 热点
|
||||
- 当前 `RP-123` 通过 `$gframework-pr-review` 重新复核 `feat/cqrs-optimization` 的 latest-head review,确认 `PR #344` 仍成立且值得在本轮一起收口的问题共有四类:`CqrsDispatcher.ResolveNotificationPublisher()` 默认路径每次 publish 都重复查容器并在零注册分支分配新的 `SequentialNotificationPublisher`;`CqrsDispatcherContextValidationTests` 与 `CqrsNotificationPublisherTests` 的 strict `IIocContainer` helper 缺少 `GetAll(typeof(INotificationPublisher))` 默认装配,导致 CI 在真正断言前就被 mock 异常短路;`NotificationPublisherRegistrationExtensionsTests` 缺少“唯一注册”断言;`CqrsDispatcherCacheTests` 的隔离容器构建复制了 `SetUp()` 的注册形状,存在后续漂移风险
|
||||
- 本轮保持改动面只落在 `GFramework.Cqrs`、`GFramework.Cqrs.Tests` 与 `ai-plan/public/cqrs-rewrite`,不扩散到新的 benchmark 宿主或额外 notification API;其中 `CqrsDispatcher` 新增 dispatcher 实例级 `_resolvedNotificationPublisher` 缓存,并在首次解析后通过线程安全比较交换固定最终策略实例,继续保持“显式实例优先、容器内唯一注册次之、默认顺序发布器兜底”的既有契约
|
||||
- 两个 strict mock runtime helper 现统一预设 `IIocContainer.GetAll(typeof(INotificationPublisher)) => Array.Empty<object>()`,把“未注册自定义 publisher 时回退到默认顺序发布器”这条默认路径显式纳入测试装配,避免后续相同语义再次被环境性 mock 配置遗漏掩盖
|
||||
- `NotificationPublisherRegistrationExtensionsTests` 现在对泛型组合根重载补上 `container.GetAll(typeof(INotificationPublisher))` 的唯一注册断言,防止实现未来意外追加重复 descriptor 却仍因 `GetRequired<INotificationPublisher>()` 返回单个实例而误通过
|
||||
@ -97,9 +104,9 @@ CQRS 迁移与收敛。
|
||||
## 当前活跃事实
|
||||
|
||||
- 当前分支为 `feat/cqrs-optimization`
|
||||
- 本轮 `$gframework-batch-boot 50` 以 `origin/main` (`d389eb36`, 2026-05-08 20:08:33 +0800) 为基线;本地 `main` 仍落后,不作为 branch diff 基线
|
||||
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `10 files / 377 changed lines`
|
||||
- 本批待提交工作树集中在 `GFramework.Cqrs/Internal/CqrsDispatcher.cs` 与 `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`
|
||||
- 本轮 `$gframework-batch-boot 50` 以 `origin/main` (`d85828c5`, `2026-05-09 12:25:41 +0800`) 为基线;本地 `main` (`c2d22285`, `2026-05-06 21:34:59 +0800`) 已落后,不作为 branch diff 基线
|
||||
- 当前已提交分支相对 `origin/main` 的累计 branch diff 为 `0 files / 0 changed lines`
|
||||
- 本批待提交工作树触达 `4 files`:`GFramework.Cqrs/Internal/CqrsDispatcher.cs`、`GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`、`DispatcherZeroPipelineStreamRequest.cs` 与 `DispatcherZeroPipelineStreamHandler.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` 生命周期矩阵
|
||||
@ -156,6 +163,7 @@ CQRS 迁移与收敛。
|
||||
## 当前风险
|
||||
|
||||
- 当前 `_requestBehaviorPresenceCache` 依赖“同一 dispatcher 生命周期内,request pipeline 行为注册在容器冻结后保持稳定”这一约束;若未来引入运行时动态增删 request behavior 的模型,需要重新评估这类实例级 presence cache 的失效策略
|
||||
- 当前 `_streamBehaviorPresenceCache` 也依赖“同一 dispatcher 生命周期内,stream pipeline 行为注册在容器冻结后保持稳定”这一约束;若后续引入运行期动态增删 stream behavior 或按 scope 改写可见性的模型,需要同步设计失效策略,而不能继续假设实例级缓存永久有效
|
||||
- 标准架构启动路径现在已经有“自定义 notification publisher 不被默认顺序策略短路”的集成回归;但若后续再引入第三种仓库内置策略或新的启动快捷入口,仍需要同步补这条生产路径验证,不能只看 `CqrsTestRuntime` 测试宿主
|
||||
- 顶层 `GFramework.sln` / `GFramework.csproj` 在 WSL 下仍可能受 Windows NuGet fallback 配置影响,完整 solution 级验证成本高于模块级验证
|
||||
- 若后续新增 benchmark / example / tooling 项目但未同步校验发布面,solution 级 `dotnet pack` 仍可能在 tag 发布前才暴露异常包
|
||||
@ -176,6 +184,23 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 最近权威验证
|
||||
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs GFramework.Cqrs.Tests/Cqrs/DispatcherZeroPipelineStreamRequest.cs GFramework.Cqrs.Tests/Cqrs/DispatcherZeroPipelineStreamHandler.cs`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsDispatcherCacheTests"`
|
||||
- 结果:通过,`12/12` 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 "*StreamLifetimeBenchmarks.Stream_GFrameworkCqrs*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:`Singleton` 约 `107.241 ns / 240 B`,`Transient` 约 `119.434 ns / 264 B`
|
||||
- `git diff --check`
|
||||
- 结果:通过
|
||||
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题
|
||||
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsNotificationPublisherTests.cs GFramework.Cqrs.Tests/Cqrs/NotificationPublisherRegistrationExtensionsTests.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
|
||||
@ -2,6 +2,33 @@
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### 阶段:stream behavior presence cache(CQRS-REWRITE-RP-124)
|
||||
|
||||
- 延续 `$gframework-batch-boot 50`,在 `RP-123` 收口 notification publisher review 线程后,继续把 `CqrsDispatcher` 的 stream 热路径与 `RP-122` 的 request hot path 对齐,选择“缓存 `CreateStream(...)` 的 behavior presence 判定”这一条最小且可验证的 runtime 切片
|
||||
- 本轮主线程决策:
|
||||
- 仅修改 `GFramework.Cqrs/Internal/CqrsDispatcher.cs`、`GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs`,并新增 `DispatcherZeroPipelineStreamRequest/Handler` 测试桩,不扩散到新的公开 API、中文文档或额外 benchmark 宿主实现
|
||||
- 为 `CqrsDispatcher` 新增实例级 `_streamBehaviorPresenceCache`,按闭合 `IStreamPipelineBehavior<,>` 服务类型缓存当前 dispatcher 容器中的服务可见性,让 `CreateStream(...)` 在 steady-state 下不再重复调用 `HasRegistration(Type)`
|
||||
- 在 `CqrsDispatcherCacheTests` 新增 `Dispatcher_Should_Cache_Stream_Behavior_Presence_Per_Dispatcher_Instance()`,显式锁住“同容器共享同一 dispatcher/cache、独立容器不共享”的实例级边界,并把缓存值与容器实际 `HasRegistration(...)` 语义对齐,避免测试再依赖夹具对某个具体 stream 类型的错误可见性假设
|
||||
- 本轮验证:
|
||||
- `python3 scripts/license-header.py --check --paths GFramework.Cqrs/Internal/CqrsDispatcher.cs GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs GFramework.Cqrs.Tests/Cqrs/DispatcherZeroPipelineStreamRequest.cs GFramework.Cqrs.Tests/Cqrs/DispatcherZeroPipelineStreamHandler.cs`
|
||||
- 结果:通过
|
||||
- `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet build GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release`
|
||||
- 结果:通过,`0 warning / 0 error`
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-build --filter "FullyQualifiedName~CqrsDispatcherCacheTests"`
|
||||
- 结果:通过,`12/12` 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 "*StreamLifetimeBenchmarks.Stream_GFrameworkCqrs*" --job short --warmupCount 1 --iterationCount 1 --launchCount 1`
|
||||
- 结果:通过
|
||||
- 备注:`Singleton` 约 `107.241 ns / 240 B`,`Transient` 约 `119.434 ns / 264 B`
|
||||
- `git diff --check`
|
||||
- 结果:通过
|
||||
- 备注:仅剩 `GFramework.sln` 的历史 CRLF 提示,无本轮新增 diff 格式问题
|
||||
- 下一恢复点:
|
||||
- 推送本轮 commit 后,再次运行 `$gframework-pr-review` 复核 `PR #344` latest-head thread 是否已收敛;若 review 已清空,则下一批优先补完整 `StreamLifetimeBenchmarks` 三方对照,再决定继续压 stream 零管道常量路径还是切回 request `Transient` 热点
|
||||
|
||||
### 阶段:PR #344 latest-head review 收尾(CQRS-REWRITE-RP-123)
|
||||
|
||||
- 使用 `$gframework-pr-review` 重新抓取当前分支 PR,确认当前 worktree 对应 `PR #344`,latest-head 仍有 `CodeRabbit 2` / `Greptile 1` open thread
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user