From 255a6a152e06be24c9f8215107f5af22b8c297d7 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Thu, 30 Apr 2026 07:43:42 +0800
Subject: [PATCH] =?UTF-8?q?fix(cqrs):=20=E6=94=B6=E6=95=9B=20PR=20304=20re?=
=?UTF-8?q?view=20=E8=B7=9F=E8=BF=9B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 修复 CqrsDispatcher 的 pipeline invoker 重复创建,并补齐缓存线程模型文档
- 优化 CQRS 与 generator 回归测试的并发保护和稳定语义断言
- 更新 cqrs-rewrite 跟踪与 trace,记录 RP-062 的 PR review follow-up 验证结果
---
.../Cqrs/CqrsDispatcherCacheTests.cs | 10 ++++
...qrsHandlerRegistrarFallbackFailureTests.cs | 8 ++-
.../Cqrs/CqrsRegistrationServiceTests.cs | 17 +++---
...spatcherNotificationContextRefreshState.cs | 28 ++++++++--
.../DispatcherPipelineContextRefreshState.cs | 52 +++++++++++++++----
...patcherPipelineOrderCacheRequestHandler.cs | 2 +-
.../DispatcherPipelineOrderInnerBehavior.cs | 4 +-
.../DispatcherPipelineOrderOuterBehavior.cs | 4 +-
.../Cqrs/DispatcherPipelineOrderState.cs | 36 +++++++++++--
.../DispatcherStreamContextRefreshState.cs | 28 ++++++++--
GFramework.Cqrs/Internal/CqrsDispatcher.cs | 27 +++++++---
.../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 33 ++++++------
.../todos/cqrs-rewrite-migration-tracking.md | 10 +++-
.../traces/cqrs-rewrite-migration-trace.md | 31 +++++++++++
14 files changed, 228 insertions(+), 62 deletions(-)
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs
index 3d2040ab..cd95d2d8 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherCacheTests.cs
@@ -10,6 +10,7 @@ namespace GFramework.Cqrs.Tests.Cqrs;
/// 验证 CQRS dispatcher 会缓存热路径中的 dispatch binding。
///
[TestFixture]
+[NonParallelizable]
internal sealed class CqrsDispatcherCacheTests
{
private MicrosoftDiContainer? _container;
@@ -434,6 +435,11 @@ internal sealed class CqrsDispatcherCacheTests
///
/// 读取 request dispatch binding 中指定行为数量的 pipeline executor 缓存项。
///
+ /// dispatcher 内部的 request binding 缓存对象。
+ /// 要读取的请求运行时类型。
+ /// 要读取的响应运行时类型。
+ /// 目标 executor 对应的行为数量。
+ /// 已缓存的 executor;若 binding 或 executor 尚未建立则返回 。
private static object? GetRequestPipelineExecutorValue(
object requestBindings,
Type requestType,
@@ -471,6 +477,10 @@ internal sealed class CqrsDispatcherCacheTests
///
/// 读取指定请求/响应类型对对应的强类型 request dispatch binding。
///
+ /// dispatcher 内部的 request binding 缓存对象。
+ /// 要读取的请求运行时类型。
+ /// 要读取的响应运行时类型。
+ /// 强类型 binding;若缓存尚未建立则返回 。
private static object? GetRequestDispatchBindingValue(object requestBindings, Type requestType, Type responseType)
{
var bindingBox = GetPairCacheValue(requestBindings, requestType, responseType);
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs
index 213f3870..e4b8633f 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsHandlerRegistrarFallbackFailureTests.cs
@@ -174,6 +174,7 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
///
/// 清空本测试依赖的 registrar 静态缓存,确保每个用例都会重新执行 fallback 元数据解析。
+ /// 这些字段名直接耦合 CqrsHandlerRegistrar 当前内部实现;若后续重构缓存布局,需要同步更新这里。
///
private static void ClearRegistrarCaches()
{
@@ -194,11 +195,14 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
fieldName,
BindingFlags.NonPublic | BindingFlags.Static);
- Assert.That(field, Is.Not.Null, $"Missing registrar cache field {fieldName}.");
+ Assert.That(
+ field,
+ Is.Not.Null,
+ $"Expected field '{fieldName}' on CqrsHandlerRegistrar not found; rename/refactor may require test update.");
return field!.GetValue(null)
?? throw new InvalidOperationException(
- $"Registrar cache field {fieldName} returned null.");
+ $"Registrar cache field '{fieldName}' on CqrsHandlerRegistrar returned null.");
}
///
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs
index b8b03b1c..b1c3c60b 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsRegistrationServiceTests.cs
@@ -69,13 +69,18 @@ internal sealed class CqrsRegistrationServiceTests
Assert.Multiple(() =>
{
Assert.That(registeredAssemblies, Is.EqualTo([firstAssembly.Object]));
+ var debugMessages = logger.Logs
+ .Where(static log => log.Level == LogLevel.Debug)
+ .Select(static log => log.Message)
+ .ToArray();
+ Assert.That(debugMessages, Has.Length.EqualTo(1));
Assert.That(
- logger.Logs.Where(static log => log.Level == LogLevel.Debug).Select(static log => log.Message),
- Is.EqualTo(
- [
- "Skipping CQRS handler registration for assembly " +
- "GFramework.Cqrs.Tests.RegisteredAssembly, Version=1.0.0.0 because it was already registered."
- ]));
+ debugMessages[0],
+ Does.Contain("Skipping CQRS handler registration for assembly"));
+ Assert.That(
+ debugMessages[0],
+ Does.Contain("GFramework.Cqrs.Tests.RegisteredAssembly, Version=1.0.0.0"));
+ Assert.That(debugMessages[0], Does.Contain("already registered"));
});
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherNotificationContextRefreshState.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherNotificationContextRefreshState.cs
index 43c32531..b6d8a7ca 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherNotificationContextRefreshState.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherNotificationContextRefreshState.cs
@@ -8,12 +8,24 @@ namespace GFramework.Cqrs.Tests.Cqrs;
///
internal static class DispatcherNotificationContextRefreshState
{
+ private static readonly Lock SyncRoot = new();
private static int _nextHandlerInstanceId;
+ private static readonly List _handlerSnapshots = [];
///
- /// 获取每次 notification 分发时记录的快照。
+ /// 获取每次 notification 分发时记录的快照副本。
+ /// 共享状态通过 SyncRoot 串行化,避免并行测试写入抖动。
///
- public static List HandlerSnapshots { get; } = [];
+ public static IReadOnlyList HandlerSnapshots
+ {
+ get
+ {
+ lock (SyncRoot)
+ {
+ return _handlerSnapshots.ToArray();
+ }
+ }
+ }
///
/// 为新的 handler 测试实例分配稳定编号。
@@ -28,7 +40,10 @@ internal static class DispatcherNotificationContextRefreshState
///
public static void Record(string dispatchId, int instanceId, IArchitectureContext context)
{
- HandlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ lock (SyncRoot)
+ {
+ _handlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ }
}
///
@@ -36,7 +51,10 @@ internal static class DispatcherNotificationContextRefreshState
///
public static void Reset()
{
- _nextHandlerInstanceId = 0;
- HandlerSnapshots.Clear();
+ lock (SyncRoot)
+ {
+ _nextHandlerInstanceId = 0;
+ _handlerSnapshots.Clear();
+ }
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineContextRefreshState.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineContextRefreshState.cs
index 15913678..99cae23a 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineContextRefreshState.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineContextRefreshState.cs
@@ -8,18 +8,41 @@ namespace GFramework.Cqrs.Tests.Cqrs;
///
internal static class DispatcherPipelineContextRefreshState
{
+ private static readonly Lock SyncRoot = new();
private static int _nextBehaviorInstanceId;
private static int _nextHandlerInstanceId;
+ private static readonly List _behaviorSnapshots = [];
+ private static readonly List _handlerSnapshots = [];
///
- /// 获取每次 behavior 执行时记录的快照。
+ /// 获取每次 behavior 执行时记录的快照副本。
+ /// 共享状态通过 SyncRoot 串行化,读取端始终拿到当前稳定快照。
///
- public static List BehaviorSnapshots { get; } = [];
+ public static IReadOnlyList BehaviorSnapshots
+ {
+ get
+ {
+ lock (SyncRoot)
+ {
+ return _behaviorSnapshots.ToArray();
+ }
+ }
+ }
///
- /// 获取每次 handler 执行时记录的快照。
+ /// 获取每次 handler 执行时记录的快照副本。
+ /// 共享状态通过 SyncRoot 串行化,读取端始终拿到当前稳定快照。
///
- public static List HandlerSnapshots { get; } = [];
+ public static IReadOnlyList HandlerSnapshots
+ {
+ get
+ {
+ lock (SyncRoot)
+ {
+ return _handlerSnapshots.ToArray();
+ }
+ }
+ }
///
/// 为新的 behavior 测试实例分配稳定编号。
@@ -42,7 +65,10 @@ internal static class DispatcherPipelineContextRefreshState
///
public static void RecordBehavior(string dispatchId, int instanceId, IArchitectureContext context)
{
- BehaviorSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ lock (SyncRoot)
+ {
+ _behaviorSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ }
}
///
@@ -50,7 +76,10 @@ internal static class DispatcherPipelineContextRefreshState
///
public static void RecordHandler(string dispatchId, int instanceId, IArchitectureContext context)
{
- HandlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ lock (SyncRoot)
+ {
+ _handlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ }
}
///
@@ -58,9 +87,12 @@ internal static class DispatcherPipelineContextRefreshState
///
public static void Reset()
{
- _nextBehaviorInstanceId = 0;
- _nextHandlerInstanceId = 0;
- BehaviorSnapshots.Clear();
- HandlerSnapshots.Clear();
+ lock (SyncRoot)
+ {
+ _nextBehaviorInstanceId = 0;
+ _nextHandlerInstanceId = 0;
+ _behaviorSnapshots.Clear();
+ _handlerSnapshots.Clear();
+ }
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderCacheRequestHandler.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderCacheRequestHandler.cs
index 00224ff8..d2b88baf 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderCacheRequestHandler.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderCacheRequestHandler.cs
@@ -15,7 +15,7 @@ internal sealed class DispatcherPipelineOrderCacheRequestHandler : IRequestHandl
/// 固定整数结果。
public ValueTask Handle(DispatcherPipelineOrderCacheRequest request, CancellationToken cancellationToken)
{
- DispatcherPipelineOrderState.Steps.Add("Handler");
+ DispatcherPipelineOrderState.Record("Handler");
return ValueTask.FromResult(3);
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderInnerBehavior.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderInnerBehavior.cs
index a4b7def6..cf327d2a 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderInnerBehavior.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderInnerBehavior.cs
@@ -19,9 +19,9 @@ internal sealed class DispatcherPipelineOrderInnerBehavior : IPipelineBehavior next,
CancellationToken cancellationToken)
{
- DispatcherPipelineOrderState.Steps.Add("Inner:Before");
+ DispatcherPipelineOrderState.Record("Inner:Before");
var result = await next(request, cancellationToken).ConfigureAwait(false);
- DispatcherPipelineOrderState.Steps.Add("Inner:After");
+ DispatcherPipelineOrderState.Record("Inner:After");
return result;
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderOuterBehavior.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderOuterBehavior.cs
index b9ba2315..aa1f0291 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderOuterBehavior.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderOuterBehavior.cs
@@ -19,9 +19,9 @@ internal sealed class DispatcherPipelineOrderOuterBehavior : IPipelineBehavior next,
CancellationToken cancellationToken)
{
- DispatcherPipelineOrderState.Steps.Add("Outer:Before");
+ DispatcherPipelineOrderState.Record("Outer:Before");
var result = await next(request, cancellationToken).ConfigureAwait(false);
- DispatcherPipelineOrderState.Steps.Add("Outer:After");
+ DispatcherPipelineOrderState.Record("Outer:After");
return result;
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderState.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderState.cs
index 2673371e..fa202b85 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderState.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherPipelineOrderState.cs
@@ -1,3 +1,5 @@
+using System.Threading;
+
namespace GFramework.Cqrs.Tests.Cqrs;
///
@@ -5,16 +7,44 @@ namespace GFramework.Cqrs.Tests.Cqrs;
///
internal static class DispatcherPipelineOrderState
{
+ private static readonly Lock SyncRoot = new();
+ private static readonly List _steps = [];
+
///
- /// 获取按执行顺序追加的步骤名称。
+ /// 获取按执行顺序追加的步骤快照。
+ /// 共享状态通过 SyncRoot 串行化,避免并行行为测试互相污染步骤列表。
///
- public static List Steps { get; } = [];
+ public static IReadOnlyList Steps
+ {
+ get
+ {
+ lock (SyncRoot)
+ {
+ return _steps.ToArray();
+ }
+ }
+ }
+
+ ///
+ /// 记录一个新的 pipeline 执行步骤。
+ ///
+ /// 要追加的步骤名称。
+ public static void Record(string step)
+ {
+ lock (SyncRoot)
+ {
+ _steps.Add(step);
+ }
+ }
///
/// 清空当前记录,供下一次断言使用。
///
public static void Reset()
{
- Steps.Clear();
+ lock (SyncRoot)
+ {
+ _steps.Clear();
+ }
}
}
diff --git a/GFramework.Cqrs.Tests/Cqrs/DispatcherStreamContextRefreshState.cs b/GFramework.Cqrs.Tests/Cqrs/DispatcherStreamContextRefreshState.cs
index a315451d..4f747b7e 100644
--- a/GFramework.Cqrs.Tests/Cqrs/DispatcherStreamContextRefreshState.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/DispatcherStreamContextRefreshState.cs
@@ -8,12 +8,24 @@ namespace GFramework.Cqrs.Tests.Cqrs;
///
internal static class DispatcherStreamContextRefreshState
{
+ private static readonly Lock SyncRoot = new();
private static int _nextHandlerInstanceId;
+ private static readonly List _handlerSnapshots = [];
///
- /// 获取每次建流时记录的快照。
+ /// 获取每次建流时记录的快照副本。
+ /// 共享状态通过 SyncRoot 串行化,避免并行测试写入抖动。
///
- public static List HandlerSnapshots { get; } = [];
+ public static IReadOnlyList HandlerSnapshots
+ {
+ get
+ {
+ lock (SyncRoot)
+ {
+ return _handlerSnapshots.ToArray();
+ }
+ }
+ }
///
/// 为新的 handler 测试实例分配稳定编号。
@@ -28,7 +40,10 @@ internal static class DispatcherStreamContextRefreshState
///
public static void Record(string dispatchId, int instanceId, IArchitectureContext context)
{
- HandlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ lock (SyncRoot)
+ {
+ _handlerSnapshots.Add(new DispatcherPipelineContextSnapshot(dispatchId, instanceId, context));
+ }
}
///
@@ -36,7 +51,10 @@ internal static class DispatcherStreamContextRefreshState
///
public static void Reset()
{
- _nextHandlerInstanceId = 0;
- HandlerSnapshots.Clear();
+ lock (SyncRoot)
+ {
+ _nextHandlerInstanceId = 0;
+ _handlerSnapshots.Clear();
+ }
}
}
diff --git a/GFramework.Cqrs/Internal/CqrsDispatcher.cs b/GFramework.Cqrs/Internal/CqrsDispatcher.cs
index 17e81e8e..d604beac 100644
--- a/GFramework.Cqrs/Internal/CqrsDispatcher.cs
+++ b/GFramework.Cqrs/Internal/CqrsDispatcher.cs
@@ -415,7 +415,11 @@ internal sealed class CqrsDispatcher(
RequestInvoker requestInvoker,
Type requestType)
{
+ // 线程安全:该缓存按 behaviorCount 复用 pipeline executor 形状,GetPipelineExecutor 通过 ConcurrentDictionary
+ // 的 GetOrAdd 支持并发读写。缓存项只保存委托形状,不保留 handler/behavior 实例;若行为数量组合持续增长,
+ // 字典会随之增长且当前实现不提供回收。
private readonly ConcurrentDictionary> _pipelineExecutors = new();
+ private readonly RequestPipelineInvoker _pipelineInvoker = CreateRequestPipelineInvoker(requestType);
///
/// 获取请求处理器在容器中的服务类型。
@@ -441,8 +445,8 @@ internal sealed class CqrsDispatcher(
ArgumentOutOfRangeException.ThrowIfNegative(behaviorCount);
return _pipelineExecutors.GetOrAdd>(
behaviorCount,
- static (count, state) => CreateRequestPipelineExecutor(state.RequestType, count),
- new RequestPipelineExecutorFactoryState(requestType));
+ static (count, state) => CreateRequestPipelineExecutor(count, state.PipelineInvoker),
+ new RequestPipelineExecutorFactoryState(_pipelineInvoker));
}
///
@@ -460,17 +464,23 @@ internal sealed class CqrsDispatcher(
/// 行为数量用于表达缓存形状,实际分发仍会消费本次容器解析出的 handler 与 behaviors 实例。
///
private static RequestPipelineExecutor CreateRequestPipelineExecutor(
- Type requestType,
- int behaviorCount)
+ int behaviorCount,
+ RequestPipelineInvoker invoker)
{
ArgumentOutOfRangeException.ThrowIfNegative(behaviorCount);
+ return new RequestPipelineExecutor(behaviorCount, invoker);
+ }
+ ///
+ /// 为指定请求/响应类型创建可跨多个 behaviorCount 复用的 typed pipeline invoker。
+ ///
+ private static RequestPipelineInvoker CreateRequestPipelineInvoker(Type requestType)
+ {
var method = RequestPipelineInvokerMethodDefinition
.MakeGenericMethod(requestType, typeof(TResponse));
- var invoker = (RequestPipelineInvoker)Delegate.CreateDelegate(
+ return (RequestPipelineInvoker)Delegate.CreateDelegate(
typeof(RequestPipelineInvoker),
method);
- return new RequestPipelineExecutor(behaviorCount, invoker);
}
///
@@ -510,7 +520,8 @@ internal sealed class CqrsDispatcher(
/// 为 pipeline executor 缓存携带当前请求类型,避免按行为数量建缓存时创建闭包。
///
/// 请求响应类型。
- private readonly record struct RequestPipelineExecutorFactoryState(Type RequestType);
+ private readonly record struct RequestPipelineExecutorFactoryState(
+ RequestPipelineInvoker PipelineInvoker);
///
/// 保存单次 request pipeline 分发所需的当前 handler、behavior 列表和 continuation 缓存。
@@ -537,6 +548,8 @@ internal sealed class CqrsDispatcher(
///
/// 获取指定阶段的 continuation,并在首次请求时为该阶段绑定一次不可变调用入口。
/// 同一行为多次调用 next 时会命中相同 continuation,保持与传统链式委托一致的语义。
+ /// 线程模型上,该缓存仅假定单次分发链按顺序推进;若某个 behavior 并发调用多个 next,
+ /// 这里可能重复创建等价 continuation,但不会跨分发共享,也不会缓存容器解析出的实例。
///
private MessageHandlerDelegate GetContinuation(int index)
{
diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
index 51926a0d..2b83cddc 100644
--- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
+++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs
@@ -1763,13 +1763,12 @@ public class CqrsHandlerRegistryGeneratorTests
{
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0Argument1Element = registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
+ Does.Contain("registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType(2));"));
- Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces("));
+ Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType("));
+ Assert.That(generatedSource, Does.Contain(".MakeArrayType(2)"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
});
}
@@ -1786,13 +1785,12 @@ public class CqrsHandlerRegistryGeneratorTests
{
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0Argument1ElementElement = registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
+ Does.Contain("registryAssembly.GetType(\"TestApp.Container+HiddenResponse\", throwOnError: false, ignoreCase: false);"));
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1ElementElement.MakeArrayType().MakeArrayType());"));
- Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces("));
+ Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType("));
+ Assert.That(generatedSource, Does.Contain(".MakeArrayType().MakeArrayType()"));
+ Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
});
}
@@ -1912,11 +1910,11 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(
generatedSource,
Does.Contain(
- "var serviceType0_0Argument1Element = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedResponse\");"));
+ "ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedResponse\")"));
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType(2));"));
+ Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType("));
+ Assert.That(generatedSource, Does.Contain(".MakeArrayType(2)"));
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
});
}
@@ -1945,16 +1943,15 @@ public class CqrsHandlerRegistryGeneratorTests
Assert.That(
generatedSource,
Does.Contain(
- "var serviceType0_0Argument0 = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedRequest\");"));
+ "ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedRequest\")"));
Assert.That(
generatedSource,
Does.Contain(
- "var serviceType0_0Argument1GenericDefinition = ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedEnvelope`1\");"));
+ "ResolveReferencedAssemblyType(\"Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\", \"Dep.VisibilityScope+ProtectedEnvelope`1\")"));
Assert.That(
generatedSource,
- Does.Contain(
- "var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1GenericDefinition.MakeGenericType(typeof(string)));"));
- Assert.That(generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces("));
+ Does.Contain("typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType("));
+ Assert.That(generatedSource, Does.Contain(".MakeGenericType(typeof(string))"));
Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute("));
});
}
diff --git a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
index e41c245f..65874cd5 100644
--- a/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
+++ b/ai-plan/public/cqrs-rewrite/todos/cqrs-rewrite-migration-tracking.md
@@ -7,7 +7,7 @@ CQRS 迁移与收敛。
## 当前恢复点
-- 恢复点编号:`CQRS-REWRITE-RP-061`
+- 恢复点编号:`CQRS-REWRITE-RP-062`
- 当前阶段:`Phase 8`
- 当前焦点:
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
@@ -29,6 +29,9 @@ CQRS 迁移与收敛。
- 已将 registrar 的重复映射判定从线性扫描 `IServiceCollection` 收敛为本地映射索引,减少 fallback 注册路径的重复查找
- 已完成一轮 `static lambda + state` 微收敛:`CqrsDispatcher` 与 `CqrsHandlerRegistrar` 现会在弱缓存 / 并发缓存入口优先使用无捕获工厂,继续压低热路径上的额外闭包分配
- 已补充 `CqrsReflectionFallbackAttribute` 叶子级合同测试,锁定空 marker、字符串 fallback 名称归一化、直接 `Type` fallback 归一化与空参数防御语义
+ - 已完成 `PR #304` review follow-up 收敛:`CqrsDispatcher` 现补齐 pipeline executor / continuation 缓存的线程模型文档,并把 request pipeline invoker 从按 `behaviorCount` 重复创建收敛为 binding 内复用
+ - 已收紧 CQRS / generator 回归测试的脆弱断言:日志断言改为语义匹配,precise runtime type lookup 回归改为锁定数组秩、外部类型查找与“未发射 fallback metadata”这些稳定语义
+ - 已为 dispatcher cache / context refresh / pipeline order 三组测试状态容器补齐并发保护,并将 `CqrsDispatcherCacheTests` 标记为 `NonParallelizable`,避免静态缓存与共享快照在并行测试中相互污染
- 中期上继续 `Phase 8` 主线:参考 `ai-libs/Mediator`,继续扩大 generator 覆盖,并选择下一个收益明确的 dispatch / invoker 反射收敛点
## 当前状态摘要
@@ -79,6 +82,11 @@ CQRS 迁移与收敛。
- latest reviewed commit 当前剩余 `3` 条 open AI review threads:`2` 条 Greptile、`1` 条 CodeRabbit
- 本地核对后确认 `dotnet-format` 仍只有 `Restore operation failed` 噪音,没有附带当前仍成立的文件级格式诊断
- 已按 review triage 修正 generator source preamble 的多实例 fallback 特性排版、移除死参数,并补强 mixed/direct fallback 发射回归断言与 XML 文档
+- `2026-04-30` 已重新执行 `$gframework-pr-review`:
+ - 当前分支对应 `PR #304`,状态为 `OPEN`
+ - latest reviewed commit 当前剩余 `7` 条 CodeRabbit nitpick 与 `2` 条 Greptile open threads,集中在测试脆弱断言、共享测试状态并发保护,以及 `CqrsDispatcher` 的缓存线程模型文档
+ - 本地核对后,已确认这些评论仍对应当前代码;MegaLinter 继续只暴露 `dotnet-format` 的 `Restore operation failed` 环境噪音,CTRF 汇总为 `2203/2203` passed
+ - 已在本地完成 follow-up:request pipeline invoker 改为 binding 级复用、共享测试状态切换到 `System.Threading.Lock` 保护、顺序测试改为受控记录接口、`CqrsDispatcherCacheTests` 标记为 `NonParallelizable`,并补齐相关 XML / 线程模型注释
- `2026-04-29` 已完成一轮 precise runtime type lookup 的数组回归补强:
- `GFramework.SourceGenerators.Tests` 已新增多维数组、交错数组、外部程序集隐藏元素类型三类回归
- 当前生成器在 precise runtime type lookup 下已稳定保留数组秩信息,并递归发射交错数组的 `MakeArrayType()` 链
diff --git a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
index eb37d88c..25dc1c6a 100644
--- a/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
+++ b/ai-plan/public/cqrs-rewrite/traces/cqrs-rewrite-migration-trace.md
@@ -1,5 +1,36 @@
# CQRS 重写迁移追踪
+## 2026-04-30
+
+### 阶段:PR #304 review follow-up 收敛(CQRS-REWRITE-RP-062)
+
+- 本轮使用 `$gframework-pr-review` 重新抓取当前分支 PR:
+ - 当前分支 `feat/cqrs-optimization` 对应 `PR #304`
+ - latest review 信号主要由 `7` 条 CodeRabbit nitpick 与 `2` 条 Greptile open threads 组成
+ - MegaLinter 仍只给出 `dotnet-format` 的 `Restore operation failed`,未附带当前仍成立的文件级格式问题;CTRF 汇总为 `2203/2203` passed
+- 本地复核后接受并收敛的 review follow-up:
+ - `GFramework.Cqrs/Internal/CqrsDispatcher.cs`
+ - 为 `_pipelineExecutors` 与 `RequestPipelineInvocation.GetContinuation(...)` 补齐线程模型与失败模式说明
+ - 将 request pipeline invoker 从“按 `behaviorCount` 重复创建”收敛为“binding 内创建一次、executor 缓存复用”
+ - `GFramework.Cqrs.Tests/Cqrs/*.cs`
+ - 将 `DispatcherPipelineContextRefreshState`、`DispatcherNotificationContextRefreshState`、`DispatcherStreamContextRefreshState` 与 `DispatcherPipelineOrderState` 切换为 `System.Threading.Lock` 保护的共享状态
+ - 将 pipeline 顺序记录从公开可变 `List` 收敛为 `Record(...)` + 快照只读访问
+ - 为 `CqrsDispatcherCacheTests` 添加 `[NonParallelizable]`,并补齐反射辅助方法的 XML `param` / `returns`
+ - 将 `CqrsRegistrationServiceTests` 的 debug 日志断言改为锁定语义片段而非整句文本
+ - 将 `CqrsHandlerRegistrarFallbackFailureTests` 的缓存字段诊断改为显式指出 `CqrsHandlerRegistrar` 耦合点
+ - `GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs`
+ - 将 precise runtime type lookup 的数组 / 外部泛型回归断言从局部变量名绑定改为稳定的语义片段断言
+- 验证过程与结果:
+ - 首次把多个 `dotnet` restore / test 并发跑在同一 worktree 时,`GFramework.Cqrs.Tests` 出现 `*.nuget.g.props already exists` 竞争;该失败属于本地并发 restore 冲突,不代表代码问题
+ - 串行重跑后确认:
+ - `dotnet build GFramework.Cqrs/GFramework.Cqrs.csproj -c Release`
+ - `dotnet test GFramework.SourceGenerators.Tests/GFramework.SourceGenerators.Tests.csproj -c Release --filter "FullyQualifiedName~CqrsHandlerRegistryGeneratorTests"`
+ - `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests|FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsRegistrationServiceTests|FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsHandlerRegistrarFallbackFailureTests"`
+ - 在第一次串行测试中暴露 `MA0158 Use System.Threading.Lock` warning 后,已同轮切换同步原语并准备重跑无 warning 验证
+- 结果:
+ - 本轮把仍成立的 PR review 评论全部收敛到本地代码或测试基础设施
+ - 下一步应以“重跑无 warning 验证 + 提交本轮 follow-up”为恢复入口,而不是继续扩写新的 CQRS 优化切片
+
## 2026-04-29
### 阶段:registrar fallback 失败分支回归(CQRS-REWRITE-RP-061)