diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs index 5c79175a..4530e537 100644 --- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs +++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs @@ -448,6 +448,126 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests Assert.That(results, Is.EqualTo([3, 4])); } + /// + /// 验证当 generated request invoker provider 的 descriptor 枚举抛出异常时, + /// registrar 会跳过 generated descriptor 预热并回退到反射路径。 + /// + [Test] + public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Request_Descriptor_Enumeration_Throws() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(ThrowingEnumeratingRequestInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingRequestInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false); + Assert.That(response, Is.EqualTo("runtime:payload")); + } + + /// + /// 验证当 generated stream invoker provider 的 descriptor 枚举抛出异常时, + /// registrar 会跳过 generated descriptor 预热并回退到反射建流路径。 + /// + [Test] + public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Stream_Descriptor_Enumeration_Throws() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(ThrowingEnumeratingStreamInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.ThrowingEnumeratingStreamInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false); + Assert.That(results, Is.EqualTo([3, 4])); + } + + /// + /// 验证当 request descriptor 枚举返回重复 request-response pair 时, + /// registrar 会稳定保留首个有效描述符,并忽略后续重复项。 + /// + [Test] + public async Task SendAsync_Should_Use_First_Generated_Request_Descriptor_When_Duplicates_Are_Enumerated() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(DuplicateEnumeratingRequestInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingRequestInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false); + Assert.That(response, Is.EqualTo("generated:payload")); + } + + /// + /// 验证当 stream descriptor 枚举返回重复 request-response pair 时, + /// registrar 会稳定保留首个有效描述符,并忽略后续重复项。 + /// + [Test] + public async Task CreateStream_Should_Use_First_Generated_Stream_Descriptor_When_Duplicates_Are_Enumerated() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(DuplicateEnumeratingStreamInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.DuplicateEnumeratingStreamInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false); + Assert.That(results, Is.EqualTo([30, 31])); + } + + /// + /// 验证当 request descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时, + /// registrar 会忽略该坏 descriptor,并继续回退到反射路径。 + /// + [Test] + public async Task SendAsync_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Request_Descriptor_Does_Not_Match_Provider() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(MismatchedEnumeratingRequestInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingRequestInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload")).ConfigureAwait(false); + Assert.That(response, Is.EqualTo("runtime:payload")); + } + + /// + /// 验证当 stream descriptor 枚举项与 provider 的 TryGetDescriptor 结果不一致时, + /// registrar 会忽略该坏 descriptor,并继续回退到反射建流路径。 + /// + [Test] + public async Task CreateStream_Should_Fall_Back_To_Runtime_Path_When_Enumerated_Stream_Descriptor_Does_Not_Match_Provider() + { + var generatedAssembly = CreateGeneratedAssembly( + typeof(MismatchedEnumeratingStreamInvokerProviderRegistry), + "GFramework.Cqrs.Tests.Cqrs.MismatchedEnumeratingStreamInvokerAssembly, Version=1.0.0.0"); + var container = new MicrosoftDiContainer(); + + CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object); + container.Freeze(); + + var context = new ArchitectureContext(container); + var results = await DrainAsync(context.CreateStream(new GeneratedStreamInvokerRequest(3))).ConfigureAwait(false); + Assert.That(results, Is.EqualTo([3, 4])); + } + /// /// 模拟返回实例 request invoker 方法的 generated registry。 /// @@ -860,6 +980,382 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests } } + /// + /// 模拟 descriptor 枚举阶段抛出异常的 request invoker provider。 + /// + private sealed class ThrowingEnumeratingRequestInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsRequestInvokerProvider, + IEnumeratesCqrsRequestInvokerDescriptors + { + private static readonly CqrsRequestInvokerDescriptor Descriptor = new( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsRequestInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string)) + { + descriptor = Descriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + throw new InvalidOperationException("request descriptors failed"); + } + } + + /// + /// 模拟 descriptor 枚举阶段抛出异常的 stream invoker provider。 + /// + private sealed class ThrowingEnumeratingStreamInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsStreamInvokerProvider, + IEnumeratesCqrsStreamInvokerDescriptors + { + private static readonly CqrsStreamInvokerDescriptor Descriptor = new( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsStreamInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int)) + { + descriptor = Descriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + throw new InvalidOperationException("stream descriptors failed"); + } + } + + /// + /// 模拟返回重复 request descriptor 条目的 generated registry。 + /// + private sealed class DuplicateEnumeratingRequestInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsRequestInvokerProvider, + IEnumeratesCqrsRequestInvokerDescriptors + { + private static readonly CqrsRequestInvokerDescriptor PrimaryDescriptor = new( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + private static readonly CqrsRequestInvokerDescriptor SecondaryDescriptor = new( + typeof(IRequestHandler), + typeof(DuplicateEnumeratingRequestInvokerProviderRegistry).GetMethod( + nameof(InvokeAlternativeGenerated), + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsRequestInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string)) + { + descriptor = PrimaryDescriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + return + [ + new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), PrimaryDescriptor), + new CqrsRequestInvokerDescriptorEntry(typeof(GeneratedRequestInvokerRequest), typeof(string), SecondaryDescriptor) + ]; + } + + private static ValueTask InvokeAlternativeGenerated( + object handler, + object request, + CancellationToken cancellationToken) + { + return ValueTask.FromResult("duplicate:payload"); + } + } + + /// + /// 模拟返回重复 stream descriptor 条目的 generated registry。 + /// + private sealed class DuplicateEnumeratingStreamInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsStreamInvokerProvider, + IEnumeratesCqrsStreamInvokerDescriptors + { + private static readonly CqrsStreamInvokerDescriptor PrimaryDescriptor = new( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + private static readonly CqrsStreamInvokerDescriptor SecondaryDescriptor = new( + typeof(IStreamRequestHandler), + typeof(DuplicateEnumeratingStreamInvokerProviderRegistry).GetMethod( + nameof(InvokeAlternativeGenerated), + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsStreamInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int)) + { + descriptor = PrimaryDescriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + return + [ + new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), PrimaryDescriptor), + new CqrsStreamInvokerDescriptorEntry(typeof(GeneratedStreamInvokerRequest), typeof(int), SecondaryDescriptor) + ]; + } + + private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken) + { + return new[] { 900, 901 }.ToAsyncEnumerable(); + } + } + + /// + /// 模拟枚举出的 request descriptor 与 provider 显式查询结果不一致的 generated registry。 + /// + private sealed class MismatchedEnumeratingRequestInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsRequestInvokerProvider, + IEnumeratesCqrsRequestInvokerDescriptors + { + private static readonly CqrsRequestInvokerDescriptor ProviderDescriptor = new( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + private static readonly CqrsRequestInvokerDescriptor EnumeratedDescriptor = new( + typeof(IRequestHandler), + typeof(MismatchedEnumeratingRequestInvokerProviderRegistry).GetMethod( + nameof(InvokeAlternativeGenerated), + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IRequestHandler), + typeof(GeneratedRequestInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsRequestInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedRequestInvokerRequest) && responseType == typeof(string)) + { + descriptor = ProviderDescriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + return + [ + new CqrsRequestInvokerDescriptorEntry( + typeof(GeneratedRequestInvokerRequest), + typeof(string), + EnumeratedDescriptor) + ]; + } + + private static ValueTask InvokeAlternativeGenerated( + object handler, + object request, + CancellationToken cancellationToken) + { + return ValueTask.FromResult("mismatched:payload"); + } + } + + /// + /// 模拟枚举出的 stream descriptor 与 provider 显式查询结果不一致的 generated registry。 + /// + private sealed class MismatchedEnumeratingStreamInvokerProviderRegistry : + ICqrsHandlerRegistry, + ICqrsStreamInvokerProvider, + IEnumeratesCqrsStreamInvokerDescriptors + { + private static readonly CqrsStreamInvokerDescriptor ProviderDescriptor = new( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerProviderRegistry).GetMethod( + "InvokeGenerated", + BindingFlags.NonPublic | BindingFlags.Static)!); + + private static readonly CqrsStreamInvokerDescriptor EnumeratedDescriptor = new( + typeof(IStreamRequestHandler), + typeof(MismatchedEnumeratingStreamInvokerProviderRegistry).GetMethod( + nameof(InvokeAlternativeGenerated), + BindingFlags.NonPublic | BindingFlags.Static)!); + + /// + public void Register(IServiceCollection services, ILogger logger) + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(logger); + + services.AddTransient( + typeof(IStreamRequestHandler), + typeof(GeneratedStreamInvokerRequestHandler)); + } + + /// + public bool TryGetDescriptor( + Type requestType, + Type responseType, + out CqrsStreamInvokerDescriptor? descriptor) + { + ArgumentNullException.ThrowIfNull(requestType); + ArgumentNullException.ThrowIfNull(responseType); + + if (requestType == typeof(GeneratedStreamInvokerRequest) && responseType == typeof(int)) + { + descriptor = ProviderDescriptor; + return true; + } + + descriptor = null; + return false; + } + + /// + public IReadOnlyList GetDescriptors() + { + return + [ + new CqrsStreamInvokerDescriptorEntry( + typeof(GeneratedStreamInvokerRequest), + typeof(int), + EnumeratedDescriptor) + ]; + } + + private static object InvokeAlternativeGenerated(object handler, object request, CancellationToken cancellationToken) + { + return new[] { 700, 701 }.ToAsyncEnumerable(); + } + } + /// /// 创建带有 generated request invoker registry 元数据的程序集替身。 /// diff --git a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs index a332927c..1b90edb8 100644 --- a/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs +++ b/GFramework.Cqrs/Internal/CqrsHandlerRegistrar.cs @@ -16,6 +16,13 @@ namespace GFramework.Cqrs.Internal; /// internal static class CqrsHandlerRegistrar { + /// + /// 描述 generated invoker descriptor 在 registrar 预热阶段使用的 request/response 类型对键。 + /// + /// 请求运行时类型。 + /// 响应运行时类型。 + private readonly record struct InvokerDescriptorKey(Type RequestType, Type ResponseType); + // 卸载安全的进程级缓存:程序集元数据只按弱键复用。 // 若程序集来自 collectible AssemblyLoadContext,被回收后会重新分析,而不会被静态缓存永久钉住。 private static readonly WeakKeyCache AssemblyMetadataCache = @@ -321,8 +328,48 @@ internal static class CqrsHandlerRegistrar if (provider is not IEnumeratesCqrsRequestInvokerDescriptors descriptorSource) return; - foreach (var descriptorEntry in descriptorSource.GetDescriptors()) + IReadOnlyList? descriptors; + try { + descriptors = descriptorSource.GetDescriptors(); + } + catch (Exception exception) + { + logger.Warn( + $"Failed to enumerate generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for request invokers: {exception.Message}"); + return; + } + + if (descriptors is null) + { + logger.Warn( + $"Ignoring generated CQRS request invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null."); + return; + } + + var registeredKeys = new HashSet(); + foreach (var descriptorEntry in descriptors) + { + if (descriptorEntry is null) + { + logger.Warn( + $"Ignoring null generated CQRS request invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}."); + continue; + } + + var descriptorKey = new InvokerDescriptorKey( + descriptorEntry.RequestType, + descriptorEntry.ResponseType); + if (!registeredKeys.Add(descriptorKey)) + { + logger.Warn( + $"Ignoring duplicate generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}."); + continue; + } + + if (!TryValidateEnumeratedRequestInvokerDescriptor(provider, descriptorEntry, assemblyName, logger)) + continue; + CqrsDispatcher.RegisterGeneratedRequestInvokerDescriptor( descriptorEntry.RequestType, descriptorEntry.ResponseType, @@ -376,8 +423,48 @@ internal static class CqrsHandlerRegistrar if (provider is not IEnumeratesCqrsStreamInvokerDescriptors descriptorSource) return; - foreach (var descriptorEntry in descriptorSource.GetDescriptors()) + IReadOnlyList? descriptors; + try { + descriptors = descriptorSource.GetDescriptors(); + } + catch (Exception exception) + { + logger.Warn( + $"Failed to enumerate generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName}. Falling back to runtime reflection for stream invokers: {exception.Message}"); + return; + } + + if (descriptors is null) + { + logger.Warn( + $"Ignoring generated CQRS stream invoker descriptors from provider {provider.GetType().FullName} in assembly {assemblyName} because GetDescriptors() returned null."); + return; + } + + var registeredKeys = new HashSet(); + foreach (var descriptorEntry in descriptors) + { + if (descriptorEntry is null) + { + logger.Warn( + $"Ignoring null generated CQRS stream invoker descriptor entry from provider {provider.GetType().FullName} in assembly {assemblyName}."); + continue; + } + + var descriptorKey = new InvokerDescriptorKey( + descriptorEntry.RequestType, + descriptorEntry.ResponseType); + if (!registeredKeys.Add(descriptorKey)) + { + logger.Warn( + $"Ignoring duplicate generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName}."); + continue; + } + + if (!TryValidateEnumeratedStreamInvokerDescriptor(provider, descriptorEntry, assemblyName, logger)) + continue; + CqrsDispatcher.RegisterGeneratedStreamInvokerDescriptor( descriptorEntry.RequestType, descriptorEntry.ResponseType, @@ -387,6 +474,96 @@ internal static class CqrsHandlerRegistrar } } + /// + /// 校验 request descriptor 枚举项是否与 provider 的显式查询结果保持一致。 + /// + /// 当前正在预热的 request invoker provider。 + /// 当前枚举到的描述符条目。 + /// 当前程序集的稳定名称。 + /// 日志记录器。 + /// 当该枚举项可安全写入 dispatcher 缓存时返回 ;否则返回 + private static bool TryValidateEnumeratedRequestInvokerDescriptor( + ICqrsRequestInvokerProvider provider, + CqrsRequestInvokerDescriptorEntry descriptorEntry, + string assemblyName, + ILogger logger) + { + try + { + if (!provider.TryGetDescriptor( + descriptorEntry.RequestType, + descriptorEntry.ResponseType, + out var resolvedDescriptor) || + resolvedDescriptor is null) + { + logger.Warn( + $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor."); + return false; + } + + if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) || + resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType) + { + logger.Warn( + $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor."); + return false; + } + + return true; + } + catch (Exception exception) + { + logger.Warn( + $"Ignoring generated CQRS request invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}"); + return false; + } + } + + /// + /// 校验 stream descriptor 枚举项是否与 provider 的显式查询结果保持一致。 + /// + /// 当前正在预热的 stream invoker provider。 + /// 当前枚举到的描述符条目。 + /// 当前程序集的稳定名称。 + /// 日志记录器。 + /// 当该枚举项可安全写入 dispatcher 缓存时返回 ;否则返回 + private static bool TryValidateEnumeratedStreamInvokerDescriptor( + ICqrsStreamInvokerProvider provider, + CqrsStreamInvokerDescriptorEntry descriptorEntry, + string assemblyName, + ILogger logger) + { + try + { + if (!provider.TryGetDescriptor( + descriptorEntry.RequestType, + descriptorEntry.ResponseType, + out var resolvedDescriptor) || + resolvedDescriptor is null) + { + logger.Warn( + $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor did not return a matching descriptor."); + return false; + } + + if (!ReferenceEquals(resolvedDescriptor.InvokerMethod, descriptorEntry.Descriptor.InvokerMethod) || + resolvedDescriptor.HandlerType != descriptorEntry.Descriptor.HandlerType) + { + logger.Warn( + $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because the enumerated descriptor does not match TryGetDescriptor."); + return false; + } + + return true; + } + catch (Exception exception) + { + logger.Warn( + $"Ignoring generated CQRS stream invoker descriptor for {descriptorEntry.RequestType.FullName} -> {descriptorEntry.ResponseType.FullName} from provider {provider.GetType().FullName} in assembly {assemblyName} because TryGetDescriptor threw: {exception.Message}"); + return false; + } + } + /// /// 将 generated registry 的 fallback 元数据转换为统一的注册结果,并记录下一阶段是定向补扫还是整程序集扫描。 ///