diff --git a/src/NServiceBus.AcceptanceTests/Core/Conventions/When_scanning_an_assembly_containing_a_ref_struct_and_sagas_enabled.cs b/src/NServiceBus.AcceptanceTests/Core/Conventions/When_scanning_an_assembly_containing_a_ref_struct_and_sagas_enabled.cs new file mode 100644 index 00000000000..b4de3291180 --- /dev/null +++ b/src/NServiceBus.AcceptanceTests/Core/Conventions/When_scanning_an_assembly_containing_a_ref_struct_and_sagas_enabled.cs @@ -0,0 +1,57 @@ +namespace NServiceBus.AcceptanceTests.Core.Conventions +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using EndpointTemplates; + using NServiceBus.AcceptanceTesting.Customization; + using NUnit.Framework; + + public class When_scanning_an_assembly_containing_a_ref_struct_and_sagas_enabled : NServiceBusAcceptanceTest + { + [Test] + public void It_should_not_throw_an_exception() + => Assert.DoesNotThrowAsync( + () => Scenario.Define() + .WithEndpoint() + .Run() + ); + + // HINT: This will get picked up by the AssemblyRouteSource created by the routing call below + // Even though it is not a message type, it is still checked by passing it to conventions. + // The conventions added by Sagas were throwing an exception when passed a ref struct. + // See https://github.com/Particular/NServiceBus/issues/7179 for details. + ref struct RefStruct + { + } + + class EndpointWithASaga : EndpointConfigurationBuilder + { + public EndpointWithASaga() => EndpointSetup(cfg => cfg + .ConfigureTransport() + .Routing() + .RouteToEndpoint( + typeof(RefStruct).Assembly, + Conventions.EndpointNamingConvention(typeof(EndpointWithASaga)) + ) + ); + class RealSagaToSetUpConventions : Saga, IAmStartedByMessages + { + public Task Handle(SomeMessage message, IMessageHandlerContext context) => Task.CompletedTask; + + protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper) + => mapper.MapSaga(saga => saga.BusinessId).ToMessage(msg => msg.BusinessId); + + public class RealSagaToSetUpConventionsSagaData : ContainSagaData + { + public virtual Guid BusinessId { get; set; } + } + } + } + + public class SomeMessage : IMessage + { + public Guid BusinessId { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.Core/Sagas/Sagas.cs b/src/NServiceBus.Core/Sagas/Sagas.cs index 93b379f57f3..1bd51f9e474 100644 --- a/src/NServiceBus.Core/Sagas/Sagas.cs +++ b/src/NServiceBus.Core/Sagas/Sagas.cs @@ -98,6 +98,13 @@ static bool IsCompatible(Type t, Type source) static bool IsTypeATimeoutHandledByAnySaga(Type type, IEnumerable sagas) { + // MakeGenericType() throws an exception if passed a ref struct type + // Messages cannot be ref struct types + if (type.CustomAttributes.Any(a => string.Equals(a.AttributeType.FullName, "System.Runtime.CompilerServices.IsByRefLikeAttribute"))) + { + return false; + } + var timeoutHandler = typeof(IHandleTimeouts<>).MakeGenericType(type); var messageHandler = typeof(IHandleMessages<>).MakeGenericType(type);