From 3eb41b328b85737d071f8b29afcdcff1b3eda85b Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 24 Apr 2024 20:20:00 -0500 Subject: [PATCH 01/27] Fixed #5125 - Added overloads for the Entity Framework outbox to disable schema caching when using multiple DbContext's --- ...yFrameworkOutboxConfigurationExtensions.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Configuration/EntityFrameworkOutboxConfigurationExtensions.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Configuration/EntityFrameworkOutboxConfigurationExtensions.cs index 9c49e65b39f..ae02262b725 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Configuration/EntityFrameworkOutboxConfigurationExtensions.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/Configuration/EntityFrameworkOutboxConfigurationExtensions.cs @@ -87,6 +87,19 @@ public static IEntityFrameworkOutboxConfigurator UseSqlServer(this IEntityFramew return configurator; } + /// + /// Configure the outbox for use with SQL Server + /// + /// + /// Set to false when using multiple DbContexts + /// + public static IEntityFrameworkOutboxConfigurator UseSqlServer(this IEntityFrameworkOutboxConfigurator configurator, bool enableSchemaCaching) + { + configurator.LockStatementProvider = new SqlServerLockStatementProvider(enableSchemaCaching); + + return configurator; + } + /// /// Configure the outbox for use with Postgres /// @@ -99,6 +112,19 @@ public static IEntityFrameworkOutboxConfigurator UsePostgres(this IEntityFramewo return configurator; } + /// + /// Configure the outbox for use with Postgres + /// + /// + /// Set to false when using multiple DbContexts + /// + public static IEntityFrameworkOutboxConfigurator UsePostgres(this IEntityFrameworkOutboxConfigurator configurator, bool enableSchemaCaching) + { + configurator.LockStatementProvider = new PostgresLockStatementProvider(enableSchemaCaching); + + return configurator; + } + /// /// Configure the outbox for use with MySQL /// @@ -111,6 +137,19 @@ public static IEntityFrameworkOutboxConfigurator UseMySql(this IEntityFrameworkO return configurator; } + /// + /// Configure the outbox for use with MySQL + /// + /// + /// Set to false when using multiple DbContexts + /// + public static IEntityFrameworkOutboxConfigurator UseMySql(this IEntityFrameworkOutboxConfigurator configurator, bool enableSchemaCaching) + { + configurator.LockStatementProvider = new MySqlLockStatementProvider(enableSchemaCaching); + + return configurator; + } + /// /// Configure the outbox for use with SQLite /// @@ -123,6 +162,19 @@ public static IEntityFrameworkOutboxConfigurator UseSqlite(this IEntityFramework return configurator; } + /// + /// Configure the outbox for use with SQLite + /// + /// + /// Set to false when using multiple DbContexts + /// + public static IEntityFrameworkOutboxConfigurator UseSqlite(this IEntityFrameworkOutboxConfigurator configurator, bool enableSchemaCaching) + { + configurator.LockStatementProvider = new SqliteLockStatementProvider(enableSchemaCaching); + + return configurator; + } + /// /// Adds all three entities (, , and ) /// to the DbContext. If this method is used, the , , and From 7997da14badd2c92f79602fe1107f11feeeb96bc Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 24 Apr 2024 20:26:51 -0500 Subject: [PATCH 02/27] Related to #5114 - mute the InvalidOperationException error from PGSQL when a concurrent update is not allowed. --- .../BusOutboxDeliveryService.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/BusOutboxDeliveryService.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/BusOutboxDeliveryService.cs index bd20c983fbe..d5762c2f14a 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/BusOutboxDeliveryService.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/BusOutboxDeliveryService.cs @@ -83,6 +83,10 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) catch (DbUpdateConcurrencyException) { } + catch (InvalidOperationException exception) when (exception.InnerException != null + && exception.InnerException.Message.Contains("concurrent update")) + { + } catch (Exception exception) { _logger.LogError(exception, "ProcessMessageBatch faulted"); @@ -143,6 +147,11 @@ async Task Execute() { throw; } + catch (InvalidOperationException exception) when (exception.InnerException != null + && exception.InnerException.Message.Contains("concurrent update")) + { + throw; + } catch (Exception) { await RollbackTransaction(transaction).ConfigureAwait(false); From 7a28c393ea26ca91d6e7e1899929a3e865564fc6 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 25 Apr 2024 09:16:52 -0500 Subject: [PATCH 03/27] Related to #5060 - handling of SQL Server instance/host/port in all scenarios --- .../SqlServer/SqlServerSqlHostSettings.cs | 68 ++++++++++++------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs index 57a3c1aa86c..8607d4e6400 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs @@ -1,6 +1,7 @@ namespace MassTransit.SqlTransport.SqlServer { using System; + using System.Text; using Configuration; using Microsoft.Data.SqlClient; @@ -26,14 +27,7 @@ public SqlServerSqlHostSettings(string connectionString) public SqlServerSqlHostSettings(SqlTransportOptions options) { - var hostSegments = options.Host?.Split('\\'); - if (hostSegments?.Length == 2) - { - Host = hostSegments[0].Trim(); - InstanceName = hostSegments[1].Trim(); - } - else - Host = options.Host; + ParseHost(options.Host); Database = options.Database; Username = options.Username; @@ -44,26 +38,13 @@ public SqlServerSqlHostSettings(SqlTransportOptions options) Port = options.Port.Value; } - public string? Role { get; set; } - - public string? AdminUsername { get; set; } - public string? AdminPassword { get; set; } - public string? ConnectionString { set { var builder = new SqlConnectionStringBuilder(value); - var split = builder.DataSource.Split(','); - if (split.Length == 2) - { - Host = split[0].Trim(); - if (int.TryParse(split[1].Trim(), out var port)) - Port = port; - } - else - Host = builder.DataSource; + ParseDataSource(builder.DataSource); Username = builder.UserID; Password = builder.Password; @@ -83,7 +64,7 @@ public string GetConnectionString() { var builder = _builder ??= new SqlConnectionStringBuilder { - DataSource = Host, + DataSource = FormatDataSource(), UserID = Username, Password = Password, InitialCatalog = Database, @@ -92,5 +73,46 @@ public string GetConnectionString() return builder.ToString(); } + + void ParseDataSource(string? source) + { + var split = source?.Split(','); + if (split?.Length == 2) + { + ParseHost(split[0].Trim()); + if (int.TryParse(split[1].Trim(), out var port)) + Port = port; + } + else + ParseHost(source); + } + + void ParseHost(string? host) + { + var hostSegments = host?.Split('\\'); + if (hostSegments?.Length == 2) + { + Host = hostSegments[0].Trim(); + InstanceName = hostSegments[1].Trim(); + } + else + Host = host; + } + + string? FormatDataSource() + { + if (string.IsNullOrWhiteSpace(Host)) + return null; + + var sb = new StringBuilder(); + sb.Append(Host); + if (!string.IsNullOrWhiteSpace(InstanceName)) + sb.Append('\\').Append(InstanceName); + + if (Port.HasValue) + sb.Append(',').Append(Port.Value); + + return sb.ToString(); + } } } From af7a812d68c8dd4fd67a3e9be1c019ff8c7fdefc Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 25 Apr 2024 12:39:51 -0500 Subject: [PATCH 04/27] Fixed #5130 - Changed SessionId and PartitionKey filters on Azure Service Bus to check if the context is a service bus context. --- ...rtitionKeyMessageSendTopologyConvention.cs | 1 + .../SessionIdMessageSendTopologyConvention.cs | 1 + .../SetPartitionKeyMessageSendTopology.cs | 51 ------------------- .../SetSessionIdMessageSendTopology.cs | 51 ------------------- .../Middleware/ServiceBusSendContextFilter.cs | 27 ++++++++++ .../SetPartitionKeyMessageSendTopology.cs | 27 ++++++++++ .../SetSessionIdMessageSendTopology.cs | 27 ++++++++++ 7 files changed, 83 insertions(+), 102 deletions(-) delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetPartitionKeyMessageSendTopology.cs delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetSessionIdMessageSendTopology.cs create mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/ServiceBusSendContextFilter.cs create mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs create mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetSessionIdMessageSendTopology.cs diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs index 8eeca1092be..9115e3f0ca1 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs @@ -1,6 +1,7 @@ namespace MassTransit.AzureServiceBusTransport.Configuration { using MassTransit.Configuration; + using Topology; public class PartitionKeyMessageSendTopologyConvention : diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SessionIdMessageSendTopologyConvention.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SessionIdMessageSendTopologyConvention.cs index 3a6ae42cac6..856d104acb5 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SessionIdMessageSendTopologyConvention.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SessionIdMessageSendTopologyConvention.cs @@ -1,6 +1,7 @@ namespace MassTransit.AzureServiceBusTransport.Configuration { using MassTransit.Configuration; + using Topology; public class SessionIdMessageSendTopologyConvention : diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetPartitionKeyMessageSendTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetPartitionKeyMessageSendTopology.cs deleted file mode 100644 index d4d30d818b2..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetPartitionKeyMessageSendTopology.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration -{ - using System; - using System.Threading.Tasks; - using MassTransit.Configuration; - using Middleware; - - - public class SetPartitionKeyMessageSendTopology : - IMessageSendTopology - where T : class - { - readonly IFilter> _filter; - - public SetPartitionKeyMessageSendTopology(IMessagePartitionKeyFormatter partitionKeyFormatter) - { - if (partitionKeyFormatter == null) - throw new ArgumentNullException(nameof(partitionKeyFormatter)); - - _filter = new Proxy(new SetPartitionKeyFilter(partitionKeyFormatter)); - } - - public void Apply(ITopologyPipeBuilder> builder) - { - builder.AddFilter(_filter); - } - - - class Proxy : - IFilter> - { - readonly IFilter> _filter; - - public Proxy(IFilter> filter) - { - _filter = filter; - } - - public Task Send(SendContext context, IPipe> next) - { - var serviceBusSendContext = context.GetPayload>(); - - return _filter.Send(serviceBusSendContext, next); - } - - public void Probe(ProbeContext context) - { - } - } - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetSessionIdMessageSendTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetSessionIdMessageSendTopology.cs deleted file mode 100644 index 7e9bf5028d3..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/SetSessionIdMessageSendTopology.cs +++ /dev/null @@ -1,51 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration -{ - using System; - using System.Threading.Tasks; - using MassTransit.Configuration; - using Middleware; - - - public class SetSessionIdMessageSendTopology : - IMessageSendTopology - where T : class - { - readonly IFilter> _filter; - - public SetSessionIdMessageSendTopology(IMessageSessionIdFormatter sessionIdFormatter) - { - if (sessionIdFormatter == null) - throw new ArgumentNullException(nameof(sessionIdFormatter)); - - _filter = new Proxy(new SetSessionIdFilter(sessionIdFormatter)); - } - - public void Apply(ITopologyPipeBuilder> builder) - { - builder.AddFilter(_filter); - } - - - class Proxy : - IFilter> - { - readonly IFilter> _filter; - - public Proxy(IFilter> filter) - { - _filter = filter; - } - - public Task Send(SendContext context, IPipe> next) - { - var sendContext = context.GetPayload>(); - - return _filter.Send(sendContext, next); - } - - public void Probe(ProbeContext context) - { - } - } - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/ServiceBusSendContextFilter.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/ServiceBusSendContextFilter.cs new file mode 100644 index 00000000000..bee7cc64842 --- /dev/null +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/ServiceBusSendContextFilter.cs @@ -0,0 +1,27 @@ +namespace MassTransit.AzureServiceBusTransport.Middleware; + +using System.Threading.Tasks; + + +public class ServiceBusSendContextFilter : + IFilter> + where T : class +{ + readonly IFilter> _filter; + + public ServiceBusSendContextFilter(IFilter> filter) + { + _filter = filter; + } + + public Task Send(SendContext context, IPipe> next) + { + return context.TryGetPayload(out ServiceBusSendContext serviceBusSendContext) + ? _filter.Send(serviceBusSendContext, next) + : next.Send(context); + } + + public void Probe(ProbeContext context) + { + } +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs new file mode 100644 index 00000000000..b9f00f00af2 --- /dev/null +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs @@ -0,0 +1,27 @@ +namespace MassTransit.AzureServiceBusTransport.Topology +{ + using System; + using MassTransit.Configuration; + using Middleware; + + + public class SetPartitionKeyMessageSendTopology : + IMessageSendTopology + where T : class + { + readonly IFilter> _filter; + + public SetPartitionKeyMessageSendTopology(IMessagePartitionKeyFormatter partitionKeyFormatter) + { + if (partitionKeyFormatter == null) + throw new ArgumentNullException(nameof(partitionKeyFormatter)); + + _filter = new ServiceBusSendContextFilter(new SetPartitionKeyFilter(partitionKeyFormatter)); + } + + public void Apply(ITopologyPipeBuilder> builder) + { + builder.AddFilter(_filter); + } + } +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetSessionIdMessageSendTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetSessionIdMessageSendTopology.cs new file mode 100644 index 00000000000..22c3a020c2b --- /dev/null +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetSessionIdMessageSendTopology.cs @@ -0,0 +1,27 @@ +namespace MassTransit.AzureServiceBusTransport.Topology +{ + using System; + using MassTransit.Configuration; + using Middleware; + + + public class SetSessionIdMessageSendTopology : + IMessageSendTopology + where T : class + { + readonly IFilter> _filter; + + public SetSessionIdMessageSendTopology(IMessageSessionIdFormatter sessionIdFormatter) + { + if (sessionIdFormatter == null) + throw new ArgumentNullException(nameof(sessionIdFormatter)); + + _filter = new ServiceBusSendContextFilter(new SetSessionIdFilter(sessionIdFormatter)); + } + + public void Apply(ITopologyPipeBuilder> builder) + { + builder.AddFilter(_filter); + } + } +} From e7c59ea34ea07f1a88df106923f54ae48debaf16 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 25 Apr 2024 13:50:32 -0500 Subject: [PATCH 05/27] Standardized the topology for partition key across service bus, event hub, and the SQL transport --- .../PartitionKeyConventionExtensions.cs} | 19 +++++------ .../RoutingKeyConventionExtensions.cs | 11 +++---- .../Middleware/SetPartitionKeyFilter.cs | 33 +++++++++++++++++++ ...rtitionKeyMessageSendTopologyConvention.cs | 4 +-- .../IPartitionKeySendTopologyConvention.cs | 7 ++++ ...rtitionKeyMessageSendTopologyConvention.cs | 5 ++- .../PartitionKeySendTopologyConvention.cs | 9 +---- .../SetPartitionKeyMessageSendTopology.cs | 26 +++++++++++++++ .../DelegatePartitionKeyFormatter.cs | 2 +- .../IMessagePartitionKeyFormatter.cs | 2 +- .../Transports/IPartitionKeyFormatter.cs | 13 ++++++++ .../MessagePartitionKeyFormatter.cs | 2 +- .../IPartitionKeySendTopologyConvention.cs | 15 --------- .../EmptyPartitionKeyFormatter.cs | 11 ------- .../IPartitionKeyFormatter.cs | 8 ----- .../Middleware/SetPartitionKeyFilter.cs | 32 ------------------ .../SetPartitionKeyMessageSendTopology.cs | 27 --------------- .../EventHubSendContext.cs | 4 +-- 18 files changed, 103 insertions(+), 127 deletions(-) rename src/{Transports/MassTransit.Azure.ServiceBus.Core/Configuration/Topology/ServiceBusPartitionKeyConventionExtensions.cs => MassTransit/Configuration/PartitionKeyConventionExtensions.cs} (85%) create mode 100644 src/MassTransit/Middleware/SetPartitionKeyFilter.cs rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology => MassTransit/Topology/Configuration}/IPartitionKeyMessageSendTopologyConvention.cs (75%) create mode 100644 src/MassTransit/Topology/Configuration/IPartitionKeySendTopologyConvention.cs rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology => MassTransit/Topology/Configuration}/PartitionKeyMessageSendTopologyConvention.cs (92%) rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology => MassTransit/Topology/Configuration}/PartitionKeySendTopologyConvention.cs (81%) create mode 100644 src/MassTransit/Topology/Configuration/SetPartitionKeyMessageSendTopology.cs rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport => MassTransit/Transports}/DelegatePartitionKeyFormatter.cs (91%) rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport => MassTransit/Transports}/IMessagePartitionKeyFormatter.cs (77%) create mode 100644 src/MassTransit/Transports/IPartitionKeyFormatter.cs rename src/{Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport => MassTransit/Transports}/MessagePartitionKeyFormatter.cs (90%) delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeySendTopologyConvention.cs delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/EmptyPartitionKeyFormatter.cs delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IPartitionKeyFormatter.cs delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/SetPartitionKeyFilter.cs delete mode 100644 src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/Topology/ServiceBusPartitionKeyConventionExtensions.cs b/src/MassTransit/Configuration/PartitionKeyConventionExtensions.cs similarity index 85% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/Topology/ServiceBusPartitionKeyConventionExtensions.cs rename to src/MassTransit/Configuration/PartitionKeyConventionExtensions.cs index 4e087189087..c18a7129360 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/Topology/ServiceBusPartitionKeyConventionExtensions.cs +++ b/src/MassTransit/Configuration/PartitionKeyConventionExtensions.cs @@ -1,22 +1,21 @@ -namespace MassTransit +namespace MassTransit { using System; - using AzureServiceBusTransport; - using AzureServiceBusTransport.Configuration; + using Configuration; + using Transports; - public static class ServiceBusPartitionKeyConventionExtensions + public static class PartitionKeyConventionExtensions { public static void UsePartitionKeyFormatter(this IMessageSendTopologyConfigurator configurator, IMessagePartitionKeyFormatter formatter) where T : class { - configurator.UpdateConvention>( - update => - { - update.SetFormatter(formatter); + configurator.UpdateConvention>(update => + { + update.SetFormatter(formatter); - return update; - }); + return update; + }); } /// diff --git a/src/MassTransit/Configuration/RoutingKeyConventionExtensions.cs b/src/MassTransit/Configuration/RoutingKeyConventionExtensions.cs index 96cbbbee891..d5fcdc61bcc 100644 --- a/src/MassTransit/Configuration/RoutingKeyConventionExtensions.cs +++ b/src/MassTransit/Configuration/RoutingKeyConventionExtensions.cs @@ -10,13 +10,12 @@ public static class RoutingKeyConventionExtensions public static void UseRoutingKeyFormatter(this IMessageSendTopologyConfigurator configurator, IMessageRoutingKeyFormatter formatter) where T : class { - configurator.UpdateConvention>( - update => - { - update.SetFormatter(formatter); + configurator.UpdateConvention>(update => + { + update.SetFormatter(formatter); - return update; - }); + return update; + }); } /// diff --git a/src/MassTransit/Middleware/SetPartitionKeyFilter.cs b/src/MassTransit/Middleware/SetPartitionKeyFilter.cs new file mode 100644 index 00000000000..4fc485c397b --- /dev/null +++ b/src/MassTransit/Middleware/SetPartitionKeyFilter.cs @@ -0,0 +1,33 @@ +namespace MassTransit.Middleware +{ + using System.Threading.Tasks; + using Transports; + + + public class SetPartitionKeyFilter : + IFilter> + where TMessage : class + { + readonly IMessagePartitionKeyFormatter _routingKeyFormatter; + + public SetPartitionKeyFilter(IMessagePartitionKeyFormatter routingKeyFormatter) + { + _routingKeyFormatter = routingKeyFormatter; + } + + public Task Send(SendContext context, IPipe> next) + { + var routingKey = _routingKeyFormatter.FormatPartitionKey(context); + + if (context.TryGetPayload(out PartitionKeySendContext routingKeySendContext)) + routingKeySendContext.PartitionKey = routingKey; + + return next.Send(context); + } + + public void Probe(ProbeContext context) + { + context.CreateFilterScope("setPartitionKey"); + } + } +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeyMessageSendTopologyConvention.cs b/src/MassTransit/Topology/Configuration/IPartitionKeyMessageSendTopologyConvention.cs similarity index 75% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeyMessageSendTopologyConvention.cs rename to src/MassTransit/Topology/Configuration/IPartitionKeyMessageSendTopologyConvention.cs index 4d7cda26e7f..e7a3ee49501 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeyMessageSendTopologyConvention.cs +++ b/src/MassTransit/Topology/Configuration/IPartitionKeyMessageSendTopologyConvention.cs @@ -1,6 +1,6 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration +namespace MassTransit.Configuration { - using MassTransit.Configuration; + using Transports; public interface IPartitionKeyMessageSendTopologyConvention : diff --git a/src/MassTransit/Topology/Configuration/IPartitionKeySendTopologyConvention.cs b/src/MassTransit/Topology/Configuration/IPartitionKeySendTopologyConvention.cs new file mode 100644 index 00000000000..7bc14d45247 --- /dev/null +++ b/src/MassTransit/Topology/Configuration/IPartitionKeySendTopologyConvention.cs @@ -0,0 +1,7 @@ +namespace MassTransit.Configuration +{ + public interface IPartitionKeySendTopologyConvention : + ISendTopologyConvention + { + } +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs b/src/MassTransit/Topology/Configuration/PartitionKeyMessageSendTopologyConvention.cs similarity index 92% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs rename to src/MassTransit/Topology/Configuration/PartitionKeyMessageSendTopologyConvention.cs index 9115e3f0ca1..ead54a4cb74 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeyMessageSendTopologyConvention.cs +++ b/src/MassTransit/Topology/Configuration/PartitionKeyMessageSendTopologyConvention.cs @@ -1,7 +1,6 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration +namespace MassTransit.Configuration { - using MassTransit.Configuration; - using Topology; + using Transports; public class PartitionKeyMessageSendTopologyConvention : diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeySendTopologyConvention.cs b/src/MassTransit/Topology/Configuration/PartitionKeySendTopologyConvention.cs similarity index 81% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeySendTopologyConvention.cs rename to src/MassTransit/Topology/Configuration/PartitionKeySendTopologyConvention.cs index e0e08e27290..4412585b24c 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/PartitionKeySendTopologyConvention.cs +++ b/src/MassTransit/Topology/Configuration/PartitionKeySendTopologyConvention.cs @@ -1,8 +1,5 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration +namespace MassTransit.Configuration { - using MassTransit.Configuration; - - public class PartitionKeySendTopologyConvention : IPartitionKeySendTopologyConvention { @@ -10,8 +7,6 @@ public class PartitionKeySendTopologyConvention : public PartitionKeySendTopologyConvention() { - DefaultFormatter = new EmptyPartitionKeyFormatter(); - _cache = new TopologyConventionCache(typeof(IPartitionKeyMessageSendTopologyConvention<>), new Factory()); } @@ -20,8 +15,6 @@ bool IMessageSendTopologyConvention.TryGetMessageSendTopologyConvention(out I return _cache.GetOrAdd>().TryGetMessageSendTopologyConvention(out convention); } - public IPartitionKeyFormatter DefaultFormatter { get; set; } - class Factory : IConventionTypeFactory diff --git a/src/MassTransit/Topology/Configuration/SetPartitionKeyMessageSendTopology.cs b/src/MassTransit/Topology/Configuration/SetPartitionKeyMessageSendTopology.cs new file mode 100644 index 00000000000..1d5c97ec475 --- /dev/null +++ b/src/MassTransit/Topology/Configuration/SetPartitionKeyMessageSendTopology.cs @@ -0,0 +1,26 @@ +namespace MassTransit.Configuration; + +using System; +using Middleware; +using Transports; + + +public class SetPartitionKeyMessageSendTopology : + IMessageSendTopology + where TMessage : class +{ + readonly IFilter> _filter; + + public SetPartitionKeyMessageSendTopology(IMessagePartitionKeyFormatter partitionKeyFormatter) + { + if (partitionKeyFormatter == null) + throw new ArgumentNullException(nameof(partitionKeyFormatter)); + + _filter = new SetPartitionKeyFilter(partitionKeyFormatter); + } + + public void Apply(ITopologyPipeBuilder> builder) + { + builder.AddFilter(_filter); + } +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/DelegatePartitionKeyFormatter.cs b/src/MassTransit/Transports/DelegatePartitionKeyFormatter.cs similarity index 91% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/DelegatePartitionKeyFormatter.cs rename to src/MassTransit/Transports/DelegatePartitionKeyFormatter.cs index 2648d89acd5..10a4a1751e9 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/DelegatePartitionKeyFormatter.cs +++ b/src/MassTransit/Transports/DelegatePartitionKeyFormatter.cs @@ -1,4 +1,4 @@ -namespace MassTransit.AzureServiceBusTransport +namespace MassTransit.Transports { using System; diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IMessagePartitionKeyFormatter.cs b/src/MassTransit/Transports/IMessagePartitionKeyFormatter.cs similarity index 77% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IMessagePartitionKeyFormatter.cs rename to src/MassTransit/Transports/IMessagePartitionKeyFormatter.cs index d9e8c60ab9b..1b3473c7ab2 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IMessagePartitionKeyFormatter.cs +++ b/src/MassTransit/Transports/IMessagePartitionKeyFormatter.cs @@ -1,4 +1,4 @@ -namespace MassTransit.AzureServiceBusTransport +namespace MassTransit.Transports { public interface IMessagePartitionKeyFormatter where TMessage : class diff --git a/src/MassTransit/Transports/IPartitionKeyFormatter.cs b/src/MassTransit/Transports/IPartitionKeyFormatter.cs new file mode 100644 index 00000000000..a4355f60161 --- /dev/null +++ b/src/MassTransit/Transports/IPartitionKeyFormatter.cs @@ -0,0 +1,13 @@ +namespace MassTransit.Transports; + +public interface IPartitionKeyFormatter +{ + /// + /// Format the partition key to be used by the transport, if supported + /// + /// The message type + /// The message send context + /// The routing key to specify in the transport + string FormatPartitionKey(SendContext context) + where T : class; +} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/MessagePartitionKeyFormatter.cs b/src/MassTransit/Transports/MessagePartitionKeyFormatter.cs similarity index 90% rename from src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/MessagePartitionKeyFormatter.cs rename to src/MassTransit/Transports/MessagePartitionKeyFormatter.cs index b55b50448c3..7a5cb29ef77 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/MessagePartitionKeyFormatter.cs +++ b/src/MassTransit/Transports/MessagePartitionKeyFormatter.cs @@ -1,4 +1,4 @@ -namespace MassTransit.AzureServiceBusTransport +namespace MassTransit.Transports { public class MessagePartitionKeyFormatter : IMessagePartitionKeyFormatter diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeySendTopologyConvention.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeySendTopologyConvention.cs deleted file mode 100644 index 170b225803a..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Configuration/Topology/IPartitionKeySendTopologyConvention.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport.Configuration -{ - using MassTransit.Configuration; - - - public interface IPartitionKeySendTopologyConvention : - ISendTopologyConvention - { - /// - /// The default, non-message specific routing key formatter used by messages - /// when no specific convention has been specified. - /// - IPartitionKeyFormatter DefaultFormatter { get; set; } - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/EmptyPartitionKeyFormatter.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/EmptyPartitionKeyFormatter.cs deleted file mode 100644 index aebc4592894..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/EmptyPartitionKeyFormatter.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport -{ - public class EmptyPartitionKeyFormatter : - IPartitionKeyFormatter - { - string IPartitionKeyFormatter.FormatPartitionKey(SendContext context) - { - return null; - } - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IPartitionKeyFormatter.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IPartitionKeyFormatter.cs deleted file mode 100644 index f8020b842dc..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/IPartitionKeyFormatter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport -{ - public interface IPartitionKeyFormatter - { - string FormatPartitionKey(SendContext context) - where T : class; - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/SetPartitionKeyFilter.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/SetPartitionKeyFilter.cs deleted file mode 100644 index 8534fa668d7..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Middleware/SetPartitionKeyFilter.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport.Middleware -{ - using System.Threading.Tasks; - - - public class SetPartitionKeyFilter : - IFilter> - where T : class - { - readonly IMessagePartitionKeyFormatter _partitionKeyFormatter; - - public SetPartitionKeyFilter(IMessagePartitionKeyFormatter partitionKeyFormatter) - { - _partitionKeyFormatter = partitionKeyFormatter; - } - - public Task Send(ServiceBusSendContext context, IPipe> next) - { - var partitionKey = _partitionKeyFormatter.FormatPartitionKey(context); - - if (!string.IsNullOrWhiteSpace(partitionKey)) - context.PartitionKey = partitionKey; - - return next.Send(context); - } - - public void Probe(ProbeContext context) - { - context.CreateFilterScope("setPartitionKey"); - } - } -} diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs deleted file mode 100644 index b9f00f00af2..00000000000 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/SetPartitionKeyMessageSendTopology.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace MassTransit.AzureServiceBusTransport.Topology -{ - using System; - using MassTransit.Configuration; - using Middleware; - - - public class SetPartitionKeyMessageSendTopology : - IMessageSendTopology - where T : class - { - readonly IFilter> _filter; - - public SetPartitionKeyMessageSendTopology(IMessagePartitionKeyFormatter partitionKeyFormatter) - { - if (partitionKeyFormatter == null) - throw new ArgumentNullException(nameof(partitionKeyFormatter)); - - _filter = new ServiceBusSendContextFilter(new SetPartitionKeyFilter(partitionKeyFormatter)); - } - - public void Apply(ITopologyPipeBuilder> builder) - { - builder.AddFilter(_filter); - } - } -} diff --git a/src/Transports/MassTransit.EventHubIntegration/EventHubSendContext.cs b/src/Transports/MassTransit.EventHubIntegration/EventHubSendContext.cs index d18b59d9934..cf8fc04d355 100644 --- a/src/Transports/MassTransit.EventHubIntegration/EventHubSendContext.cs +++ b/src/Transports/MassTransit.EventHubIntegration/EventHubSendContext.cs @@ -1,10 +1,10 @@ namespace MassTransit { public interface EventHubSendContext : - SendContext + SendContext, + PartitionKeySendContext { string PartitionId { get; set; } - string PartitionKey { get; set; } } From 2af9f8f21786863934ad8021d6e11e18bb6b4d49 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 25 Apr 2024 16:06:13 -0500 Subject: [PATCH 06/27] Package updates, consistent use of language version across all src/test projects. --- Directory.Packages.props | 16 ++++++++-------- tests/Directory.Build.props | 1 - ...assTransit.Azure.ServiceBus.Core.Tests.csproj | 1 - .../MassTransit.Benchmark.csproj | 1 - .../MassTransit.EventHubIntegration.Tests.csproj | 1 - .../MassTransit.KafkaIntegration.Tests.csproj | 1 - .../MassTransit.MartenIntegration.Tests.csproj | 1 - .../MassTransit.SqlTransport.Tests.csproj | 1 - tests/MassTransit.Tests/MassTransit.Tests.csproj | 5 ----- 9 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 8a92afcb6ca..8a6459df940 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,7 +8,7 @@ - + @@ -26,16 +26,16 @@ - + - + - - + + @@ -73,14 +73,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 4a4dc1e9e9b..bde69c73839 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -3,7 +3,6 @@ - 8 4 $(NoWarn),CS0618 true diff --git a/tests/MassTransit.Azure.ServiceBus.Core.Tests/MassTransit.Azure.ServiceBus.Core.Tests.csproj b/tests/MassTransit.Azure.ServiceBus.Core.Tests/MassTransit.Azure.ServiceBus.Core.Tests.csproj index 937184cb2b3..f67f3724a13 100644 --- a/tests/MassTransit.Azure.ServiceBus.Core.Tests/MassTransit.Azure.ServiceBus.Core.Tests.csproj +++ b/tests/MassTransit.Azure.ServiceBus.Core.Tests/MassTransit.Azure.ServiceBus.Core.Tests.csproj @@ -2,7 +2,6 @@ net8.0 - 9 diff --git a/tests/MassTransit.Benchmark/MassTransit.Benchmark.csproj b/tests/MassTransit.Benchmark/MassTransit.Benchmark.csproj index f82199e4eb5..3aa2b5d0484 100644 --- a/tests/MassTransit.Benchmark/MassTransit.Benchmark.csproj +++ b/tests/MassTransit.Benchmark/MassTransit.Benchmark.csproj @@ -5,7 +5,6 @@ mtbench MassTransitBenchmark net8.0 - latest diff --git a/tests/MassTransit.EventHubIntegration.Tests/MassTransit.EventHubIntegration.Tests.csproj b/tests/MassTransit.EventHubIntegration.Tests/MassTransit.EventHubIntegration.Tests.csproj index 7cf24c9f4b9..d7bfb4fa8d9 100644 --- a/tests/MassTransit.EventHubIntegration.Tests/MassTransit.EventHubIntegration.Tests.csproj +++ b/tests/MassTransit.EventHubIntegration.Tests/MassTransit.EventHubIntegration.Tests.csproj @@ -2,7 +2,6 @@ net8.0 - latest diff --git a/tests/MassTransit.KafkaIntegration.Tests/MassTransit.KafkaIntegration.Tests.csproj b/tests/MassTransit.KafkaIntegration.Tests/MassTransit.KafkaIntegration.Tests.csproj index 57c16705762..793dbd01e76 100644 --- a/tests/MassTransit.KafkaIntegration.Tests/MassTransit.KafkaIntegration.Tests.csproj +++ b/tests/MassTransit.KafkaIntegration.Tests/MassTransit.KafkaIntegration.Tests.csproj @@ -2,7 +2,6 @@ net8.0 - latest diff --git a/tests/MassTransit.MartenIntegration.Tests/MassTransit.MartenIntegration.Tests.csproj b/tests/MassTransit.MartenIntegration.Tests/MassTransit.MartenIntegration.Tests.csproj index 0ffb94481b3..cbeae8e3e35 100644 --- a/tests/MassTransit.MartenIntegration.Tests/MassTransit.MartenIntegration.Tests.csproj +++ b/tests/MassTransit.MartenIntegration.Tests/MassTransit.MartenIntegration.Tests.csproj @@ -2,7 +2,6 @@ net8.0 - 10 diff --git a/tests/MassTransit.SqlTransport.Tests/MassTransit.SqlTransport.Tests.csproj b/tests/MassTransit.SqlTransport.Tests/MassTransit.SqlTransport.Tests.csproj index f69c96d519b..8aeacd7239f 100644 --- a/tests/MassTransit.SqlTransport.Tests/MassTransit.SqlTransport.Tests.csproj +++ b/tests/MassTransit.SqlTransport.Tests/MassTransit.SqlTransport.Tests.csproj @@ -2,7 +2,6 @@ net8.0 - 11 MassTransit.DbTransport.Tests diff --git a/tests/MassTransit.Tests/MassTransit.Tests.csproj b/tests/MassTransit.Tests/MassTransit.Tests.csproj index a759ee61f93..8dbc7f16c46 100644 --- a/tests/MassTransit.Tests/MassTransit.Tests.csproj +++ b/tests/MassTransit.Tests/MassTransit.Tests.csproj @@ -8,11 +8,6 @@ $(TargetFrameworks);net472 - - 10 - - - From c287ee383c6ceb5f26fea1974c5f1e4aff7c7f2f Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 25 Apr 2024 16:14:58 -0500 Subject: [PATCH 07/27] Related to #3303 - Added support for ICredentialsProvider and perhaps ICredentialsRefresher (RabbitMQ). --- .../IRabbitMqHostConfigurator.cs | 23 +++++++---- .../Configuration/RabbitMqHostSettings.cs | 10 +++++ .../RefreshConnectionFactoryCallback.cs | 7 ++++ .../ConfigurationHostSettings.cs | 6 ++- .../Configuration/RabbitMqHostConfigurator.cs | 38 +++++++++++-------- .../RabbitMqAddressExtensions.cs | 4 ++ .../RabbitMqOptionSet.cs | 2 + 7 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/Transports/MassTransit.RabbitMqTransport/Configuration/RefreshConnectionFactoryCallback.cs diff --git a/src/Transports/MassTransit.RabbitMqTransport/Configuration/IRabbitMqHostConfigurator.cs b/src/Transports/MassTransit.RabbitMqTransport/Configuration/IRabbitMqHostConfigurator.cs index 36503b17390..0f5367ece55 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/Configuration/IRabbitMqHostConfigurator.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/Configuration/IRabbitMqHostConfigurator.cs @@ -1,13 +1,10 @@ -namespace MassTransit +#nullable enable +namespace MassTransit { using System; - using System.Threading.Tasks; using RabbitMQ.Client; - public delegate Task RefreshConnectionFactoryCallback(ConnectionFactory connectionFactory); - - public interface IRabbitMqHostConfigurator { /// @@ -18,11 +15,21 @@ public interface IRabbitMqHostConfigurator RefreshConnectionFactoryCallback OnRefreshConnectionFactory { set; } + /// + /// Sets the credential provider, overriding the default username/password credentials + /// + ICredentialsProvider CredentialsProvider { set; } + + /// + /// Sets the credentials refresher, allowing access token based credentials to be refreshed + /// + ICredentialsRefresher CredentialsRefresher { set; } + /// /// Configure the use of SSL to connection to RabbitMQ /// - /// - void UseSsl(Action configureSsl); + /// + void UseSsl(Action? configure = null); /// /// Specifies the heartbeat interval, in seconds, used to maintain the connection to RabbitMQ. @@ -94,6 +101,6 @@ public interface IRabbitMqHostConfigurator /// /// Sets the connection name for the connection to RabbitMQ /// - void ConnectionName(string connectionName); + void ConnectionName(string? connectionName); } } diff --git a/src/Transports/MassTransit.RabbitMqTransport/Configuration/RabbitMqHostSettings.cs b/src/Transports/MassTransit.RabbitMqTransport/Configuration/RabbitMqHostSettings.cs index 15823b5e33c..f215eae2a5e 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/Configuration/RabbitMqHostSettings.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/Configuration/RabbitMqHostSettings.cs @@ -148,6 +148,16 @@ public interface RabbitMqHostSettings /// uint? MaxMessageSize { get; } + /// + /// The credential provider, overriding the default username/password credentials + /// + ICredentialsProvider CredentialsProvider { get; } + + /// + /// The credentials refresher, allowing access token based credentials to be refreshed + /// + ICredentialsRefresher CredentialsRefresher { get; } + /// /// Called prior to the connection factory being used to connect, so that any settings can be updated. /// Typically this would be the username/password in response to an expired token, etc. diff --git a/src/Transports/MassTransit.RabbitMqTransport/Configuration/RefreshConnectionFactoryCallback.cs b/src/Transports/MassTransit.RabbitMqTransport/Configuration/RefreshConnectionFactoryCallback.cs new file mode 100644 index 00000000000..ee212957f87 --- /dev/null +++ b/src/Transports/MassTransit.RabbitMqTransport/Configuration/RefreshConnectionFactoryCallback.cs @@ -0,0 +1,7 @@ +namespace MassTransit; + +using System.Threading.Tasks; +using RabbitMQ.Client; + + +public delegate Task RefreshConnectionFactoryCallback(ConnectionFactory connectionFactory); diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs index 9432641b598..602b7da8a3d 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs @@ -1,3 +1,4 @@ +#nullable enable namespace MassTransit.RabbitMqTransport.Configuration { using System; @@ -55,7 +56,7 @@ public ConfigurationHostSettings() public LocalCertificateSelectionCallback CertificateSelectionCallback { get; set; } public RemoteCertificateValidationCallback CertificateValidationCallback { get; set; } public IRabbitMqEndpointResolver EndpointResolver { get; set; } - public string ClientProvidedName { get; set; } + public string? ClientProvidedName { get; set; } public bool PublisherConfirmation { get; set; } public Uri HostAddress => _hostAddress.Value; public ushort RequestedChannelMax { get; set; } @@ -65,6 +66,9 @@ public ConfigurationHostSettings() public TimeSpan ContinuationTimeout { get; set; } public uint? MaxMessageSize { get; set; } + public ICredentialsProvider? CredentialsProvider { get; set; } + public ICredentialsRefresher? CredentialsRefresher { get; set; } + public Task Refresh(ConnectionFactory connectionFactory) { return OnRefreshConnectionFactory?.Invoke(connectionFactory) ?? Task.CompletedTask; diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqHostConfigurator.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqHostConfigurator.cs index 31eca770e01..c0549c40f6b 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqHostConfigurator.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqHostConfigurator.cs @@ -1,24 +1,22 @@ +#nullable enable namespace MassTransit.RabbitMqTransport.Configuration { using System; + using RabbitMQ.Client; public class RabbitMqHostConfigurator : IRabbitMqHostConfigurator { - static readonly char[] _pathSeparator = { '/' }; + static readonly char[] _pathSeparator = ['/']; readonly ConfigurationHostSettings _settings; - public RabbitMqHostConfigurator(Uri hostAddress, string connectionName = null) + public RabbitMqHostConfigurator(Uri hostAddress, string? connectionName = null) { _settings = hostAddress.GetConfigurationHostSettings(); if (_settings.Port == 5671) - { - UseSsl(s => - { - }); - } + UseSsl(); _settings.VirtualHost = Uri.UnescapeDataString(GetVirtualHost(hostAddress)); @@ -26,7 +24,7 @@ public RabbitMqHostConfigurator(Uri hostAddress, string connectionName = null) _settings.ClientProvidedName = connectionName; } - public RabbitMqHostConfigurator(string host, string virtualHost, ushort port = 5672, string connectionName = null) + public RabbitMqHostConfigurator(string host, string virtualHost, ushort port = 5672, string? connectionName = null) { _settings = new ConfigurationHostSettings { @@ -53,11 +51,11 @@ public bool PublisherConfirmation set => _settings.PublisherConfirmation = value; } - public void UseSsl(Action configureSsl) + public void UseSsl(Action? configure = null) { var configurator = new RabbitMqSslConfigurator(_settings); - configureSsl(configurator); + configure?.Invoke(configurator); _settings.Ssl = true; _settings.ClientCertificatePassphrase = configurator.CertificatePassphrase; @@ -116,6 +114,16 @@ public void Password(string password) _settings.Password = password; } + public ICredentialsProvider CredentialsProvider + { + set => _settings.CredentialsProvider = value; + } + + public ICredentialsRefresher CredentialsRefresher + { + set => _settings.CredentialsRefresher = value; + } + public void UseCluster(Action configureCluster) { var configurator = new RabbitMqClusterConfigurator(_settings); @@ -139,6 +147,11 @@ public void RequestedConnectionTimeout(TimeSpan timeSpan) _settings.RequestedConnectionTimeout = timeSpan; } + public void ConnectionName(string? connectionName) + { + _settings.ClientProvidedName = connectionName; + } + string GetVirtualHost(Uri address) { var segments = address.AbsolutePath.Split(_pathSeparator, StringSplitOptions.RemoveEmptyEntries); @@ -151,10 +164,5 @@ string GetVirtualHost(Uri address) throw new FormatException("The host path must be empty or contain a single virtual host name"); } - - public void ConnectionName(string connectionName) - { - _settings.ClientProvidedName = connectionName; - } } } diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs index 7c2a44b10af..c01b280db5f 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs @@ -65,6 +65,8 @@ public static ConnectionFactory GetConnectionFactory(this RabbitMqHostSettings s factory.UserName = ""; factory.Password = ""; } + else if (settings.CredentialsProvider != null) + factory.CredentialsProvider = settings.CredentialsProvider; else { if (!string.IsNullOrWhiteSpace(settings.Username)) @@ -74,6 +76,8 @@ public static ConnectionFactory GetConnectionFactory(this RabbitMqHostSettings s factory.Password = settings.Password; } + factory.CredentialsRefresher = settings.CredentialsRefresher; + ApplySslOptions(settings, factory.Ssl); factory.ClientProperties ??= new Dictionary(); diff --git a/tests/MassTransit.Benchmark/RabbitMqOptionSet.cs b/tests/MassTransit.Benchmark/RabbitMqOptionSet.cs index e625af393b6..5758642ea15 100644 --- a/tests/MassTransit.Benchmark/RabbitMqOptionSet.cs +++ b/tests/MassTransit.Benchmark/RabbitMqOptionSet.cs @@ -103,6 +103,8 @@ public RabbitMqOptionSet() public TimeSpan ContinuationTimeout => TimeSpan.FromSeconds(20); public uint? MaxMessageSize { get; set; } + public ICredentialsProvider CredentialsProvider { get; set; } + public ICredentialsRefresher CredentialsRefresher { get; set; } public Task Refresh(ConnectionFactory connectionFactory) { From be70dece8a6116bfc1a0add7243c25e3843681de Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 26 Apr 2024 09:27:55 -0500 Subject: [PATCH 08/27] Fixed #5137 - Updated SqlTransportOptions so that it allows for additional connection string parameters --- .../Configuration/SqlTransportOptions.cs | 5 ++ ...ostgresBusFactoryConfiguratorExtensions.cs | 20 +++++++ .../PostgreSql/PostgresSqlHostSettings.cs | 28 +++++----- .../PostgresSqlTransportConnection.cs | 53 ++++++++++--------- ...lServerBusFactoryConfiguratorExtensions.cs | 20 +++++++ .../SqlServerHostConfigurationExtensions.cs | 24 +++++++-- .../SqlServer/SqlServerSqlHostSettings.cs | 14 ++--- .../SqlServerSqlTransportConnection.cs | 53 ++++++++++--------- 8 files changed, 143 insertions(+), 74 deletions(-) diff --git a/src/MassTransit/SqlTransport/Configuration/SqlTransportOptions.cs b/src/MassTransit/SqlTransport/Configuration/SqlTransportOptions.cs index 917fa713e9f..936df2d3186 100644 --- a/src/MassTransit/SqlTransport/Configuration/SqlTransportOptions.cs +++ b/src/MassTransit/SqlTransport/Configuration/SqlTransportOptions.cs @@ -13,4 +13,9 @@ public class SqlTransportOptions public string? AdminUsername { get; set; } public string? AdminPassword { get; set; } + + /// + /// Optional, if specified, will be parsed to capture additional properties on the connection. + /// + public string? ConnectionString { get; set; } } diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/Configuration/PostgresBusFactoryConfiguratorExtensions.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/Configuration/PostgresBusFactoryConfiguratorExtensions.cs index d9da9524eda..4045952d7d6 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/Configuration/PostgresBusFactoryConfiguratorExtensions.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/Configuration/PostgresBusFactoryConfiguratorExtensions.cs @@ -21,5 +21,25 @@ public static void UsingPostgres(this IBusRegistrationConfigurator configurator, configure?.Invoke(context, cfg); })); } + + /// + /// Configure the bus to use the PostgreSQL database transport + /// + /// The registration configurator (configured via AddMassTransit) + /// + /// Connection string to be used/parsed by the transport. are not + /// used with this overload + /// + /// The configuration callback for the bus factory + public static void UsingPostgres(this IBusRegistrationConfigurator configurator, string connectionString, + Action? configure = null) + { + configurator.SetBusFactory(new SqlRegistrationBusFactory((context, cfg) => + { + cfg.UsePostgres(connectionString); + + configure?.Invoke(context, cfg); + })); + } } } diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlHostSettings.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlHostSettings.cs index 89bf41a4768..bd17438691d 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlHostSettings.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlHostSettings.cs @@ -22,20 +22,20 @@ public PostgresSqlHostSettings(string connectionString) public PostgresSqlHostSettings(SqlTransportOptions options) { - Host = options.Host; - Database = options.Database; - Username = options.Username; - Password = options.Password; - Schema = options.Schema; + var builder = PostgresSqlTransportConnection.CreateBuilder(options); - if (options.Port.HasValue) - Port = options.Port.Value; - } + Host = builder.Host; + if (builder.Port > 0 && builder.Port != NpgsqlConnection.DefaultPort) + Port = options.Port; - public string? Role { get; set; } + Database = builder.Database; + Schema = options.Schema; - public string? AdminUsername { get; set; } - public string? AdminPassword { get; set; } + Username = builder.Username; + Password = builder.Password; + + _builder = builder; + } public string? ConnectionString { @@ -44,14 +44,14 @@ public string? ConnectionString var builder = new NpgsqlConnectionStringBuilder(value); Host = builder.Host; - if (builder.Port > 0) + if (builder.Port > 0 && builder.Port != NpgsqlConnection.DefaultPort) Port = builder.Port; + Database = builder.Database; + Username = builder.Username; Password = builder.Password; - Database = builder.Database; - _builder = builder; } } diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlTransportConnection.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlTransportConnection.cs index 7dda5576d55..ae19827c848 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlTransportConnection.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresSqlTransportConnection.cs @@ -44,50 +44,51 @@ public Task Close() public static PostgresSqlTransportConnection GetSystemDatabaseConnection(SqlTransportOptions options) { - var builder = new NpgsqlConnectionStringBuilder - { - Host = options.Host, - Database = "postgres", - Username = options.AdminUsername ?? options.Username, - Password = options.AdminPassword ?? options.Password - }; + var builder = CreateBuilder(options); - if (options.Port.HasValue) - builder.Port = options.Port.Value; + builder.Database = "postgres"; + + if (!string.IsNullOrWhiteSpace(options.AdminUsername)) + builder.Username = options.AdminUsername; + if (!string.IsNullOrWhiteSpace(options.AdminPassword)) + builder.Password = options.AdminPassword; return new PostgresSqlTransportConnection(builder.ToString()); } public static PostgresSqlTransportConnection GetDatabaseAdminConnection(SqlTransportOptions options) { - var builder = new NpgsqlConnectionStringBuilder - { - Host = options.Host, - Database = options.Database, - Username = options.AdminUsername ?? options.Username, - Password = options.AdminPassword ?? options.Password - }; + var builder = CreateBuilder(options); - if (options.Port.HasValue) - builder.Port = options.Port.Value; + if (!string.IsNullOrWhiteSpace(options.AdminUsername)) + builder.Username = options.AdminUsername; + if (!string.IsNullOrWhiteSpace(options.AdminPassword)) + builder.Password = options.AdminPassword; return new PostgresSqlTransportConnection(builder.ToString()); } public static PostgresSqlTransportConnection GetDatabaseConnection(SqlTransportOptions options) { - var builder = new NpgsqlConnectionStringBuilder - { - Host = options.Host, - Database = options.Database, - Username = options.Username, - Password = options.Password - }; + return new PostgresSqlTransportConnection(CreateBuilder(options).ToString()); + } + public static NpgsqlConnectionStringBuilder CreateBuilder(SqlTransportOptions options) + { + var builder = new NpgsqlConnectionStringBuilder(options.ConnectionString); + + if (!string.IsNullOrWhiteSpace(options.Host)) + builder.Host = options.Host; + if (!string.IsNullOrWhiteSpace(options.Database)) + builder.Database = options.Database; + if (!string.IsNullOrWhiteSpace(options.Username)) + builder.Username = options.Username; + if (!string.IsNullOrWhiteSpace(options.Password)) + builder.Password = options.Password; if (options.Port.HasValue) builder.Port = options.Port.Value; - return new PostgresSqlTransportConnection(builder.ToString()); + return builder; } public static string? GetAdminMigrationPrincipal(SqlTransportOptions options) diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerBusFactoryConfiguratorExtensions.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerBusFactoryConfiguratorExtensions.cs index 639349ac7e5..cd4f8a778df 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerBusFactoryConfiguratorExtensions.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerBusFactoryConfiguratorExtensions.cs @@ -21,5 +21,25 @@ public static void UsingSqlServer(this IBusRegistrationConfigurator configurator configure?.Invoke(context, cfg); })); } + + /// + /// Configure the bus to use the PostgreSQL database transport + /// + /// The registration configurator (configured via AddMassTransit) + /// + /// Connection string to be used/parsed by the transport. are not + /// used with this overload + /// + /// The configuration callback for the bus factory + public static void UsingSqlServer(this IBusRegistrationConfigurator configurator, string connectionString, + Action? configure = null) + { + configurator.SetBusFactory(new SqlRegistrationBusFactory((context, cfg) => + { + cfg.UseSqlServer(connectionString); + + configure?.Invoke(context, cfg); + })); + } } } diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerHostConfigurationExtensions.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerHostConfigurationExtensions.cs index 2e46f47a938..d42925baf7b 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerHostConfigurationExtensions.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/Configuration/SqlServerHostConfigurationExtensions.cs @@ -9,7 +9,7 @@ namespace MassTransit public static class SqlServerHostConfigurationExtensions { /// - /// Configures the database transport to use PostgreSQL as the storage engine + /// Configures the database transport to use SQL Server as the storage engine /// /// /// The MassTransit host address of the database @@ -24,12 +24,13 @@ public static void UseSqlServer(this ISqlBusFactoryConfigurator configurator, Ur } /// - /// Configures the database transport to use PostgreSQL as the storage engine + /// Configures the database transport to use SQL Server as the storage engine /// /// /// The bus registration context, used to retrieve the DbTransportOptions /// - public static void UseSqlServer(this ISqlBusFactoryConfigurator configurator, IBusRegistrationContext context, Action? configure = null) + public static void UseSqlServer(this ISqlBusFactoryConfigurator configurator, IBusRegistrationContext context, + Action? configure = null) { var hostConfigurator = new SqlServerSqlHostConfigurator(context.GetRequiredService>().Value); @@ -37,5 +38,20 @@ public static void UseSqlServer(this ISqlBusFactoryConfigurator configurator, IB configurator.Host(hostConfigurator.Settings); } + + /// + /// Configures the database transport to use SQL Server as the storage engine + /// + /// + /// A valid SQL Server connection string + /// + public static void UseSqlServer(this ISqlBusFactoryConfigurator configurator, string connectionString, Action? configure = null) + { + var hostConfigurator = new SqlServerSqlHostConfigurator(connectionString); + + configure?.Invoke(hostConfigurator); + + configurator.Host(hostConfigurator.Settings); + } } -} \ No newline at end of file +} diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs index 8607d4e6400..38f9e22d8bc 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlHostSettings.cs @@ -27,15 +27,17 @@ public SqlServerSqlHostSettings(string connectionString) public SqlServerSqlHostSettings(SqlTransportOptions options) { - ParseHost(options.Host); + var builder = SqlServerSqlTransportConnection.CreateBuilder(options); - Database = options.Database; - Username = options.Username; - Password = options.Password; + ParseDataSource(builder.DataSource); + + Database = builder.InitialCatalog; Schema = options.Schema; - if (options.Port.HasValue) - Port = options.Port.Value; + Username = builder.UserID; + Password = builder.Password; + + _builder = builder; } public string? ConnectionString diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlTransportConnection.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlTransportConnection.cs index 6b17526502f..409b4a0a8c8 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlTransportConnection.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerSqlTransportConnection.cs @@ -47,45 +47,50 @@ public SqlCommand CreateCommand(string commandText) public static SqlServerSqlTransportConnection GetSystemDatabaseConnection(SqlTransportOptions options) { - var builder = new SqlConnectionStringBuilder - { - DataSource = options.FormatDataSource(), - InitialCatalog = "master", - UserID = options.AdminUsername ?? options.Username, - Password = options.AdminPassword ?? options.Password, - TrustServerCertificate = true - }; + var builder = CreateBuilder(options); + builder.InitialCatalog = "master"; + + if (!string.IsNullOrWhiteSpace(options.AdminUsername)) + builder.UserID = options.AdminUsername; + if (!string.IsNullOrWhiteSpace(options.AdminPassword)) + builder.Password = options.AdminPassword; return new SqlServerSqlTransportConnection(builder.ToString()); } public static SqlServerSqlTransportConnection GetDatabaseAdminConnection(SqlTransportOptions options) { - var builder = new SqlConnectionStringBuilder - { - DataSource = options.FormatDataSource(), - InitialCatalog = options.Database, - UserID = options.AdminUsername ?? options.Username, - Password = options.AdminPassword ?? options.Password, - TrustServerCertificate = true - }; + var builder = CreateBuilder(options); + if (!string.IsNullOrWhiteSpace(options.AdminUsername)) + builder.UserID = options.AdminUsername; + if (!string.IsNullOrWhiteSpace(options.AdminPassword)) + builder.Password = options.AdminPassword; return new SqlServerSqlTransportConnection(builder.ToString()); } public static SqlServerSqlTransportConnection GetDatabaseConnection(SqlTransportOptions options) { - var builder = new SqlConnectionStringBuilder - { - DataSource = options.FormatDataSource(), - InitialCatalog = options.Database, - UserID = options.Username, - Password = options.Password, - TrustServerCertificate = true - }; + var builder = CreateBuilder(options); return new SqlServerSqlTransportConnection(builder.ToString()); } + + public static SqlConnectionStringBuilder CreateBuilder(SqlTransportOptions options) + { + var builder = new SqlConnectionStringBuilder(options.ConnectionString) { TrustServerCertificate = true }; + + if (!string.IsNullOrWhiteSpace(options.Host)) + builder.DataSource = options.FormatDataSource(); + if (!string.IsNullOrWhiteSpace(options.Database)) + builder.InitialCatalog = options.Database; + if (!string.IsNullOrWhiteSpace(options.Username)) + builder.UserID = options.Username; + if (!string.IsNullOrWhiteSpace(options.Password)) + builder.Password = options.Password; + + return builder; + } } From 1050df6de31ca6a37e5630d0febc84b33a301c44 Mon Sep 17 00:00:00 2001 From: mikemao Date: Wed, 10 Apr 2024 09:56:58 +1200 Subject: [PATCH 09/27] Added WhenAllCompletedOrFaulted to Durable Future --- .../Futures/BatchCompleted.cs | 11 +++ .../Futures/BatchFaulted.cs | 11 +++ .../Futures/BatchFuture.cs | 46 ++++++++++ .../Futures/BatchRequest.cs | 12 +++ .../Futures/ProcessBatchItem.cs | 10 +++ .../Futures/ProcessBatchItemCompleted.cs | 10 +++ .../Futures/ProcessBatchItemConsumer.cs | 29 +++++++ .../Futures/Tests/BatchFuture_Specs.cs | 20 +++++ src/MassTransit/Futures/Future.cs | 12 +++ .../Futures/Futures/FutureFault.cs | 26 ++++-- .../Scenarios/WhenAllCompletedOrFaulted.cs | 84 +++++++++++++++++++ 11 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 src/MassTransit.TestFramework/Futures/BatchCompleted.cs create mode 100644 src/MassTransit.TestFramework/Futures/BatchFaulted.cs create mode 100644 src/MassTransit.TestFramework/Futures/BatchFuture.cs create mode 100644 src/MassTransit.TestFramework/Futures/BatchRequest.cs create mode 100644 src/MassTransit.TestFramework/Futures/ProcessBatchItem.cs create mode 100644 src/MassTransit.TestFramework/Futures/ProcessBatchItemCompleted.cs create mode 100644 src/MassTransit.TestFramework/Futures/ProcessBatchItemConsumer.cs create mode 100644 src/MassTransit.TestFramework/Futures/Tests/BatchFuture_Specs.cs create mode 100644 tests/MassTransit.Tests/ContainerTests/Scenarios/WhenAllCompletedOrFaulted.cs diff --git a/src/MassTransit.TestFramework/Futures/BatchCompleted.cs b/src/MassTransit.TestFramework/Futures/BatchCompleted.cs new file mode 100644 index 00000000000..7f41a5b2129 --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/BatchCompleted.cs @@ -0,0 +1,11 @@ +namespace MassTransit.TestFramework.Futures; + +using System; +using System.Collections.Generic; + + +public interface BatchCompleted +{ + public Guid CorrelationId { get; } + public IReadOnlyList ProcessedJobsNumbers { get; } +} diff --git a/src/MassTransit.TestFramework/Futures/BatchFaulted.cs b/src/MassTransit.TestFramework/Futures/BatchFaulted.cs new file mode 100644 index 00000000000..7e7e2bbad8d --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/BatchFaulted.cs @@ -0,0 +1,11 @@ +namespace MassTransit.TestFramework.Futures; + +using System; +using System.Collections.Generic; + + +public interface BatchFaulted +{ + public Guid CorrelationId { get; } + public IReadOnlyList ProcessedJobsNumbers { get; } +} diff --git a/src/MassTransit.TestFramework/Futures/BatchFuture.cs b/src/MassTransit.TestFramework/Futures/BatchFuture.cs new file mode 100644 index 00000000000..4e074bd0428 --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/BatchFuture.cs @@ -0,0 +1,46 @@ +namespace MassTransit.TestFramework.Futures; + +using System.Collections.Generic; +using System.Linq; + + +public class BatchFuture : + Future +{ + public BatchFuture() + { + ConfigureCommand(x => x.CorrelateById(context => context.Message.CorrelationId)); + + SendRequests(x => x.JobNumbers, + x => + { + x.UsingRequestInitializer(context => new + { + CorrelationId = InVar.Id, + JobNumber = context.Message + }); + x.TrackPendingRequest(message => message.CorrelationId); + }) + .OnResponseReceived(x => + { + x.CompletePendingRequest(y => y.CorrelationId); + }); + + WhenAllCompleted(r => r.SetCompletedUsingInitializer(MapResponse)); + WhenAllCompletedOrFaulted(r => r.SetFaultedUsingInitializer(MapResponse)); + } + + object MapResponse(BehaviorContext context) + { + var command = context.GetCommand(); + List processedJobNumbers = context + .SelectResults() + .Select(r => r.JobNumber).ToList(); + + return new + { + command.CorrelationId, + ProcessedJobsNumbers = processedJobNumbers + }; + } +} diff --git a/src/MassTransit.TestFramework/Futures/BatchRequest.cs b/src/MassTransit.TestFramework/Futures/BatchRequest.cs new file mode 100644 index 00000000000..1b3be1a7c24 --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/BatchRequest.cs @@ -0,0 +1,12 @@ +namespace MassTransit.TestFramework.Futures; + +using System; +using System.Collections.Generic; + + +public interface BatchRequest +{ + public DateTime? BatchExpiry { get; } + public Guid CorrelationId { get; } + public IReadOnlyList JobNumbers { get; } +} diff --git a/src/MassTransit.TestFramework/Futures/ProcessBatchItem.cs b/src/MassTransit.TestFramework/Futures/ProcessBatchItem.cs new file mode 100644 index 00000000000..2e0d09c206d --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/ProcessBatchItem.cs @@ -0,0 +1,10 @@ +namespace MassTransit.TestFramework.Futures; + +using System; + + +public interface ProcessBatchItem +{ + public Guid CorrelationId { get; } + public string JobNumber { get; } +} diff --git a/src/MassTransit.TestFramework/Futures/ProcessBatchItemCompleted.cs b/src/MassTransit.TestFramework/Futures/ProcessBatchItemCompleted.cs new file mode 100644 index 00000000000..111d2774987 --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/ProcessBatchItemCompleted.cs @@ -0,0 +1,10 @@ +namespace MassTransit.TestFramework.Futures; + +using System; + + +public interface ProcessBatchItemCompleted +{ + public Guid CorrelationId { get; } + public string JobNumber { get; } +} diff --git a/src/MassTransit.TestFramework/Futures/ProcessBatchItemConsumer.cs b/src/MassTransit.TestFramework/Futures/ProcessBatchItemConsumer.cs new file mode 100644 index 00000000000..9df157480fb --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/ProcessBatchItemConsumer.cs @@ -0,0 +1,29 @@ +namespace MassTransit.TestFramework.Futures; + +using System; +using System.Threading.Tasks; + + +public class ProcessBatchItemConsumer : + IConsumer +{ + public Task Consume(ConsumeContext context) + { + async Task WaitAndRespond(int milliSecond) + { + await Task.Delay(milliSecond); + await context.RespondAsync(new + { + context.Message.CorrelationId, + context.Message.JobNumber + }); + } + + return context.Message.JobNumber switch + { + "Delay" => WaitAndRespond(2000), + "Error" => throw new InvalidOperationException(), + _ => WaitAndRespond(0) + }; + } +} diff --git a/src/MassTransit.TestFramework/Futures/Tests/BatchFuture_Specs.cs b/src/MassTransit.TestFramework/Futures/Tests/BatchFuture_Specs.cs new file mode 100644 index 00000000000..8c1f6d374bb --- /dev/null +++ b/src/MassTransit.TestFramework/Futures/Tests/BatchFuture_Specs.cs @@ -0,0 +1,20 @@ +namespace MassTransit.TestFramework.Futures.Tests; + +using NUnit.Framework; + + +[TestFixture] +public class BatchFuture_Specs : + FutureTestFixture +{ + public BatchFuture_Specs(IFutureTestFixtureConfigurator testFixtureConfigurator) + : base(testFixtureConfigurator) + { + } + + protected override void ConfigureMassTransit(IBusRegistrationConfigurator configurator) + { + configurator.AddConsumer(); + configurator.AddFuture(); + } +} diff --git a/src/MassTransit/Futures/Future.cs b/src/MassTransit/Futures/Future.cs index b9b091b5bda..6928f301390 100644 --- a/src/MassTransit/Futures/Future.cs +++ b/src/MassTransit/Futures/Future.cs @@ -417,6 +417,18 @@ protected void WhenAnyFaulted(Action> configure configure?.Invoke(configurator); } + /// + /// When all requests have either completed or faulted, Set the future Faulted + /// + /// + protected void WhenAllCompletedOrFaulted(Action> configure) + { + _fault.WaitForPending = true; + var configurator = new FutureFaultConfigurator(_fault); + + configure?.Invoke(configurator); + } + static Task GetResult(BehaviorContext context) { if (context.TryGetResult(context.Saga.CorrelationId, out TResult completed)) diff --git a/src/MassTransit/Futures/Futures/FutureFault.cs b/src/MassTransit/Futures/Futures/FutureFault.cs index 52039513dfa..e28c71edfc8 100644 --- a/src/MassTransit/Futures/Futures/FutureFault.cs +++ b/src/MassTransit/Futures/Futures/FutureFault.cs @@ -26,6 +26,8 @@ public ContextMessageFactory, TFault> Facto set => _factory = value; } + public bool WaitForPending { get; set; } + public IEnumerable Validate() { yield break; @@ -33,12 +35,15 @@ public IEnumerable Validate() public async Task SetFaulted(BehaviorContext context) { - context.SetFaulted(context.Saga.CorrelationId); + if (!WaitForPending || !context.Saga.HasPending()) + { + context.SetFaulted(context.Saga.CorrelationId); - var fault = await context.SendMessageToSubscriptions(_factory, - context.Saga.HasSubscriptions() ? context.Saga.Subscriptions.ToArray() : Array.Empty()); + var fault = await context.SendMessageToSubscriptions(_factory, + context.Saga.HasSubscriptions() ? context.Saga.Subscriptions.ToArray() : Array.Empty()); - context.SetFault(context.Saga.CorrelationId, fault); + context.SetFault(context.Saga.CorrelationId, fault); + } } static Task> DefaultFactory(BehaviorContext context) @@ -86,6 +91,8 @@ public ContextMessageFactory, TFault> Factory set => _factory = value; } + public bool WaitForPending { get; set; } + public IEnumerable Validate() { yield break; @@ -93,12 +100,15 @@ public IEnumerable Validate() public async Task SetFaulted(BehaviorContext context) { - context.SetFaulted(context.Saga.CorrelationId); + if (!WaitForPending || !context.Saga.HasPending()) + { + context.SetFaulted(context.Saga.CorrelationId); - var fault = await context.SendMessageToSubscriptions(_factory, - context.Saga.HasSubscriptions() ? context.Saga.Subscriptions.ToArray() : Array.Empty()); + var fault = await context.SendMessageToSubscriptions(_factory, + context.Saga.HasSubscriptions() ? context.Saga.Subscriptions.ToArray() : Array.Empty()); - context.SetFault(context.Saga.CorrelationId, fault); + context.SetFault(context.Saga.CorrelationId, fault); + } } static Task> DefaultFactory(BehaviorContext context) diff --git a/tests/MassTransit.Tests/ContainerTests/Scenarios/WhenAllCompletedOrFaulted.cs b/tests/MassTransit.Tests/ContainerTests/Scenarios/WhenAllCompletedOrFaulted.cs new file mode 100644 index 00000000000..c36c56535e8 --- /dev/null +++ b/tests/MassTransit.Tests/ContainerTests/Scenarios/WhenAllCompletedOrFaulted.cs @@ -0,0 +1,84 @@ +namespace MassTransit.Tests.ContainerTests.Scenarios; + +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using TestFramework.Futures; +using TestFramework.Futures.Tests; + + +[TestFixture] +public class WhenAllCompletedOrFaulted : + BatchFuture_Specs +{ + [Test] + public async Task Delayed_success() + { + var batchId = NewId.NextGuid(); + var jobNumbers = new[] { "C12345", "Delay" }; + + var scope = Provider.CreateScope(); + + var client = scope.ServiceProvider.GetRequiredService>(); + + Response response = await client.GetResponse(new + { + CorrelationId = batchId, + JobNumbers = jobNumbers + }, timeout: RequestTimeout.After(s: 5)); + + Assert.That(response.Message.ProcessedJobsNumbers, Is.EqualTo(jobNumbers)); + } + + [Test] + public async Task Error_partially_uploaded() + { + var batchId = NewId.NextGuid(); + var jobNumbers = new[] { "C12345", "Error", "C54321", "Error", "C33454" }; + + var scope = Provider.CreateScope(); + + var client = scope.ServiceProvider.GetRequiredService>(); + + Response response = await client.GetResponse(new + { + CorrelationId = batchId, + JobNumbers = jobNumbers + }); + + switch (response) + { + case (_, BatchFaulted faulted): + //Batch is partially successful, downstream consumers are notified of succeeded uploads + Assert.That(faulted.ProcessedJobsNumbers, Is.EquivalentTo(new[] { "C12345", "C54321", "C33454" })); + break; + default: + Assert.Fail("Unexpected response"); + break; + } + } + + [Test] + public async Task Should_succeed() + { + var batchId = NewId.NextGuid(); + var jobNumbers = new[] { "C12345", "C54321" }; + + var scope = Provider.CreateScope(); + + var client = scope.ServiceProvider.GetRequiredService>(); + + Response response = await client.GetResponse(new + { + CorrelationId = batchId, + JobNumbers = jobNumbers + }, timeout: RequestTimeout.After(s: 5)); + + Assert.That(response.Message.ProcessedJobsNumbers, Is.EquivalentTo(jobNumbers)); + } + + public WhenAllCompletedOrFaulted() + : base(new InMemoryFutureTestFixtureConfigurator()) + { + } +} From f36c14faa4f7d2a04f38998c6b270c01867dd6f4 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 26 Apr 2024 09:43:40 -0500 Subject: [PATCH 10/27] Cleaned up warnings on RabbitMQ ConfigurationHostSettings --- .../ConfigurationHostSettings.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs index 602b7da8a3d..661834d138f 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/ConfigurationHostSettings.cs @@ -37,25 +37,25 @@ public ConfigurationHostSettings() _hostAddress = new Lazy(FormatHostAddress); } - public RefreshConnectionFactoryCallback OnRefreshConnectionFactory { get; set; } + public RefreshConnectionFactoryCallback? OnRefreshConnectionFactory { get; set; } - public string Host { get; set; } + public string? Host { get; set; } public int Port { get; set; } - public string VirtualHost { get; set; } - public string Username { get; set; } - public string Password { get; set; } + public string? VirtualHost { get; set; } + public string? Username { get; set; } + public string? Password { get; set; } public TimeSpan Heartbeat { get; set; } public bool Ssl { get; set; } public SslProtocols SslProtocol { get; set; } - public string SslServerName { get; set; } + public string? SslServerName { get; set; } public SslPolicyErrors AcceptablePolicyErrors { get; set; } - public string ClientCertificatePath { get; set; } - public string ClientCertificatePassphrase { get; set; } - public X509Certificate ClientCertificate { get; set; } + public string? ClientCertificatePath { get; set; } + public string? ClientCertificatePassphrase { get; set; } + public X509Certificate? ClientCertificate { get; set; } public bool UseClientCertificateAsAuthenticationIdentity { get; set; } - public LocalCertificateSelectionCallback CertificateSelectionCallback { get; set; } - public RemoteCertificateValidationCallback CertificateValidationCallback { get; set; } - public IRabbitMqEndpointResolver EndpointResolver { get; set; } + public LocalCertificateSelectionCallback? CertificateSelectionCallback { get; set; } + public RemoteCertificateValidationCallback? CertificateValidationCallback { get; set; } + public IRabbitMqEndpointResolver? EndpointResolver { get; set; } public string? ClientProvidedName { get; set; } public bool PublisherConfirmation { get; set; } public Uri HostAddress => _hostAddress.Value; From 71429ffb3464b09a666abda7946200a214bdb582 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 26 Apr 2024 15:45:25 -0500 Subject: [PATCH 11/27] Updated version to 8.2.3 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cecc699200..cf7622e1269 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,6 @@ name: MassTransit env: - MASSTRANSIT_VERSION: 8.2.2 + MASSTRANSIT_VERSION: 8.2.3 on: push: paths: From 25fd0509b29028a8ab56cfb17cc448bac7966553 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Sat, 27 Apr 2024 14:11:55 -0500 Subject: [PATCH 12/27] Fixed #5141 - Quotes around username in Postgres Migrator --- .../SqlTransport/PostgreSql/PostgresDatabaseMigrator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresDatabaseMigrator.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresDatabaseMigrator.cs index 93eb6cf72ac..c6a711b847b 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresDatabaseMigrator.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresDatabaseMigrator.cs @@ -20,7 +20,7 @@ public class PostgresDatabaseMigrator : const string GrantRoleSql = """ GRANT USAGE ON SCHEMA "{1}" TO "{0}"; ALTER SCHEMA "{1}" OWNER TO "{0}"; - GRANT "{0}" TO {2}; + GRANT "{0}" TO "{2}"; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "{1}" TO "{0}"; GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA "{1}" TO "{0}"; ALTER DEFAULT PRIVILEGES IN SCHEMA "{1}" GRANT ALL PRIVILEGES ON TABLES TO "{0}"; From 12563fa0d269f9d78522666fb7e1a6585cb33bfd Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 1 May 2024 08:18:12 -0500 Subject: [PATCH 13/27] Added xmin to PostgresLockStatementFormatter.cs queries to allow for EF-style optimistic concurrency checking. --- .../PostgresLockStatementFormatter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/PostgresLockStatementFormatter.cs b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/PostgresLockStatementFormatter.cs index 964f0126f93..b08a3c12c6c 100644 --- a/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/PostgresLockStatementFormatter.cs +++ b/src/Persistence/MassTransit.EntityFrameworkCoreIntegration/EntityFrameworkCoreIntegration/PostgresLockStatementFormatter.cs @@ -8,7 +8,7 @@ public class PostgresLockStatementFormatter : { public void Create(StringBuilder sb, string schema, string table) { - sb.AppendFormat("SELECT * FROM {0} WHERE ", FormatTableName(schema, table)); + sb.AppendFormat("SELECT *, xmin FROM {0} WHERE ", FormatTableName(schema, table)); } public void AppendColumn(StringBuilder sb, int index, string columnName) @@ -26,7 +26,7 @@ public void Complete(StringBuilder sb) public void CreateOutboxStatement(StringBuilder sb, string schema, string table, string columnName) { - sb.AppendFormat(@"SELECT * FROM {0} ORDER BY ""{1}"" LIMIT 1 FOR UPDATE SKIP LOCKED", FormatTableName(schema, table), columnName); + sb.AppendFormat(@"SELECT *, xmin FROM {0} ORDER BY ""{1}"" LIMIT 1 FOR UPDATE SKIP LOCKED", FormatTableName(schema, table), columnName); } static string FormatTableName(string schema, string table) From 1d640dc6c587309dbaaadf7083f8b6534aaf8ce6 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 2 May 2024 09:14:58 -0500 Subject: [PATCH 14/27] Fixed #4510 - Properly handle cross-region or SNS deliveries that are wrapped in an SNS envelope when deserializing using System.Text.Json only. --- src/MassTransit/JsonMessageBody.cs | 12 ++ .../SystemTextJsonMessageSerializer.cs | 11 +- .../SystemTextJsonRawMessageSerializer.cs | 19 +++- .../AmazonSqsHeaderProvider.cs | 10 +- .../AmazonSqsReceiveContext.cs | 10 +- .../AmazonSqsTransport/SqsMessageBody.cs | 40 +++++++ .../MessageBody_Specs.cs | 107 ++++++++++++++++++ 7 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 src/MassTransit/JsonMessageBody.cs create mode 100644 src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SqsMessageBody.cs create mode 100644 tests/MassTransit.AmazonSqsTransport.Tests/MessageBody_Specs.cs diff --git a/src/MassTransit/JsonMessageBody.cs b/src/MassTransit/JsonMessageBody.cs new file mode 100644 index 00000000000..a661685e9bd --- /dev/null +++ b/src/MassTransit/JsonMessageBody.cs @@ -0,0 +1,12 @@ +namespace MassTransit; + +using System.Text.Json; + + +/// +/// If the incoming message is in a JSON format, use this to unwrap the JSON document from any transport-specific encapsulation +/// +public interface JsonMessageBody +{ + JsonElement? GetJsonElement(JsonSerializerOptions options); +} diff --git a/src/MassTransit/Serialization/SystemTextJsonMessageSerializer.cs b/src/MassTransit/Serialization/SystemTextJsonMessageSerializer.cs index 90992c299bc..557a90c3891 100644 --- a/src/MassTransit/Serialization/SystemTextJsonMessageSerializer.cs +++ b/src/MassTransit/Serialization/SystemTextJsonMessageSerializer.cs @@ -3,7 +3,6 @@ namespace MassTransit.Serialization { using System; using System.Net.Mime; - using System.Reflection; using System.Runtime.Serialization; using System.Text.Encodings.Web; using System.Text.Json; @@ -77,7 +76,11 @@ public SerializerContext Deserialize(MessageBody body, Headers headers, Uri? des { try { - var envelope = JsonSerializer.Deserialize(body.GetBytes(), Options); + JsonElement? bodyElement = body is JsonMessageBody jsonMessageBody + ? jsonMessageBody.GetJsonElement(Options) + : JsonSerializer.Deserialize(body.GetBytes(), Options); + + var envelope = bodyElement?.Deserialize(Options); if (envelope == null) throw new SerializationException("Message envelope not found"); @@ -85,9 +88,7 @@ public SerializerContext Deserialize(MessageBody body, Headers headers, Uri? des var messageTypes = envelope.MessageType ?? Array.Empty(); - var serializerContext = new SystemTextJsonSerializerContext(this, Options, ContentType, messageContext, messageTypes, envelope); - - return serializerContext; + return new SystemTextJsonSerializerContext(this, Options, ContentType, messageContext, messageTypes, envelope); } catch (SerializationException) { diff --git a/src/MassTransit/Serialization/SystemTextJsonRawMessageSerializer.cs b/src/MassTransit/Serialization/SystemTextJsonRawMessageSerializer.cs index e1995764d76..2ded6093a17 100644 --- a/src/MassTransit/Serialization/SystemTextJsonRawMessageSerializer.cs +++ b/src/MassTransit/Serialization/SystemTextJsonRawMessageSerializer.cs @@ -39,18 +39,25 @@ public SerializerContext Deserialize(MessageBody body, Headers headers, Uri? des { try { - var bytes = body.GetBytes(); - - var jsonElement = bytes.Length > 0 - ? JsonSerializer.Deserialize(bytes, SystemTextJsonMessageSerializer.Options) - : JsonDocument.Parse("{}").RootElement; + JsonElement? bodyElement; + if (body is JsonMessageBody jsonMessageBody) + bodyElement = jsonMessageBody.GetJsonElement(SystemTextJsonMessageSerializer.Options); + else + { + var bytes = body.GetBytes(); + bodyElement = bytes.Length > 0 + ? JsonSerializer.Deserialize(bytes, SystemTextJsonMessageSerializer.Options) + : null; + } + + bodyElement ??= JsonDocument.Parse("{}").RootElement; var messageTypes = headers.GetMessageTypes(); var messageContext = new RawMessageContext(headers, destinationAddress, _options); var serializerContext = new SystemTextJsonRawSerializerContext(SystemTextJsonMessageSerializer.Instance, - SystemTextJsonMessageSerializer.Options, ContentType, messageContext, messageTypes, _options, jsonElement); + SystemTextJsonMessageSerializer.Options, ContentType, messageContext, messageTypes, _options, bodyElement.Value); return serializerContext; } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsHeaderProvider.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsHeaderProvider.cs index 9b83f28e4af..bce17beef35 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsHeaderProvider.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsHeaderProvider.cs @@ -12,11 +12,13 @@ public class AmazonSqsHeaderProvider : IHeaderProvider { + readonly SqsMessageBody _body; readonly Message _message; - public AmazonSqsHeaderProvider(Message message) + public AmazonSqsHeaderProvider(Message message, SqsMessageBody body) { _message = message; + _body = body; } public bool TryGetHeader(string key, out object value) @@ -33,6 +35,12 @@ public bool TryGetHeader(string key, out object value) return true; } + if ("TopicArn".Equals(key, StringComparison.OrdinalIgnoreCase)) + { + value = _body.TopicArn; + return value != null; + } + if (MessageHeaders.TransportSentTime.Equals(key, StringComparison.OrdinalIgnoreCase)) { if (_message.Attributes.TryGetValue(MessageSystemAttributeName.SentTimestamp, out var sentTimestamp)) diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsReceiveContext.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsReceiveContext.cs index f094e7cb96d..bb2553766d7 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsReceiveContext.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsReceiveContext.cs @@ -13,16 +13,22 @@ public sealed class AmazonSqsReceiveContext : AmazonSqsMessageContext, TransportReceiveContext { + readonly AmazonSqsHeaderProvider _headerProvider; + public AmazonSqsReceiveContext(Message message, bool redelivered, SqsReceiveEndpointContext context, ClientContext clientContext, ReceiveSettings settings, ConnectionContext connectionContext) : base(redelivered, context, settings, clientContext, connectionContext) { TransportMessage = message; - Body = new StringMessageBody(message?.Body); + var messageBody = new SqsMessageBody(message); + + Body = messageBody; + + _headerProvider = new AmazonSqsHeaderProvider(TransportMessage, messageBody); } - protected override IHeaderProvider HeaderProvider => new AmazonSqsHeaderProvider(TransportMessage); + protected override IHeaderProvider HeaderProvider => _headerProvider; public override MessageBody Body { get; } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SqsMessageBody.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SqsMessageBody.cs new file mode 100644 index 00000000000..cbac6225ab1 --- /dev/null +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SqsMessageBody.cs @@ -0,0 +1,40 @@ +#nullable enable +namespace MassTransit.AmazonSqsTransport; + +using System.Text.Json; +using Amazon.SQS.Model; + + +public class SqsMessageBody : + StringMessageBody, + JsonMessageBody +{ + readonly Message _message; + JsonElement? _topicArn; + + public SqsMessageBody(Message message) + : base(message.Body) + { + _message = message; + } + + public string? TopicArn => _topicArn?.GetString(); + + public JsonElement? GetJsonElement(JsonSerializerOptions options) + { + if (_message.Body == null) + return null; + + var jsonElement = JsonSerializer.Deserialize(_message.Body, options); + + if (jsonElement.TryGetProperty("TopicArn", out var topicElement) || jsonElement.TryGetProperty("topicArn", out topicElement)) + { + _topicArn = topicElement; + + if (jsonElement.TryGetProperty("Message", out var messageElement) || jsonElement.TryGetProperty("message", out messageElement)) + return JsonSerializer.Deserialize(messageElement.GetString() ?? string.Empty, options); + } + + return jsonElement; + } +} diff --git a/tests/MassTransit.AmazonSqsTransport.Tests/MessageBody_Specs.cs b/tests/MassTransit.AmazonSqsTransport.Tests/MessageBody_Specs.cs new file mode 100644 index 00000000000..12181cb1bde --- /dev/null +++ b/tests/MassTransit.AmazonSqsTransport.Tests/MessageBody_Specs.cs @@ -0,0 +1,107 @@ +namespace MassTransit.AmazonSqsTransport.Tests +{ + using System.Text.Json; + using Amazon.SQS.Model; + using NUnit.Framework; + using Serialization; + + + [TestFixture] + public class MessageBody_Specs + { + [Test] + public void Should_handle_cross_region_messages() + { + var body = new SqsMessageBody(new Message { Body = CrossRegionMessage }); + + JsonElement? bodyElement = body.GetJsonElement(SystemTextJsonMessageSerializer.Options); + + var envelope = bodyElement?.Deserialize(SystemTextJsonMessageSerializer.Options); + + Assert.Multiple(() => + { + Assert.That(envelope, Is.Not.Null); + Assert.That(envelope.MessageId, Is.EqualTo("00ab0000-6ab3-f8b4-f78c-08db7c8365ff")); + }); + } + + [Test] + public void Should_handle_intra_region_messages() + { + var body = new SqsMessageBody(new Message { Body = InRegionMessage }); + + JsonElement? bodyElement = body.GetJsonElement(SystemTextJsonMessageSerializer.Options); + + var envelope = bodyElement?.Deserialize(SystemTextJsonMessageSerializer.Options); + + Assert.Multiple(() => + { + Assert.That(envelope, Is.Not.Null); + Assert.That(envelope.MessageId, Is.EqualTo("00ab0000-6ab3-f8b4-f78c-08db7c8365ff")); + }); + } + + const string InRegionMessage = """ + { + "messageId": "00ab0000-6ab3-f8b4-f78c-08db7c8365ff", + "requestId": null, + "correlationId": null, + "conversationId": "00ab0000-6ab3-f8b4-739e-08db7c83660f", + "initiatorId": null, + "sourceAddress": "amazonsqs://us-east-1/some-namespace/some_address?durable=false&autodelete=true", + "destinationAddress": "amazonsqs://us-east-1/some-namespace_TestMessage.fifo?type=topic", + "responseAddress": null, + "faultAddress": null, + "messageType": [ + "urn:message:TestSerialization:TestMessage" + ], + "message": { + "name": "Hello world!" + }, + "expirationTime": null, + "sentTime": "2023-07-04T11:39:59.689102Z", + "headers": {}, + "host": + { + "machineName": "XXXX", + "processName": "Publisher", + "processId": 43776, + "assembly": "Publisher", + "assemblyVersion": "1.0.0.0", + "frameworkVersion": "6.0.14", + "massTransitVersion": "8.0.16.0", + "operatingSystemVersion": "Microsoft Windows NT 10.0.19044.0" + } + } + """; + + const string CrossRegionMessage = """ + { + "Type": "Notification", + "MessageId": "00ab0000-6ab3-f8b4-f78c-08db7c8365ff", + "SequenceNumber": "10000000000000127000", + "TopicArn": "arn:aws:sns:eu-west-1:000696323999:some-namespace_TestMessage.fifo", + "Message": "{\r\n \"messageId\": \"00ab0000-6ab3-f8b4-f78c-08db7c8365ff\",\r\n \"requestId\": null,\r\n \"correlationId\": null,\r\n \"conversationId\": \"00ab0000-6ab3-f8b4-739e-08db7c83660f\",\r\n \"initiatorId\": null,\r\n \"sourceAddress\": \"amazonsqs://us-east-1/some-namespace/some_address?durable=false&autodelete=true\",\r\n \"destinationAddress\": \"amazonsqs://us-east-1/some-namespace_TestMessage.fifo?type=topic\",\r\n \"responseAddress\": null,\r\n \"faultAddress\": null,\r\n \"messageType\": [\r\n \"urn:message:TestSerialization:TestMessage\"\r\n ],\r\n \"message\": {\r\n \"name\": \"Hello world!\"\r\n },\r\n \"expirationTime\": null,\r\n \"sentTime\": \"2023-07-04T11:39:59.689102Z\",\r\n \"headers\": {},\r\n \"host\": {\r\n \"machineName\": \"XXXX\",\r\n \"processName\": \"Publisher\",\r\n \"processId\": 43776,\r\n \"assembly\": \"Publisher\",\r\n \"assemblyVersion\": \"1.0.0.0\",\r\n \"frameworkVersion\": \"6.0.14\",\r\n \"massTransitVersion\": \"8.0.16.0\",\r\n \"operatingSystemVersion\": \"Microsoft Windows NT 10.0.19044.0\"\r\n }\r\n}", + "Timestamp": "2023-07-04T06:49:35.120Z", + "UnsubscribeURL": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:000696323999:some-namespace_TestMessage.fifo:16dc9aa0-4cc9-43eb-b0a5-48e56d5a0565", + "MessageAttributes": + { + "Content-Type": + { + "Type": "String", + "Value": "application/vnd.masstransit+json" + } + } + } + """; + } +} + + +namespace TestSerialization +{ + public class TestMessage + { + public string Name { get; set; } + } +} From 5e70659847408fc9d65d697da78ec962a54ea9e7 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 2 May 2024 09:15:24 -0500 Subject: [PATCH 15/27] Added formatting option for raw TEXT to avoid excessive indention. --- MassTransit.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/MassTransit.sln.DotSettings b/MassTransit.sln.DotSettings index 7b784e69f06..00bee3f9ac2 100644 --- a/MassTransit.sln.DotSettings +++ b/MassTransit.sln.DotSettings @@ -38,6 +38,7 @@ OUTDENT USUAL_INDENT OUTDENT + INDENT 1 False False From c9561ee99203d41f4b894761c29670f51555d7fc Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 2 May 2024 10:56:12 -0500 Subject: [PATCH 16/27] Fixed #5146 - Extend the inactivity timeout when the debugger is attached --- .../Configuration/DependencyInjection/TestHarnessOptions.cs | 2 +- src/MassTransit/Testing/AsyncTestHarness.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MassTransit/Configuration/DependencyInjection/TestHarnessOptions.cs b/src/MassTransit/Configuration/DependencyInjection/TestHarnessOptions.cs index 81e8b65d6b4..916a837a25d 100644 --- a/src/MassTransit/Configuration/DependencyInjection/TestHarnessOptions.cs +++ b/src/MassTransit/Configuration/DependencyInjection/TestHarnessOptions.cs @@ -7,6 +7,6 @@ namespace MassTransit public class TestHarnessOptions { public TimeSpan TestTimeout { get; set; } = Debugger.IsAttached ? TimeSpan.FromMinutes(50) : TimeSpan.FromSeconds(30); - public TimeSpan TestInactivityTimeout { get; set; } = TimeSpan.FromSeconds(1.2); + public TimeSpan TestInactivityTimeout { get; set; } = Debugger.IsAttached ? TimeSpan.FromMinutes(30) : TimeSpan.FromSeconds(1.2); } } diff --git a/src/MassTransit/Testing/AsyncTestHarness.cs b/src/MassTransit/Testing/AsyncTestHarness.cs index ef55feff9fc..881f7c306b0 100644 --- a/src/MassTransit/Testing/AsyncTestHarness.cs +++ b/src/MassTransit/Testing/AsyncTestHarness.cs @@ -19,7 +19,7 @@ public abstract class AsyncTestHarness : protected AsyncTestHarness() { TestTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(50) : TimeSpan.FromSeconds(30); - TestInactivityTimeout = TimeSpan.FromSeconds(6); + TestInactivityTimeout = Debugger.IsAttached ? TimeSpan.FromMinutes(30) : TimeSpan.FromSeconds(6); _inactivityObserver = new Lazy(() => new AsyncInactivityObserver(TestInactivityTimeout, TestCancellationToken)); } From 25d63732d32fc89bb696e10abfbe5bc4182cebd4 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Thu, 2 May 2024 18:39:01 -0500 Subject: [PATCH 17/27] Added ReplyTo as a settable property on the ServiceBusSendContext --- .../Contexts/AzureServiceBusSendContext.cs | 7 +++++++ .../Contexts/AzureServiceBusTransportPropertyNames.cs | 1 + .../ServiceBusSendTransportContext.cs | 3 +++ .../ServiceBusSendContext.cs | 5 +++++ .../ServiceBusSendContextExtensions.cs | 6 ++++++ 5 files changed, 22 insertions(+) diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusSendContext.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusSendContext.cs index 3bdc4b6546b..42fc8151254 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusSendContext.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusSendContext.cs @@ -42,6 +42,7 @@ public override TimeSpan? Delay } public string ReplyToSessionId { get; set; } + public string ReplyTo { get; set; } public DateTime? ScheduledEnqueueTimeUtc { get; set; } @@ -126,6 +127,10 @@ public override void ReadPropertiesFrom(IReadOnlyDictionary prop if (!string.IsNullOrWhiteSpace(replyToSessionId)) ReplyToSessionId = replyToSessionId; + var replyTo = ReadString(properties, AzureServiceBusTransportPropertyNames.ReplyTo); + if (!string.IsNullOrWhiteSpace(replyTo)) + ReplyTo = replyTo; + var label = ReadString(properties, AzureServiceBusTransportPropertyNames.Label); if (!string.IsNullOrWhiteSpace(label)) Label = label; @@ -141,6 +146,8 @@ public override void WritePropertiesTo(IDictionary properties) properties[AzureServiceBusTransportPropertyNames.SessionId] = SessionId; if (!string.IsNullOrWhiteSpace(ReplyToSessionId)) properties[AzureServiceBusTransportPropertyNames.ReplyToSessionId] = ReplyToSessionId; + if (!string.IsNullOrWhiteSpace(ReplyTo)) + properties[AzureServiceBusTransportPropertyNames.ReplyTo] = ReplyTo; if (!string.IsNullOrWhiteSpace(Label)) properties[AzureServiceBusTransportPropertyNames.Label] = Label; } diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusTransportPropertyNames.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusTransportPropertyNames.cs index a191d7b7903..78d282322e6 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusTransportPropertyNames.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Contexts/AzureServiceBusTransportPropertyNames.cs @@ -5,6 +5,7 @@ static class AzureServiceBusTransportPropertyNames public const string PartitionKey = "ASB-PartitionKey"; public const string SessionId = "ASB-SessionId"; public const string ReplyToSessionId = "ASB-ReplyToSessionId"; + public const string ReplyTo = "ASB-ReplyTo"; public const string Label = "ASB-Label"; } } diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusSendTransportContext.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusSendTransportContext.cs index f071267b8a9..c1cb4e137c0 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusSendTransportContext.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusSendTransportContext.cs @@ -201,6 +201,9 @@ static ServiceBusMessage CreateMessage(AzureServiceBusSendContext context) if (context.ReplyToSessionId != null) message.ReplyToSessionId = context.ReplyToSessionId; + if (context.ReplyTo != null) + message.ReplyTo = context.ReplyTo; + if (context.Label != null) message.Subject = context.Label; diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContext.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContext.cs index 5e275530a29..32587f3053e 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContext.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContext.cs @@ -22,6 +22,11 @@ public interface ServiceBusSendContext : /// string ReplyToSessionId { set; } + /// + /// Sets the ReplyTo address of the message + /// + string ReplyTo { set; } + /// /// Set the application specific label of the message /// diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContextExtensions.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContextExtensions.cs index e23c6d41396..9f38f0b81d3 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContextExtensions.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/ServiceBusSendContextExtensions.cs @@ -44,6 +44,12 @@ public static void SetReplyToSessionId(this SendContext context, string sessionI sendContext.ReplyToSessionId = sessionId; } + public static void SetReplyTo(this SendContext context, string replyTo) + { + if (context.TryGetPayload(out ServiceBusSendContext sendContext)) + sendContext.ReplyTo = replyTo; + } + public static void SetLabel(this SendContext context, string label) { if (context.TryGetPayload(out ServiceBusSendContext sendContext)) From 03816740c184311e883b7502a38aebe0e399ff15 Mon Sep 17 00:00:00 2001 From: Sergiu Ciudin Date: Thu, 29 Feb 2024 17:17:10 +0200 Subject: [PATCH 18/27] Fixed "Submitted policy is over max allowed size" exception, serialized access to the QueueInfo when adding subscriptions to reduce race condition. --- .../AmazonSqsClientContext.cs | 53 ++----------- .../AmazonSqsTransport/ClientContext.cs | 2 +- .../ConfigureAmazonSqsTopologyFilter.cs | 27 +++++-- .../AmazonSqsTransport/QueueCache.cs | 4 +- .../AmazonSqsTransport/QueueInfo.cs | 79 ++++++++++++++++++- .../AmazonSqsTransport/ScopeClientContext.cs | 2 +- .../AmazonSqsTransport/SharedClientContext.cs | 2 +- .../AmazonSqsTransport/TopicCache.cs | 4 +- .../AmazonSqsTransport/TopicInfo.cs | 4 +- .../RawJson_Specs.cs | 2 +- 10 files changed, 114 insertions(+), 65 deletions(-) diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsClientContext.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsClientContext.cs index 5a73412f2d6..f6e20be7c7f 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsClientContext.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/AmazonSqsClientContext.cs @@ -1,12 +1,9 @@ namespace MassTransit.AmazonSqsTransport { - using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; - using Amazon.Auth.AccessControlPolicy; - using Amazon.Auth.AccessControlPolicy.ActionIdentifiers; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Amazon.SQS; @@ -23,12 +20,10 @@ public class AmazonSqsClientContext : readonly IAmazonSimpleNotificationService _snsClient; readonly IAmazonSQS _sqsClient; - public AmazonSqsClientContext( - ConnectionContext connectionContext, + public AmazonSqsClientContext(ConnectionContext connectionContext, IAmazonSQS sqsClient, IAmazonSimpleNotificationService snsClient, - CancellationToken cancellationToken - ) + CancellationToken cancellationToken) : base(connectionContext) { ConnectionContext = connectionContext; @@ -52,7 +47,7 @@ public Task CreateQueue(Queue queue) return ConnectionContext.GetQueue(queue); } - public async Task CreateQueueSubscription(Topology.Topic topic, Queue queue) + public async Task CreateQueueSubscription(Topology.Topic topic, Queue queue) { var topicInfo = await ConnectionContext.GetTopic(topic).ConfigureAwait(false); var queueInfo = await ConnectionContext.GetQueue(queue).ConfigureAwait(false); @@ -78,38 +73,14 @@ public async Task CreateQueueSubscription(Topology.Topic topic, Queue queue) } catch (InvalidParameterException exception) when (exception.Message.Contains("exists")) { - return; + return false; } queueInfo.SubscriptionArns.Add(response.SubscriptionArn); var sqsQueueArn = queueInfo.Arn; - queueInfo.Attributes.TryGetValue(QueueAttributeName.Policy, out var policyValue); - var policy = string.IsNullOrEmpty(policyValue) - ? new Policy() - : Policy.FromJson(policyValue); - - if (!QueueHasTopicPermission(policy, topicInfo.Arn, sqsQueueArn)) - { - var statement = new Statement(Statement.StatementEffect.Allow); - #pragma warning disable 618 - statement.Actions.Add(SQSActionIdentifiers.SendMessage); - #pragma warning restore 618 - statement.Resources.Add(new Resource(sqsQueueArn)); - statement.Conditions.Add(ConditionFactory.NewSourceArnCondition(topicInfo.Arn)); - statement.Principals.Add(new Principal("Service","sns.amazonaws.com")); - policy.Statements.Add(statement); - - var jsonPolicy = policy.ToJson(); - - var setAttributes = new Dictionary { { QueueAttributeName.Policy, jsonPolicy } }; - var setAttributesResponse = await _sqsClient.SetQueueAttributesAsync(queueInfo.Url, setAttributes, CancellationToken).ConfigureAwait(false); - - setAttributesResponse.EnsureSuccessfulResponse(); - - queueInfo.Attributes[QueueAttributeName.Policy] = jsonPolicy; - } + return await queueInfo.UpdatePolicy(sqsQueueArn, topicInfo.Arn, CancellationToken).ConfigureAwait(false); } public async Task DeleteTopic(Topology.Topic topic) @@ -226,19 +197,5 @@ async Task DeleteQueueSubscription(string subscriptionArn) response.EnsureSuccessfulResponse(); } - - static bool QueueHasTopicPermission(Policy policy, string topicArn, string sqsQueueArn) - { - var topicArnPattern = topicArn.Substring(0, topicArn.LastIndexOf(':') + 1) + "*"; - - IEnumerable conditions = policy.Statements - .Where(s => s.Resources.Any(r => r.Id.Equals(sqsQueueArn))) - .SelectMany(s => s.Conditions); - - return conditions.Any(c => - string.Equals(c.Type, ConditionFactory.ArnComparisonType.ArnLike.ToString(), StringComparison.OrdinalIgnoreCase) && - string.Equals(c.ConditionKey, ConditionFactory.SOURCE_ARN_CONDITION_KEY, StringComparison.OrdinalIgnoreCase) && - c.Values.Any(v => v == topicArnPattern || v == topicArn)); - } } } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ClientContext.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ClientContext.cs index 6deba07e273..44487d5678a 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ClientContext.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ClientContext.cs @@ -17,7 +17,7 @@ public interface ClientContext : Task CreateQueue(Queue queue); - Task CreateQueueSubscription(Topology.Topic topic, Queue queue); + Task CreateQueueSubscription(Topology.Topic topic, Queue queue); Task DeleteTopic(Topology.Topic topic); diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Middleware/ConfigureAmazonSqsTopologyFilter.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Middleware/ConfigureAmazonSqsTopologyFilter.cs index 521790be493..658f384f998 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Middleware/ConfigureAmazonSqsTopologyFilter.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Middleware/ConfigureAmazonSqsTopologyFilter.cs @@ -80,9 +80,17 @@ bool AnyAutoDelete() return _brokerTopology.Topics.Any(x => x.AutoDelete) || _brokerTopology.Queues.Any(x => x.AutoDelete); } - async Task Declare(ClientContext context, Topic topic) + static async Task Declare(ClientContext context, Topic topic) { var topicInfo = await context.CreateTopic(topic).ConfigureAwait(false); + + if (topicInfo.Existing) + { + LogContext.Debug?.Log("Existing topic {Topic} {TopicArn}", topicInfo.EntityName, topicInfo.Arn); + return topicInfo; + } + + // Why? I don't know, but damn, it takes two times, or it doesn't catch properly topicInfo = await context.CreateTopic(topic).ConfigureAwait(false); LogContext.Debug?.Log("Created topic {Topic} {TopicArn}", topicInfo.EntityName, topicInfo.Arn); @@ -90,18 +98,23 @@ async Task Declare(ClientContext context, Topic topic) return topicInfo; } - Task Declare(ClientContext context, QueueSubscription subscription) + static async Task Declare(ClientContext context, QueueSubscription subscription) { - LogContext.Debug?.Log("Binding topic {Topic} to {Queue}", subscription.Source, subscription.Destination); - - return context.CreateQueueSubscription(subscription.Source, subscription.Destination); + var created = await context.CreateQueueSubscription(subscription.Source, subscription.Destination).ConfigureAwait(false); + LogContext.Debug?.Log(created ? "Created subscription {Topic} to {Queue}" : "Existing subscription {Topic} to {Queue}", + subscription.Source, subscription.Destination); } - async Task Declare(ClientContext context, Queue queue) + static async Task Declare(ClientContext context, Queue queue) { - // Why? I don't know, but damn, it takes two times, or it doesn't catch properly var queueInfo = await context.CreateQueue(queue).ConfigureAwait(false); + if (queueInfo.Existing) + { + LogContext.Debug?.Log("Existing queue {Queue} {QueueArn} {QueueUrl}", queueInfo.EntityName, queueInfo.Arn, queueInfo.Url); + return queueInfo; + } + // Why? I don't know, but damn, it takes two times, or it doesn't catch properly queueInfo = await context.CreateQueue(queue).ConfigureAwait(false); LogContext.Debug?.Log("Created queue {Queue} {QueueArn} {QueueUrl}", queueInfo.EntityName, queueInfo.Arn, queueInfo.Url); diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueCache.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueCache.cs index 8f1e68b02fe..c0659201621 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueCache.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueCache.cs @@ -112,7 +112,7 @@ async Task CreateMissingQueue(Queue queue) attributesResponse.EnsureSuccessfulResponse(); - var missingQueue = new QueueInfo(queue.EntityName, createResponse.QueueUrl, attributesResponse.Attributes, _client, _cancellationToken); + var missingQueue = new QueueInfo(queue.EntityName, createResponse.QueueUrl, attributesResponse.Attributes, _client, _cancellationToken, false); if (queue.Durable && queue.AutoDelete == false) { @@ -133,7 +133,7 @@ async Task GetExistingQueue(string queueName) attributesResponse.EnsureSuccessfulResponse(); - return new QueueInfo(queueName, urlResponse.QueueUrl, attributesResponse.Attributes, _client, _cancellationToken); + return new QueueInfo(queueName, urlResponse.QueueUrl, attributesResponse.Attributes, _client, _cancellationToken, true); } } } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueInfo.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueInfo.cs index c87280377c9..9febffe0cf0 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueInfo.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/QueueInfo.cs @@ -2,8 +2,11 @@ namespace MassTransit.AmazonSqsTransport { using System; using System.Collections.Generic; + using System.Linq; using System.Threading; using System.Threading.Tasks; + using Amazon.Auth.AccessControlPolicy; + using Amazon.Auth.AccessControlPolicy.ActionIdentifiers; using Amazon.SQS; using Amazon.SQS.Model; @@ -13,11 +16,16 @@ public class QueueInfo : { readonly Lazy> _batchDeleter; readonly Lazy> _batchSender; + readonly IAmazonSQS _client; + readonly SemaphoreSlim _updateSemaphore; bool _disposed; - public QueueInfo(string entityName, string url, IDictionary attributes, IAmazonSQS client, CancellationToken cancellationToken) + public QueueInfo(string entityName, string url, IDictionary attributes, IAmazonSQS client, CancellationToken cancellationToken, + bool existing) { + _client = client; Attributes = attributes; + Existing = existing; EntityName = entityName; Url = url; @@ -25,6 +33,8 @@ public QueueInfo(string entityName, string url, IDictionary attr ? queueArn : throw new ArgumentException($"The queueArn was not found: {url}", nameof(attributes)); + _updateSemaphore = new SemaphoreSlim(1); + _batchSender = new Lazy>(() => new SendBatcher(client, url, cancellationToken)); _batchDeleter = new Lazy>(() => new DeleteBatcher(client, url, cancellationToken)); @@ -36,6 +46,7 @@ public QueueInfo(string entityName, string url, IDictionary attr public string Arn { get; } public IDictionary Attributes { get; } public IList SubscriptionArns { get; } + public bool Existing { get; } public async ValueTask DisposeAsync() { @@ -44,6 +55,8 @@ public async ValueTask DisposeAsync() _disposed = true; + _updateSemaphore.Dispose(); + if (_batchSender.IsValueCreated) await _batchSender.Value.DisposeAsync().ConfigureAwait(false); if (_batchDeleter.IsValueCreated) @@ -61,5 +74,69 @@ public Task Delete(string receiptHandle, CancellationToken cancellationToken) return _batchDeleter.Value.Execute(entry, cancellationToken); } + + public async Task UpdatePolicy(string sqsQueueArn, string topicArn, CancellationToken cancellationToken) + { + await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + Attributes.TryGetValue(QueueAttributeName.Policy, out var policyValue); + var policy = string.IsNullOrEmpty(policyValue) + ? new Policy() + : Policy.FromJson(policyValue); + + if (QueueHasTopicPermission(policy, topicArn, sqsQueueArn)) + return false; + + var statement = policy.Statements.FirstOrDefault(); + if (statement is null) + { + statement = new Statement(Statement.StatementEffect.Allow); + #pragma warning disable 618 + statement.Actions.Add(SQSActionIdentifiers.SendMessage); + #pragma warning restore 618 + statement.Resources.Add(new Resource(sqsQueueArn)); + statement.Conditions.Add(ConditionFactory.NewSourceArnCondition(topicArn)); + statement.Principals.Add(new Principal("Service", "sns.amazonaws.com")); + policy.Statements.Add(statement); + } + + var condition = statement.Conditions.First(); + condition.Values = condition + .Values + .Append(topicArn) + .Distinct() + .ToArray(); + + var jsonPolicy = policy.ToJson(); + + var setAttributes = new Dictionary { { QueueAttributeName.Policy, jsonPolicy } }; + var setAttributesResponse = await _client.SetQueueAttributesAsync(Url, setAttributes, cancellationToken).ConfigureAwait(false); + + setAttributesResponse.EnsureSuccessfulResponse(); + + Attributes[QueueAttributeName.Policy] = jsonPolicy; + + return true; + } + finally + { + _updateSemaphore.Release(); + } + } + + static bool QueueHasTopicPermission(Policy policy, string topicArn, string sqsQueueArn) + { + var topicArnPattern = topicArn.Substring(0, topicArn.LastIndexOf(':') + 1) + "*"; + + IEnumerable conditions = policy.Statements + .Where(s => s.Resources.Any(r => r.Id.Equals(sqsQueueArn))) + .SelectMany(s => s.Conditions); + + return conditions.Any(c => + string.Equals(c.Type, ConditionFactory.ArnComparisonType.ArnLike.ToString(), StringComparison.OrdinalIgnoreCase) && + string.Equals(c.ConditionKey, ConditionFactory.SOURCE_ARN_CONDITION_KEY, StringComparison.OrdinalIgnoreCase) && + c.Values.Any(v => v == topicArnPattern || v == topicArn)); + } } } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ScopeClientContext.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ScopeClientContext.cs index 544197a6ad6..ef053dc845b 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ScopeClientContext.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/ScopeClientContext.cs @@ -36,7 +36,7 @@ public Task CreateQueue(Queue queue) return _context.CreateQueue(queue); } - public Task CreateQueueSubscription(Topology.Topic topic, Queue queue) + public Task CreateQueueSubscription(Topology.Topic topic, Queue queue) { return _context.CreateQueueSubscription(topic, queue); } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SharedClientContext.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SharedClientContext.cs index d65b3590e1d..66f7ba73789 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SharedClientContext.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/SharedClientContext.cs @@ -36,7 +36,7 @@ public Task CreateQueue(Queue queue) return _context.CreateQueue(queue); } - public Task CreateQueueSubscription(Topology.Topic topic, Queue queue) + public Task CreateQueueSubscription(Topology.Topic topic, Queue queue) { return _context.CreateQueueSubscription(topic, queue); } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicCache.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicCache.cs index aa8491e35e7..b8b3725ba97 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicCache.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicCache.cs @@ -106,7 +106,7 @@ async Task CreateMissingTopic(Topology.Topic topic) attributesResponse.EnsureSuccessfulResponse(); - var missingTopic = new TopicInfo(topic.EntityName, createResponse.TopicArn, _client, _cancellationToken); + var missingTopic = new TopicInfo(topic.EntityName, createResponse.TopicArn, _client, _cancellationToken, false); if (topic.Durable && topic.AutoDelete == false) { @@ -157,7 +157,7 @@ async Task LoadExistingTopicsLazy(CancellationToken token) await _cache.GetOrAdd(topicName, async key => { - var topicInfo = new TopicInfo(topicName, topic.TopicArn, _client, _cancellationToken); + var topicInfo = new TopicInfo(topicName, topic.TopicArn, _client, _cancellationToken, true); lock (_durableTopics) _durableTopics[topicInfo.EntityName] = topicInfo; diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicInfo.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicInfo.cs index 831ab5a2d6b..987d8395c16 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicInfo.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/TopicInfo.cs @@ -13,16 +13,18 @@ public class TopicInfo : readonly Lazy> _batchPublisher; bool _disposed; - public TopicInfo(string entityName, string arn, IAmazonSimpleNotificationService client, CancellationToken cancellationToken) + public TopicInfo(string entityName, string arn, IAmazonSimpleNotificationService client, CancellationToken cancellationToken, bool existing) { EntityName = entityName; Arn = arn; + Existing = existing; _batchPublisher = new Lazy>(() => new PublishBatcher(client, arn, cancellationToken)); } public string EntityName { get; } public string Arn { get; } + public bool Existing { get; } public async ValueTask DisposeAsync() { diff --git a/tests/MassTransit.AmazonSqsTransport.Tests/RawJson_Specs.cs b/tests/MassTransit.AmazonSqsTransport.Tests/RawJson_Specs.cs index 5b904c1f814..42e3063948c 100644 --- a/tests/MassTransit.AmazonSqsTransport.Tests/RawJson_Specs.cs +++ b/tests/MassTransit.AmazonSqsTransport.Tests/RawJson_Specs.cs @@ -153,7 +153,7 @@ protected override void ConfigureAmazonSqsBus(IAmazonSqsBusFactoryConfigurator c protected override void ConfigureAmazonSqsReceiveEndpoint(IAmazonSqsReceiveEndpointConfigurator configurator) { - configurator.UseRawJsonDeserializer(); + configurator.UseRawJsonDeserializer(RawSerializerOptions.AnyMessageType | RawSerializerOptions.AddTransportHeaders); TaskCompletionSource> handler = GetTask>(); _handler = handler.Task; From 3e8be46dc1e886a112e9c82439c3f36b3706a8eb Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 3 May 2024 11:11:39 -0500 Subject: [PATCH 19/27] Added ConfigureConsumeTopology overload that accepts a messageType parameter, fixed some topology bugs in publish/consume --- .../IReceiveEndpointConfigurator.cs | 6 ++ .../Topology/IConsumeTopologyConfigurator.cs | 15 ++-- .../IMessageConsumeTopologyConfigurator.cs | 12 +-- .../IMessagePublishTopologyConfigurator.cs | 2 + .../IMessageSendTopologyConfigurator.cs | 1 + .../Topology/MessagePublishTopology.cs | 6 ++ .../Topology/Topology/MessageSendTopology.cs | 6 ++ .../Topology/Topology/PublishTopology.cs | 69 +++++++--------- .../Topology/Topology/SendTopology.cs | 10 ++- .../ReceiveEndpointConfiguration.cs | 5 ++ .../Configuration/ReceiverConfiguration.cs | 4 + .../InMemoryConsumeTopology.cs | 2 +- .../Topology/SqlConsumeTopology.cs | 2 +- src/MassTransit/Topology/ConsumeTopology.cs | 82 ++++++++++++++----- .../Topology/ActiveMqConsumeTopology.cs | 2 +- .../Topology/AmazonSqsConsumeTopology.cs | 2 +- .../Topology/ServiceBusConsumeTopology.cs | 2 +- .../Topology/RabbitMqConsumeTopology.cs | 2 +- .../MessageTopology_Specs.cs | 32 ++++++++ 19 files changed, 179 insertions(+), 83 deletions(-) diff --git a/src/MassTransit.Abstractions/Configuration/Middleware/IReceiveEndpointConfigurator.cs b/src/MassTransit.Abstractions/Configuration/Middleware/IReceiveEndpointConfigurator.cs index 4b2ece69610..b4231ba427c 100644 --- a/src/MassTransit.Abstractions/Configuration/Middleware/IReceiveEndpointConfigurator.cs +++ b/src/MassTransit.Abstractions/Configuration/Middleware/IReceiveEndpointConfigurator.cs @@ -60,6 +60,12 @@ public interface IReceiveEndpointConfigurator : void ConfigureMessageTopology(bool enabled = true) where T : class; + /// + /// Configures whether the broker topology is configured for the specified message type. Related to + /// , but for an individual message type. + /// + void ConfigureMessageTopology(Type messageType, bool enabled = true); + [EditorBrowsable(EditorBrowsableState.Never)] void AddEndpointSpecification(IReceiveEndpointSpecification configurator); diff --git a/src/MassTransit.Abstractions/Configuration/Topology/IConsumeTopologyConfigurator.cs b/src/MassTransit.Abstractions/Configuration/Topology/IConsumeTopologyConfigurator.cs index 5149dc1fd80..4ff33747245 100644 --- a/src/MassTransit.Abstractions/Configuration/Topology/IConsumeTopologyConfigurator.cs +++ b/src/MassTransit.Abstractions/Configuration/Topology/IConsumeTopologyConfigurator.cs @@ -1,5 +1,6 @@ namespace MassTransit { + using System; using Configuration; @@ -15,19 +16,17 @@ public interface IConsumeTopologyConfigurator : new IMessageConsumeTopologyConfigurator GetMessageTopology() where T : class; + /// + /// Returns the specification for the message type + /// + /// + IMessageConsumeTopologyConfigurator GetMessageTopology(Type messageType); + /// /// Adds a convention to the topology, which will be applied to every message type /// requested, to determine if a convention for the message type is available. /// /// The Consume topology convention bool TryAddConvention(IConsumeTopologyConvention convention); - - /// - /// Add a Consume topology for a specific message type - /// - /// The message type - /// The topology - void AddMessageConsumeTopology(IMessageConsumeTopology topology) - where T : class; } } diff --git a/src/MassTransit.Abstractions/Configuration/Topology/IMessageConsumeTopologyConfigurator.cs b/src/MassTransit.Abstractions/Configuration/Topology/IMessageConsumeTopologyConfigurator.cs index 15a4841f5f2..71b3180f072 100644 --- a/src/MassTransit.Abstractions/Configuration/Topology/IMessageConsumeTopologyConfigurator.cs +++ b/src/MassTransit.Abstractions/Configuration/Topology/IMessageConsumeTopologyConfigurator.cs @@ -14,12 +14,6 @@ public interface IMessageConsumeTopologyConfigurator : IMessageConsumeTopology where TMessage : class { - /// - /// Specify whether the broker topology should be configured for this message type - /// (defaults to true) - /// - bool ConfigureConsumeTopology { get; set; } - void Add(IMessageConsumeTopology consumeTopology); /// @@ -60,6 +54,12 @@ void AddOrUpdateConvention(Func add, Func + /// Specify whether the broker topology should be configured for this message type + /// (defaults to true) + /// + bool ConfigureConsumeTopology { get; set; } + bool TryAddConvention(IConsumeTopologyConvention convention); } } diff --git a/src/MassTransit.Abstractions/Configuration/Topology/IMessagePublishTopologyConfigurator.cs b/src/MassTransit.Abstractions/Configuration/Topology/IMessagePublishTopologyConfigurator.cs index 0e399b0f39c..fd2a3bac811 100644 --- a/src/MassTransit.Abstractions/Configuration/Topology/IMessagePublishTopologyConfigurator.cs +++ b/src/MassTransit.Abstractions/Configuration/Topology/IMessagePublishTopologyConfigurator.cs @@ -50,5 +50,7 @@ public interface IMessagePublishTopologyConfigurator : /// Exclude the message type from being created as a topic/exchange. /// new bool Exclude { set; } + + bool TryAddConvention(IPublishTopologyConvention convention); } } diff --git a/src/MassTransit.Abstractions/Configuration/Topology/IMessageSendTopologyConfigurator.cs b/src/MassTransit.Abstractions/Configuration/Topology/IMessageSendTopologyConfigurator.cs index cb61850edfc..fb14d58c691 100644 --- a/src/MassTransit.Abstractions/Configuration/Topology/IMessageSendTopologyConfigurator.cs +++ b/src/MassTransit.Abstractions/Configuration/Topology/IMessageSendTopologyConfigurator.cs @@ -64,5 +64,6 @@ bool TryGetConvention([NotNullWhen(true)] out TConvention? conventi public interface IMessageSendTopologyConfigurator : ISpecification { + bool TryAddConvention(ISendTopologyConvention convention); } } diff --git a/src/MassTransit.Abstractions/Topology/Topology/MessagePublishTopology.cs b/src/MassTransit.Abstractions/Topology/Topology/MessagePublishTopology.cs index 68371f5b37d..6ec60a14e6e 100644 --- a/src/MassTransit.Abstractions/Topology/Topology/MessagePublishTopology.cs +++ b/src/MassTransit.Abstractions/Topology/Topology/MessagePublishTopology.cs @@ -78,6 +78,12 @@ public bool TryAddConvention(IMessagePublishTopologyConvention convent return true; } + public bool TryAddConvention(IPublishTopologyConvention convention) + { + return convention.TryGetMessagePublishTopologyConvention(out IMessagePublishTopologyConvention messagePublishTopologyConvention) + && TryAddConvention(messagePublishTopologyConvention); + } + public void AddOrUpdateConvention(Func add, Func update) where TConvention : class, IMessagePublishTopologyConvention { diff --git a/src/MassTransit.Abstractions/Topology/Topology/MessageSendTopology.cs b/src/MassTransit.Abstractions/Topology/Topology/MessageSendTopology.cs index d79498d62d8..531ef98fb98 100644 --- a/src/MassTransit.Abstractions/Topology/Topology/MessageSendTopology.cs +++ b/src/MassTransit.Abstractions/Topology/Topology/MessageSendTopology.cs @@ -77,6 +77,12 @@ public bool TryAddConvention(IMessageSendTopologyConvention convention return true; } + public bool TryAddConvention(ISendTopologyConvention convention) + { + return convention.TryGetMessageSendTopologyConvention(out IMessageSendTopologyConvention messageSendTopologyConvention) + && TryAddConvention(messageSendTopologyConvention); + } + public void UpdateConvention(Func update) where TConvention : class, IMessageSendTopologyConvention { diff --git a/src/MassTransit.Abstractions/Topology/Topology/PublishTopology.cs b/src/MassTransit.Abstractions/Topology/Topology/PublishTopology.cs index 5adbf571e5c..5cf9df73e6d 100644 --- a/src/MassTransit.Abstractions/Topology/Topology/PublishTopology.cs +++ b/src/MassTransit.Abstractions/Topology/Topology/PublishTopology.cs @@ -14,19 +14,18 @@ public class PublishTopology : { readonly List _conventions; readonly object _lock = new object(); - readonly ConcurrentDictionary _messageTypeFactoryCache; readonly ConcurrentDictionary> _messageTypes; + readonly ConcurrentDictionary _messageTypeSelectorCache; readonly PublishTopologyConfigurationObservable _observers; public PublishTopology() { _messageTypes = new ConcurrentDictionary>(); - - _observers = new PublishTopologyConfigurationObservable(); - _messageTypeFactoryCache = new ConcurrentDictionary(); + _messageTypeSelectorCache = new ConcurrentDictionary(); _conventions = new List(8); + _observers = new PublishTopologyConfigurationObservable(); _observers.Connect(this); } @@ -47,8 +46,7 @@ IMessagePublishTopologyConfigurator IPublishTopologyConfigurator.GetMessageTo public bool TryGetPublishAddress(Type messageType, Uri baseAddress, out Uri? publishAddress) { - return _messageTypes.GetOrAdd(messageType, type => new Lazy(() => CreateMessageType(type))) - .Value.TryGetPublishAddress(baseAddress, out publishAddress); + return GetMessageTopology(messageType).TryGetPublishAddress(baseAddress, out publishAddress); } public ConnectHandle ConnectPublishTopologyConfigurationObserver(IPublishTopologyConfigurationObserver observer) @@ -58,10 +56,10 @@ public ConnectHandle ConnectPublishTopologyConfigurationObserver(IPublishTopolog public bool TryAddConvention(IPublishTopologyConvention convention) { + var conventionType = convention.GetType(); + lock (_lock) { - var conventionType = convention.GetType(); - for (var i = 0; i < _conventions.Count; i++) { if (_conventions[i].GetType() == conventionType) @@ -69,8 +67,12 @@ public bool TryAddConvention(IPublishTopologyConvention convention) } _conventions.Add(convention); - return true; } + + foreach (Lazy messagePublishTopologyConfigurator in _messageTypes.Values) + messagePublishTopologyConfigurator.Value.TryAddConvention(convention); + + return true; } void IPublishTopologyConfigurator.AddMessagePublishTopology(IMessagePublishTopology topology) @@ -95,7 +97,8 @@ public IMessagePublishTopologyConfigurator GetMessageTopology(Type messageType) if (MessageTypeCache.IsValidMessageType(messageType) == false) throw new ArgumentException(MessageTypeCache.InvalidMessageTypeReason(messageType), nameof(messageType)); - return GetOrAddByMessageType(messageType).CreateMessageType(); + return _messageTypeSelectorCache.GetOrAdd(messageType, _ => Activation.Activate(messageType, new MessageTypeSelectorFactory(), this)) + .GetMessageTopology(); } protected virtual IMessagePublishTopologyConfigurator CreateMessageTopology() @@ -150,27 +153,6 @@ void ApplyConventionsToMessageTopology(IMessagePublishTopologyConfigurator } } - IMessagePublishTopologyConfigurator CreateMessageType(Type messageType) - { - return GetOrAddByMessageType(messageType).CreateMessageType(); - } - - IMessageTypeFactory GetOrAddByMessageType(Type type) - { - return _messageTypeFactoryCache.GetOrAdd(type, _ => Activation.Activate(type, new Factory(), this)); - } - - - readonly struct Factory : - IActivationType - { - public IMessageTypeFactory ActivateType(PublishTopology publishTopology) - where T : class - { - return new MessageTypeFactory(publishTopology); - } - } - class ImplementedMessageTypeConnector : IImplementedMessageType @@ -190,26 +172,37 @@ public void ImplementsMessageType(bool direct) } - interface IMessageTypeFactory + readonly struct MessageTypeSelectorFactory : + IActivationType + { + public IMessageTypeSelector ActivateType(PublishTopology consumeTopology) + where T : class + { + return new MessageTypeSelector(consumeTopology); + } + } + + + interface IMessageTypeSelector { - IMessagePublishTopologyConfigurator CreateMessageType(); + IMessagePublishTopologyConfigurator GetMessageTopology(); } - class MessageTypeFactory : - IMessageTypeFactory + class MessageTypeSelector : + IMessageTypeSelector where T : class { readonly PublishTopology _publishTopology; - public MessageTypeFactory(PublishTopology publishTopology) + public MessageTypeSelector(PublishTopology publishTopology) { _publishTopology = publishTopology; } - public IMessagePublishTopologyConfigurator CreateMessageType() + public IMessagePublishTopologyConfigurator GetMessageTopology() { - return _publishTopology.CreateMessageTopology(); + return _publishTopology.GetMessageTopology(); } } } diff --git a/src/MassTransit.Abstractions/Topology/Topology/SendTopology.cs b/src/MassTransit.Abstractions/Topology/Topology/SendTopology.cs index 27440e6519e..2a6bdfe4670 100644 --- a/src/MassTransit.Abstractions/Topology/Topology/SendTopology.cs +++ b/src/MassTransit.Abstractions/Topology/Topology/SendTopology.cs @@ -57,10 +57,10 @@ public ConnectHandle ConnectSendTopologyConfigurationObserver(ISendTopologyConfi public bool TryAddConvention(ISendTopologyConvention convention) { + var conventionType = convention.GetType(); + lock (_lock) { - var conventionType = convention.GetType(); - for (var i = 0; i < _conventions.Count; i++) { if (_conventions[i].GetType() == conventionType) @@ -68,8 +68,12 @@ public bool TryAddConvention(ISendTopologyConvention convention) } _conventions.Add(convention); - return true; } + + foreach (Lazy messageSendTopologyConfigurator in _messageTypes.Values) + messageSendTopologyConfigurator.Value.TryAddConvention(convention); + + return true; } void ISendTopologyConfigurator.AddMessageSendTopology(IMessageSendTopology topology) diff --git a/src/MassTransit/Configuration/Configuration/ReceiveEndpointConfiguration.cs b/src/MassTransit/Configuration/Configuration/ReceiveEndpointConfiguration.cs index a253815095d..37238237129 100644 --- a/src/MassTransit/Configuration/Configuration/ReceiveEndpointConfiguration.cs +++ b/src/MassTransit/Configuration/Configuration/ReceiveEndpointConfiguration.cs @@ -105,6 +105,11 @@ public void ConfigureMessageTopology(bool enabled = true) Topology.Consume.GetMessageTopology().ConfigureConsumeTopology = enabled; } + public void ConfigureMessageTopology(Type messageType, bool enabled = true) + { + Topology.Consume.GetMessageTopology(messageType).ConfigureConsumeTopology = enabled; + } + public void AddDependency(IReceiveEndpointDependency dependency) { _dependencies.Add(dependency); diff --git a/src/MassTransit/Configuration/Configuration/ReceiverConfiguration.cs b/src/MassTransit/Configuration/Configuration/ReceiverConfiguration.cs index b64c9426af9..9cc9a969fef 100644 --- a/src/MassTransit/Configuration/Configuration/ReceiverConfiguration.cs +++ b/src/MassTransit/Configuration/Configuration/ReceiverConfiguration.cs @@ -54,6 +54,10 @@ public void ConfigureMessageTopology(bool enabled = true) { } + public void ConfigureMessageTopology(Type messageType, bool enabled = true) + { + } + public void AddEndpointSpecification(IReceiveEndpointSpecification specification) { Specifications.Add(specification); diff --git a/src/MassTransit/InMemoryTransport/InMemoryTransport/InMemoryConsumeTopology.cs b/src/MassTransit/InMemoryTransport/InMemoryTransport/InMemoryConsumeTopology.cs index e4b1b2d7f16..c919b41b414 100644 --- a/src/MassTransit/InMemoryTransport/InMemoryTransport/InMemoryConsumeTopology.cs +++ b/src/MassTransit/InMemoryTransport/InMemoryTransport/InMemoryConsumeTopology.cs @@ -63,7 +63,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var topology = new InMemoryMessageConsumeTopology(_messageTopology.GetMessageTopology(), _publishTopology); diff --git a/src/MassTransit/SqlTransport/SqlTransport/Topology/SqlConsumeTopology.cs b/src/MassTransit/SqlTransport/SqlTransport/Topology/SqlConsumeTopology.cs index 8f0eaec038e..07699719912 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/Topology/SqlConsumeTopology.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/Topology/SqlConsumeTopology.cs @@ -65,7 +65,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var messageTopology = new SqlMessageConsumeTopology(_publishTopology.GetMessageTopology()); diff --git a/src/MassTransit/Topology/ConsumeTopology.cs b/src/MassTransit/Topology/ConsumeTopology.cs index fa6adc09bd7..1de22c27b25 100644 --- a/src/MassTransit/Topology/ConsumeTopology.cs +++ b/src/MassTransit/Topology/ConsumeTopology.cs @@ -7,6 +7,7 @@ namespace MassTransit using System.Security.Cryptography; using System.Text; using Configuration; + using Metadata; using NewIdFormatters; @@ -15,19 +16,22 @@ public class ConsumeTopology : IConsumeTopologyConfigurationObserver { readonly List _conventions; - readonly object _lock = new object(); + readonly object _lock = new(); readonly int _maxQueueNameLength; readonly ConcurrentDictionary> _messageTypes; + readonly ConcurrentDictionary _messageTypeSelectorCache; readonly ConsumeTopologyConfigurationObservable _observers; protected ConsumeTopology(int maxQueueNameLength = 1024) { _maxQueueNameLength = maxQueueNameLength; - _messageTypes = new ConcurrentDictionary>(); - _observers = new ConsumeTopologyConfigurationObservable(); + _messageTypes = new ConcurrentDictionary>(); + _messageTypeSelectorCache = new ConcurrentDictionary(); _conventions = new List(8); + + _observers = new ConsumeTopologyConfigurationObservable(); _observers.Connect(this); } @@ -41,14 +45,20 @@ IMessageConsumeTopology IConsumeTopology.GetMessageTopology() return GetMessageTopology(); } - public virtual string CreateTemporaryQueueName(string tag) + IMessageConsumeTopologyConfigurator IConsumeTopologyConfigurator.GetMessageTopology() { - return ShrinkToFit(DefaultEndpointNameFormatter.GetTemporaryQueueName(tag), _maxQueueNameLength); + return GetMessageTopology(); } - IMessageConsumeTopologyConfigurator IConsumeTopologyConfigurator.GetMessageTopology() + public IMessageConsumeTopologyConfigurator GetMessageTopology(Type messageType) { - return GetMessageTopology(); + return _messageTypeSelectorCache.GetOrAdd(messageType, _ => Activation.Activate(messageType, new MessageTypeSelectorFactory(), this)) + .GetMessageTopology(); + } + + public virtual string CreateTemporaryQueueName(string tag) + { + return ShrinkToFit(DefaultEndpointNameFormatter.GetTemporaryQueueName(tag), _maxQueueNameLength); } public ConnectHandle ConnectConsumeTopologyConfigurationObserver(IConsumeTopologyConfigurationObserver observer) @@ -58,10 +68,10 @@ public ConnectHandle ConnectConsumeTopologyConfigurationObserver(IConsumeTopolog public bool TryAddConvention(IConsumeTopologyConvention convention) { + var conventionType = convention.GetType(); + lock (_lock) { - var conventionType = convention.GetType(); - for (var i = 0; i < _conventions.Count; i++) { if (_conventions[i].GetType() == conventionType) @@ -69,12 +79,12 @@ public bool TryAddConvention(IConsumeTopologyConvention convention) } _conventions.Add(convention); + } - foreach (Lazy messageConsumeTopologyConfigurator in _messageTypes.Values) - messageConsumeTopologyConfigurator.Value.TryAddConvention(convention); + foreach (Lazy messageConsumeTopologyConfigurator in _messageTypes.Values) + messageConsumeTopologyConfigurator.Value.TryAddConvention(convention); - return true; - } + return true; } public virtual IEnumerable Validate() @@ -82,13 +92,6 @@ public virtual IEnumerable Validate() return _messageTypes.Values.SelectMany(x => x.Value.Validate()); } - void IConsumeTopologyConfigurator.AddMessageConsumeTopology(IMessageConsumeTopology topology) - { - IMessageConsumeTopologyConfigurator messageConfiguration = GetMessageTopology(); - - messageConfiguration.Add(topology); - } - static string ShrinkToFit(string inputName, int maxLength) { string name; @@ -117,7 +120,7 @@ protected IMessageConsumeTopologyConfigurator GetMessageTopology() throw new ArgumentException(MessageTypeCache.InvalidMessageTypeReason, nameof(T)); Lazy specification = _messageTypes.GetOrAdd(typeof(T), - type => new Lazy(() => CreateMessageTopology(type))); + _ => new Lazy(() => CreateMessageTopology())); return specification.Value as IMessageConsumeTopologyConfigurator; } @@ -175,7 +178,7 @@ protected void ForEach(Action callback) } } - protected virtual IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected virtual IMessageConsumeTopologyConfigurator CreateMessageTopology() where T : class { var messageTopology = new MessageConsumeTopology(); @@ -203,5 +206,40 @@ void ApplyConventionsToMessageTopology(IMessageConsumeTopologyConfigurator messageTopology.TryAddConvention(messageConsumeTopologyConvention); } } + + + readonly struct MessageTypeSelectorFactory : + IActivationType + { + public IMessageTypeSelector ActivateType(ConsumeTopology consumeTopology) + where T : class + { + return new MessageTypeSelector(consumeTopology); + } + } + + + interface IMessageTypeSelector + { + IMessageConsumeTopologyConfigurator GetMessageTopology(); + } + + + class MessageTypeSelector : + IMessageTypeSelector + where T : class + { + readonly ConsumeTopology _consumeTopology; + + public MessageTypeSelector(ConsumeTopology consumeTopology) + { + _consumeTopology = consumeTopology; + } + + public IMessageConsumeTopologyConfigurator GetMessageTopology() + { + return _consumeTopology.GetMessageTopology(); + } + } } } diff --git a/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Topology/ActiveMqConsumeTopology.cs b/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Topology/ActiveMqConsumeTopology.cs index 9d0bd4e281f..f7f1291a381 100644 --- a/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Topology/ActiveMqConsumeTopology.cs +++ b/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Topology/ActiveMqConsumeTopology.cs @@ -85,7 +85,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var messageTopology = new ActiveMqMessageConsumeTopology(_publishTopology.GetMessageTopology(), ConsumerEndpointQueueNameFormatter); diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Topology/AmazonSqsConsumeTopology.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Topology/AmazonSqsConsumeTopology.cs index 3f82d02fec3..681b7446d82 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Topology/AmazonSqsConsumeTopology.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Topology/AmazonSqsConsumeTopology.cs @@ -63,7 +63,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var messageTopology = new AmazonSqsMessageConsumeTopology(_messageTopology.GetMessageTopology(), _publishTopology); diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/ServiceBusConsumeTopology.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/ServiceBusConsumeTopology.cs index ddeacd40e04..541f9f39fe2 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/ServiceBusConsumeTopology.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/Topology/ServiceBusConsumeTopology.cs @@ -76,7 +76,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var messageTopology = new ServiceBusMessageConsumeTopology(_messageTopology.GetMessageTopology(), _publishTopology.GetMessageTopology()); diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Topology/RabbitMqConsumeTopology.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Topology/RabbitMqConsumeTopology.cs index 4b5e6e3759b..d6dd40c5366 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Topology/RabbitMqConsumeTopology.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Topology/RabbitMqConsumeTopology.cs @@ -85,7 +85,7 @@ public override IEnumerable Validate() return base.Validate().Concat(_specifications.SelectMany(x => x.Validate())); } - protected override IMessageConsumeTopologyConfigurator CreateMessageTopology(Type type) + protected override IMessageConsumeTopologyConfigurator CreateMessageTopology() { var exchangeTypeSelector = new MessageExchangeTypeSelector(ExchangeTypeSelector); diff --git a/tests/MassTransit.RabbitMqTransport.Tests/MessageTopology_Specs.cs b/tests/MassTransit.RabbitMqTransport.Tests/MessageTopology_Specs.cs index d55d7690ae6..7bff7be114f 100644 --- a/tests/MassTransit.RabbitMqTransport.Tests/MessageTopology_Specs.cs +++ b/tests/MassTransit.RabbitMqTransport.Tests/MessageTopology_Specs.cs @@ -34,4 +34,36 @@ class MessageOne public string Value { get; set; } } } + + + [TestFixture] + public class Disabling_consume_topology_for_one_message_by_type : + RabbitMqTestFixture + { + [Test] + public async Task Should_only_get_the_consumed_message() + { + await Bus.Publish(new MessageTwo { Value = "Invalid" }); + await InputQueueSendEndpoint.Send(new MessageTwo { Value = "Valid" }); + + ConsumeContext handled = await _handled; + + Assert.That(handled.Message.Value, Is.EqualTo("Valid")); + } + + Task> _handled; + + protected override void ConfigureRabbitMqReceiveEndpoint(IRabbitMqReceiveEndpointConfigurator configurator) + { + configurator.ConfigureMessageTopology(typeof(MessageTwo), false); + + _handled = Handled(configurator); + } + + + class MessageTwo + { + public string Value { get; set; } + } + } } From 6a81e2d2d8aa8fdd5d4f0d26b495723f3e4688d8 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 8 May 2024 10:28:05 -0500 Subject: [PATCH 20/27] Added endpoint strategies to the documentation --- .../2.configuration/0.index.md | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/doc/content/3.documentation/2.configuration/0.index.md b/doc/content/3.documentation/2.configuration/0.index.md index f56f24a4a4e..5f6051f6899 100755 --- a/doc/content/3.documentation/2.configuration/0.index.md +++ b/doc/content/3.documentation/2.configuration/0.index.md @@ -173,6 +173,47 @@ x.AddConfigureEndpointsCallback((name, cfg) => }); ``` +## Endpoint Strategies + +Deciding how to configure receive endpoints in your application can be easy or hard, depending upon how much energy you want to spend being concerned with things that usually don't matter. However, there are nuances to the following approaches that should be considered. + +### One Consumer for Each Queue + +Creates a queue for each registered consumer, saga, and routing slip activity. Separate queues are created for execute and compensate if compensation is supported by the activity. + +::alert{type="info"} +This is the preferred approach since it ensures that every consumer can be configured independently, including retries, delivery, and the outbox. It also ensures that messages for a consumer are not stuck behind other messages for other consumers sharing the same queue. +:: + +### Multiple Consumers on a Single Queue + +Configuring multiple consumers, while fully supported by MassTransit, may make sense in certain circumstances, however, proceed with caution as there are limitations to this approach. + +The recommendation here is to configure multiple consumers on a single queue only when those consumers are closely related in terms of business function and each consumer consumes distinct message types. An example might be consumers that each create, update, or delete an entity when the dependencies of those operations are different – create and update may depend upon a validation component, while delete may not share that dependency. + +#### Consume Multiple Message Types + +In situations where it is preferable to consume multiple message types from a single queue, create a consumer that consumes multiple message types by adding more IConsumer interface implementations to the consumer class. + +```csharp +public class AddressConsumer : + IConsumer, + IConsumer +{ +} +``` + +Sagas follow this approach, creating a single queue for each saga and configuring the broker to route message types consumed by the saga that are published to topic/exchanges to the saga’s queue. + +### All Consumers on a Single Queue + +This is never a good idea and is highly discouraged. While it is supported by MassTransit, it’s unlikely to be operationally sustainable. + +Routing slip activities must not be configured on a single queue as they will not work properly. + + + + ## Endpoint Name Formatters _ConfigureEndpoints_ uses an `IEndpointNameFormatter` to format the queue names for all supported consumer types. The default endpoint name formatter returns _PascalCase_ class names without the namespace. There are several built-in endpoint name formatters included. For the _SubmitOrderConsumer_, the receive endpoint names would be formatted as shown below. Note that class suffixes such as _Consumer_, _Saga_, and _Activity_ are trimmed from the endpoint name by default. From 4fe74cde9368b910f39f990edb0bfbbc605bccaf Mon Sep 17 00:00:00 2001 From: Dru Sellers Date: Wed, 8 May 2024 16:20:42 -0500 Subject: [PATCH 21/27] Add SQL Quick Start --- doc/content/2.quick-starts/0.index.md | 7 ++ doc/content/2.quick-starts/2.rabbitmq.md | 5 +- doc/content/2.quick-starts/5.postgresql.md | 125 +++++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 doc/content/2.quick-starts/5.postgresql.md diff --git a/doc/content/2.quick-starts/0.index.md b/doc/content/2.quick-starts/0.index.md index 34c6ebe647c..d094ce73cf8 100755 --- a/doc/content/2.quick-starts/0.index.md +++ b/doc/content/2.quick-starts/0.index.md @@ -37,5 +37,12 @@ Transports #description Requires an AWS account :: + + ::card + #title + [PostgreSQL](/quick-starts/postgresql) + #description + For smaller setups + :: :: diff --git a/doc/content/2.quick-starts/2.rabbitmq.md b/doc/content/2.quick-starts/2.rabbitmq.md index b6b135c0985..2171c4041a3 100644 --- a/doc/content/2.quick-starts/2.rabbitmq.md +++ b/doc/content/2.quick-starts/2.rabbitmq.md @@ -16,7 +16,10 @@ This tutorial will get you from zero to up and running with [RabbitMQ](/document The following instructions assume you are starting from a completed [In-Memory Quick Start](/quick-starts/in-memory) :: -This example requires a functioning installation of the .NET Runtime and SDK (at least 6.0) and a functioning installation of _Docker_ with _Docker Compose_ support enabled. +This example requires the following: + +- a functioning installation of the .NET Runtime and SDK (at least 6.0) +- a functioning installation of _Docker_ with _Docker Compose_ support enabled. ## Run RabbitMQ diff --git a/doc/content/2.quick-starts/5.postgresql.md b/doc/content/2.quick-starts/5.postgresql.md new file mode 100644 index 00000000000..9b597a2811b --- /dev/null +++ b/doc/content/2.quick-starts/5.postgresql.md @@ -0,0 +1,125 @@ +--- +navigation.title: PostgreSQL +--- + +# PostgreSQL Quick Start + +> This tutorial will get you from zero to up and running with [SQL](/documentation/transports/sql) and MassTransit. + +> Walkthrough Video TBD + +- The source for this sample is available [on GitHub](https://github.com/MassTransit/Sample-GettingStarted). + +## Prerequisites + +::alert{type="info"} +The following instructions assume you are starting from a completed [In-Memory Quick Start](/quick-starts/in-memory) +:: + +This example requires the following: + +- a functioning installation of the dotnet runtime and sdk (at least 6.0) +- a functioning installation of _Docker_ with _Docker Compose_ support enabled. + +## Run PostgreSQL + +For this quick start, we recommend running the preconfigured [official Docker image of Postgres](https://hub.docker.com/_/postgres). + +```bash +$ docker run -p 5432:5432 postgres +``` + +If you are running on an ARM platform + +```bash +$ docker run --platform linux/arm64 -p 5432:5432 postgres +``` + +Once its up and running you can use your preferred tool to browse into the database. + +## Configure PostgreSQL + +Add the _MassTransit.SqlTransport.PostgreSQL_ package to the project. + +```bash +$ dotnet add package MassTransit.SqlTransport.PostgreSQL +``` + +### Edit Program.cs + +Change _UsingInMemory_ to _UsingPostgres_ as shown below. + +```csharp +public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services.AddOptions().Configure(options => + { + options.Host = "localhost"; + options.Database = "sample"; + options.Schema = "transport"; + options.Role = "transport"; + options.Username = "masstransit"; + options.Password = "H4rd2Gu3ss!"; + + // credentials to run migrations + options.AdminUsername = "migration-user"; + options.AdminPassword = "H4rderTooGu3ss!!; + }); + // MassTransit will run the migrations on start up + services.AddPostgresMigrationHostedService(); + services.AddMassTransit(x => + { + // elided... + + x.UsingPostgres((context,cfg) => + { + cfg.ConfigureEndpoints(context); + }); + }); + + services.AddHostedService(); + }); +``` + +| Setting | Description | +|-----------------|-------------------------------------------------------------------------------------------| +| `Host` | The host to connect to. We are using `localhost` to connect to the docker container | +| `Port` | We are using the default `5432`, so we aren't setting it. | +| `Database` | The name of the database to connect to | +| `Schema` | The schema to place the tables and functions inside of | +| `Role` | the role to assign for all created tables, functions, etc. | +| `Username` | The username of the user to login as for normal operations | +| `Password` | The password of the user to login as for normal operations | +| `AdminUsername` | The username of the admin user to login as when running migration commands | +| `AdminPassword` | The password of the admin user to login as when running migration commands | + + +## Run the Project + +```bash +$ dotnet run +``` + +The output should have changed to show the message consumer generating the output (again, press Control+C to exit). Notice that the bus address now starts with _db_. + +``` +Building... +info: MassTransit[0] + Configured endpoint Message, Consumer: GettingStarted.MessageConsumer +info: Microsoft.Hosting.Lifetime[0] + Application started. Press Ctrl+C to shut down. +info: Microsoft.Hosting.Lifetime[0] + Hosting environment: Development +info: Microsoft.Hosting.Lifetime[0] + Content root path: /Users/chris/Garbage/start/GettingStarted +info: MassTransit[0] + Bus started: db://localhost/ +info: GettingStarted.MessageConsumer[0] + Received Text: The time is 3/24/2021 12:11:10 PM -05:00 +``` + +At this point the service is connecting to PostgreSQL on _localhost_ and publishing messages which are received by the consumer. + +:tada: From 4c37cd8f7ee832eb4ad680c5c4163dff4126408a Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 8 May 2024 14:10:33 -0500 Subject: [PATCH 22/27] Removed unused configuration methods. --- .../SqlBusFactoryConfiguratorExtensions.cs | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 src/MassTransit/SqlTransport/Configuration/SqlBusFactoryConfiguratorExtensions.cs diff --git a/src/MassTransit/SqlTransport/Configuration/SqlBusFactoryConfiguratorExtensions.cs b/src/MassTransit/SqlTransport/Configuration/SqlBusFactoryConfiguratorExtensions.cs deleted file mode 100644 index d5319f3d6f6..00000000000 --- a/src/MassTransit/SqlTransport/Configuration/SqlBusFactoryConfiguratorExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -#nullable enable -namespace MassTransit -{ - using System; - using SqlTransport.Configuration; - - - public static class SqlBusFactoryConfiguratorExtensions - { - /// - /// Create a bus using the database transport - /// - public static IBusControl CreateUsingDb(this IBusFactorySelector selector, Action configure) - { - return SqlBusFactory.Create(configure); - } - - /// - /// Configure the bus to use the database transport - /// - /// The registration configurator (configured via AddMassTransit) - /// The configuration callback for the bus factory - public static void UsingDb(this IBusRegistrationConfigurator configurator, - Action? configure = null) - { - configurator.SetBusFactory(new SqlRegistrationBusFactory(configure)); - } - } -} From 231fb93c72e647a61c7a4b938a8fe1f2f5624f98 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 15 May 2024 09:28:46 -0500 Subject: [PATCH 23/27] Fixed #5185 - Missing parameter on PostgreSQL query, also using wrong parameter type (object vs Guid?) for schedulingTokenId. --- .../SqlTransport/PostgreSql/PostgresClientContext.cs | 4 ++-- .../SqlTransport/PostgreSql/SqlStatements.cs | 4 ++-- .../SqlTransport/SqlServer/SqlServerClientContext.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs index 68c21f181ca..bb99840c7a1 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs @@ -145,7 +145,7 @@ public override Task Send(string queueName, SqlMessageSendContext context) IEnumerable> headers = context.Headers.GetAll().ToList(); var headersAsJson = headers.Any() ? JsonSerializer.Serialize(headers, SystemTextJsonMessageSerializer.Options) : null; - context.Headers.TryGetHeader(MessageHeaders.SchedulingTokenId, out var schedulingTokenId); + Guid? schedulingTokenId = context.Headers.Get(MessageHeaders.SchedulingTokenId); return _context.Query((x, t) => x.ExecuteScalarAsync(_sendSql, new { @@ -180,7 +180,7 @@ public override Task Publish(string topicName, SqlMessageSendContext conte IEnumerable> headers = context.Headers.GetAll().ToList(); var headersAsJson = headers.Any() ? JsonSerializer.Serialize(headers, SystemTextJsonMessageSerializer.Options) : null; - context.Headers.TryGetHeader(MessageHeaders.SchedulingTokenId, out var schedulingTokenId); + Guid? schedulingTokenId = context.Headers.Get(MessageHeaders.SchedulingTokenId); return _context.Query((x, t) => x.ExecuteScalarAsync(_publishSql, new { diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/SqlStatements.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/SqlStatements.cs index ba2de994e3b..31023fe0037 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/SqlStatements.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/SqlStatements.cs @@ -16,13 +16,13 @@ static class SqlStatements public const string DbEnqueueSql = """ SELECT * FROM "{0}".send_message(@entity_name,@priority,@transport_message_id,@body,@binary_body,@content_type, @message_type,@message_id,@correlation_id,@conversation_id,@request_id,@initiator_id,@source_address,@destination_address,@response_address,@fault_address, - @sent_time,@headers,@host,@partition_key,@routing_key,@delay) + @sent_time,@headers,@host,@partition_key,@routing_key,@delay,@scheduling_token_id) """; public const string DbPublishSql = """ SELECT * FROM "{0}".publish_message(@entity_name,@priority,@transport_message_id,@body,@binary_body,@content_type, @message_type,@message_id,@correlation_id,@conversation_id,@request_id,@initiator_id,@source_address,@destination_address,@response_address,@fault_address, - @sent_time,@headers,@host,@partition_key,@routing_key,@delay) + @sent_time,@headers,@host,@partition_key,@routing_key,@delay,@scheduling_token_id) """; public const string DbProcessMetricsSql = """SELECT * FROM "{0}".process_metrics(@row_limit)"""; diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs index 23539bc5386..6b4d0eed4a6 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs @@ -154,7 +154,7 @@ public override Task Send(string queueName, SqlMessageSendContext context) IEnumerable> headers = context.Headers.GetAll().ToList(); var headersAsJson = headers.Any() ? JsonSerializer.Serialize(headers, SystemTextJsonMessageSerializer.Options) : null; - context.Headers.TryGetHeader(MessageHeaders.SchedulingTokenId, out var schedulingTokenId); + Guid? schedulingTokenId = context.Headers.Get(MessageHeaders.SchedulingTokenId); return Execute(_sendSql, new { @@ -189,7 +189,7 @@ public override Task Publish(string topicName, SqlMessageSendContext conte IEnumerable> headers = context.Headers.GetAll().ToList(); var headersAsJson = headers.Any() ? JsonSerializer.Serialize(headers, SystemTextJsonMessageSerializer.Options) : null; - context.Headers.TryGetHeader(MessageHeaders.SchedulingTokenId, out var schedulingTokenId); + Guid? schedulingTokenId = context.Headers.Get(MessageHeaders.SchedulingTokenId); return Execute(_publishSql, new { From 2a56e02ee69a4f4173ea61ffb858630dc19d1415 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Wed, 15 May 2024 14:20:33 -0500 Subject: [PATCH 24/27] Fixed #5191 - Redelivery using SQL Server with the SQL Transport was not properly specifying the delay parameter --- .../SqlServer/SqlServerClientContext.cs | 2 +- .../Redelivery_Specs.cs | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 tests/MassTransit.SqlTransport.Tests/Redelivery_Specs.cs diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs index 6b4d0eed4a6..f11f4cf3b58 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs @@ -278,7 +278,7 @@ public override async Task Unlock(Guid lockId, long messageDeliveryId, Tim { messageDeliveryId, lockId, - duration = (int)delay.TotalSeconds, + delay = delay > TimeSpan.Zero ? Math.Max((int)delay.TotalSeconds, 1) : 0, headers = headersAsJson }).ConfigureAwait(false); diff --git a/tests/MassTransit.SqlTransport.Tests/Redelivery_Specs.cs b/tests/MassTransit.SqlTransport.Tests/Redelivery_Specs.cs new file mode 100644 index 00000000000..ab4172a811b --- /dev/null +++ b/tests/MassTransit.SqlTransport.Tests/Redelivery_Specs.cs @@ -0,0 +1,78 @@ +namespace MassTransit.DbTransport.Tests; + +using System; +using System.Threading.Tasks; +using MassTransit.Tests.Middleware.Caching; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Testing; + + +[TestFixture(typeof(PostgresDatabaseTestConfiguration))] +[TestFixture(typeof(SqlServerDatabaseTestConfiguration))] +[TestFixture] +public class When_the_redelivery_header_is_present + where T : IDatabaseTestConfiguration, new() +{ + [Test] + public async Task Should_not_exist_on_outgoing_messages() + { + await using var provider = new ServiceCollection() + .AddMassTransitTestHarness(x => + { + x.AddHandler(async (ConsumeContext context) => + { + if (context.GetRedeliveryCount() == 1) + { + await context.Publish(new OutboundMessage()); + return; + } + + throw new TestException("Ouch!"); + }); + + x.AddHandler(async (ConsumeContext _) => + { + }); + + x.SetTestTimeouts(testInactivityTimeout: TimeSpan.FromSeconds(10)); + + x.AddConfigureEndpointsCallback((_, _, cfg) => + { + cfg.UseDelayedRedelivery(r => r.Interval(10, 1000)); + }); + + _configuration.Configure(x, (context, cfg) => + { + cfg.ConfigureEndpoints(context); + }); + }) + .BuildServiceProvider(true); + + var harness = await provider.StartTestHarness(); + + await harness.Bus.Publish(new InboundMessage()); + + IReceivedMessage message = await harness.Consumed.SelectAsync().Take(1).FirstOrDefault(); + Assert.That(message, Is.Not.Null); + + Assert.That(message.Context.GetHeader(MessageHeaders.RedeliveryCount, default(int?)), Is.Null); + } + + readonly T _configuration; + + public When_the_redelivery_header_is_present() + { + _configuration = new T(); + } + + + class InboundMessage + { + } + + + class OutboundMessage + { + } +} From 40775e9d93ce48c8c8b3cfddde7f3065165b584b Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 17 May 2024 07:13:27 -0500 Subject: [PATCH 25/27] AddSqlMessageScheduler() method created so that scheduled messages can be canceled --- .../Scheduling/IMessageScheduler.cs | 9 +- .../Scheduling/IScheduleMessageProvider.cs | 6 +- .../Context/ConsumeMessageSchedulerContext.cs | 12 +- .../InMemoryOutboxMessageSchedulerContext.cs | 12 +- .../Scheduling/BaseScheduleMessageProvider.cs | 10 +- .../DelayedScheduleMessageProvider.cs | 4 +- .../EndpointScheduleMessageProvider.cs | 4 +- .../Scheduling/MessageScheduler.cs | 12 +- .../PublishScheduleMessageProvider.cs | 4 +- .../SqlScheduleMessageExtensions.cs | 55 +++++++- .../Scheduling/SqlScheduleMessageProvider.cs | 83 +++++++++-- .../SqlTransport/ClientContext.cs | 2 +- .../SqlTransport/ScopeClientContext.cs | 4 +- .../SqlTransport/SharedClientContext.cs | 4 +- .../SqlTransport/SqlClientContext.cs | 2 +- ...ServiceBusMessageSchedulerBusExtensions.cs | 18 ++- .../ServiceBusScheduleMessageProvider.cs | 6 +- .../PostgreSql/PostgresClientContext.cs | 4 +- .../SqlServer/SqlServerClientContext.cs | 11 +- .../Scheduler_Specs.cs | 132 ++++++++++++++++++ 20 files changed, 332 insertions(+), 62 deletions(-) create mode 100644 tests/MassTransit.SqlTransport.Tests/Scheduler_Specs.cs diff --git a/src/MassTransit.Abstractions/Scheduling/IMessageScheduler.cs b/src/MassTransit.Abstractions/Scheduling/IMessageScheduler.cs index 066f0e3a3e8..455ed848a9a 100644 --- a/src/MassTransit.Abstractions/Scheduling/IMessageScheduler.cs +++ b/src/MassTransit.Abstractions/Scheduling/IMessageScheduler.cs @@ -151,7 +151,8 @@ Task> ScheduleSend(Uri destinationAddress, DateTime sched /// /// The destination address of the scheduled message /// The tokenId of the scheduled message - Task CancelScheduledSend(Uri destinationAddress, Guid tokenId); + /// + Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken = default); /// /// Send a message @@ -278,7 +279,8 @@ Task> SchedulePublish(DateTime scheduledTime, object valu /// the destinationAddress. /// /// The tokenId of the scheduled message - Task CancelScheduledPublish(Guid tokenId) + /// + Task CancelScheduledPublish(Guid tokenId, CancellationToken cancellationToken = default) where T : class; /// @@ -287,6 +289,7 @@ Task CancelScheduledPublish(Guid tokenId) /// /// /// The tokenId of the scheduled message - Task CancelScheduledPublish(Type messageType, Guid tokenId); + /// + Task CancelScheduledPublish(Type messageType, Guid tokenId, CancellationToken cancellationToken = default); } } diff --git a/src/MassTransit.Abstractions/Scheduling/IScheduleMessageProvider.cs b/src/MassTransit.Abstractions/Scheduling/IScheduleMessageProvider.cs index 78e354256b3..f1875f6b35b 100644 --- a/src/MassTransit.Abstractions/Scheduling/IScheduleMessageProvider.cs +++ b/src/MassTransit.Abstractions/Scheduling/IScheduleMessageProvider.cs @@ -25,13 +25,15 @@ Task> ScheduleSend(Uri destinationAddress, DateTime sched /// Cancel a scheduled message by TokenId /// /// The tokenId of the scheduled message - Task CancelScheduledSend(Guid tokenId); + /// + Task CancelScheduledSend(Guid tokenId, CancellationToken cancellationToken); /// /// Cancel a scheduled message by TokenId /// /// The destination address of the scheduled message /// The tokenId of the scheduled message - Task CancelScheduledSend(Uri destinationAddress, Guid tokenId); + /// + Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken); } } diff --git a/src/MassTransit/Contexts/Context/ConsumeMessageSchedulerContext.cs b/src/MassTransit/Contexts/Context/ConsumeMessageSchedulerContext.cs index 7a05d3ac8ec..d9b7ff2b4fe 100644 --- a/src/MassTransit/Contexts/Context/ConsumeMessageSchedulerContext.cs +++ b/src/MassTransit/Contexts/Context/ConsumeMessageSchedulerContext.cs @@ -151,9 +151,9 @@ public Task> ScheduleSend(DateTime scheduledTime, object return _scheduler.Value.ScheduleSend(_inputAddress, scheduledTime, values, pipe, cancellationToken); } - Task IMessageScheduler.CancelScheduledSend(Uri destinationAddress, Guid tokenId) + Task IMessageScheduler.CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { - return _scheduler.Value.CancelScheduledSend(destinationAddress, tokenId); + return _scheduler.Value.CancelScheduledSend(destinationAddress, tokenId, cancellationToken); } public Task> SchedulePublish(DateTime scheduledTime, T message, CancellationToken cancellationToken) @@ -218,15 +218,15 @@ public Task> SchedulePublish(DateTime scheduledTime, obje return _scheduler.Value.SchedulePublish(scheduledTime, values, pipe, cancellationToken); } - public Task CancelScheduledPublish(Guid tokenId) + public Task CancelScheduledPublish(Guid tokenId, CancellationToken cancellationToken) where T : class { - return _scheduler.Value.CancelScheduledPublish(tokenId); + return _scheduler.Value.CancelScheduledPublish(tokenId, cancellationToken); } - public Task CancelScheduledPublish(Type messageType, Guid tokenId) + public Task CancelScheduledPublish(Type messageType, Guid tokenId, CancellationToken cancellationToken) { - return _scheduler.Value.CancelScheduledPublish(messageType, tokenId); + return _scheduler.Value.CancelScheduledPublish(messageType, tokenId, cancellationToken); } } } diff --git a/src/MassTransit/Middleware/InMemoryOutbox/InMemoryOutboxMessageSchedulerContext.cs b/src/MassTransit/Middleware/InMemoryOutbox/InMemoryOutboxMessageSchedulerContext.cs index 4fd93a8c289..ce0fd13a547 100644 --- a/src/MassTransit/Middleware/InMemoryOutbox/InMemoryOutboxMessageSchedulerContext.cs +++ b/src/MassTransit/Middleware/InMemoryOutbox/InMemoryOutboxMessageSchedulerContext.cs @@ -365,20 +365,20 @@ public async Task> SchedulePublish(DateTime scheduledTime return scheduledMessage; } - public Task CancelScheduledPublish(Guid tokenId) + public Task CancelScheduledPublish(Guid tokenId, CancellationToken cancellationToken) where T : class { - return AddCancelMessage(() => _scheduler.Value.CancelScheduledPublish(tokenId)); + return AddCancelMessage(() => _scheduler.Value.CancelScheduledPublish(tokenId, cancellationToken)); } - public Task CancelScheduledPublish(Type messageType, Guid tokenId) + public Task CancelScheduledPublish(Type messageType, Guid tokenId, CancellationToken cancellationToken) { - return AddCancelMessage(() => _scheduler.Value.CancelScheduledPublish(messageType, tokenId)); + return AddCancelMessage(() => _scheduler.Value.CancelScheduledPublish(messageType, tokenId, cancellationToken)); } - public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { - return AddCancelMessage(() => _scheduler.Value.CancelScheduledSend(destinationAddress, tokenId)); + return AddCancelMessage(() => _scheduler.Value.CancelScheduledSend(destinationAddress, tokenId, cancellationToken)); } void AddScheduledMessage(ScheduledMessage scheduledMessage) diff --git a/src/MassTransit/Scheduling/BaseScheduleMessageProvider.cs b/src/MassTransit/Scheduling/BaseScheduleMessageProvider.cs index 049e614bc5c..33387cfccb8 100644 --- a/src/MassTransit/Scheduling/BaseScheduleMessageProvider.cs +++ b/src/MassTransit/Scheduling/BaseScheduleMessageProvider.cs @@ -29,19 +29,19 @@ public async Task> ScheduleSend(Uri destinationAddress, D command.Destination, message); } - public Task CancelScheduledSend(Guid tokenId) + public Task CancelScheduledSend(Guid tokenId, CancellationToken cancellationToken) { - return CancelScheduledSend(tokenId, null); + return CancelScheduledSend(tokenId, null, cancellationToken); } - public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { - return CancelScheduledSend(tokenId, destinationAddress); + return CancelScheduledSend(tokenId, destinationAddress, cancellationToken); } protected abstract Task ScheduleSend(ScheduleMessage message, IPipe> pipe, CancellationToken cancellationToken); - protected abstract Task CancelScheduledSend(Guid tokenId, Uri destinationAddress); + protected abstract Task CancelScheduledSend(Guid tokenId, Uri destinationAddress, CancellationToken cancellationToken); } diff --git a/src/MassTransit/Scheduling/DelayedScheduleMessageProvider.cs b/src/MassTransit/Scheduling/DelayedScheduleMessageProvider.cs index 789648e13e3..1dd6babeb21 100644 --- a/src/MassTransit/Scheduling/DelayedScheduleMessageProvider.cs +++ b/src/MassTransit/Scheduling/DelayedScheduleMessageProvider.cs @@ -35,12 +35,12 @@ public async Task> ScheduleSend(Uri destinationAddress, D return new ScheduledMessageHandle(scheduleMessagePipe.ScheduledMessageId ?? NewId.NextGuid(), scheduledTime, destinationAddress, message); } - public Task CancelScheduledSend(Guid tokenId) + public Task CancelScheduledSend(Guid tokenId, CancellationToken cancellationToken) { return Task.CompletedTask; } - public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { return Task.CompletedTask; } diff --git a/src/MassTransit/Scheduling/EndpointScheduleMessageProvider.cs b/src/MassTransit/Scheduling/EndpointScheduleMessageProvider.cs index e402b4f6765..b3aa2833fe0 100644 --- a/src/MassTransit/Scheduling/EndpointScheduleMessageProvider.cs +++ b/src/MassTransit/Scheduling/EndpointScheduleMessageProvider.cs @@ -22,7 +22,7 @@ protected override async Task ScheduleSend(ScheduleMessage message, IPipe(new InVar.CorrelationId, InVar.Timestamp, TokenId = tokenId - }) + }, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/MassTransit/Scheduling/MessageScheduler.cs b/src/MassTransit/Scheduling/MessageScheduler.cs index 0abca4aa2bb..ae1909caa4c 100644 --- a/src/MassTransit/Scheduling/MessageScheduler.cs +++ b/src/MassTransit/Scheduling/MessageScheduler.cs @@ -159,9 +159,9 @@ public async Task> ScheduleSend(Uri destinationAddress, D return await _provider.ScheduleSend(destinationAddress, scheduledTime, send.Message, send.Pipe, cancellationToken).ConfigureAwait(false); } - public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { - return _provider.CancelScheduledSend(destinationAddress, tokenId); + return _provider.CancelScheduledSend(destinationAddress, tokenId, cancellationToken); } public Task> SchedulePublish(DateTime scheduledTime, T message, CancellationToken cancellationToken = default) @@ -259,19 +259,19 @@ public Task> SchedulePublish(DateTime scheduledTime, obje return ScheduleSend(destinationAddress, scheduledTime, values, pipe, cancellationToken); } - public Task CancelScheduledPublish(Guid tokenId) + public Task CancelScheduledPublish(Guid tokenId, CancellationToken cancellationToken) where T : class { var destinationAddress = GetPublishAddress(); - return CancelScheduledSend(destinationAddress, tokenId); + return CancelScheduledSend(destinationAddress, tokenId, cancellationToken); } - public Task CancelScheduledPublish(Type messageType, Guid tokenId) + public Task CancelScheduledPublish(Type messageType, Guid tokenId, CancellationToken cancellationToken) { var destinationAddress = GetPublishAddress(messageType); - return CancelScheduledSend(destinationAddress, tokenId); + return CancelScheduledSend(destinationAddress, tokenId, cancellationToken); } Uri GetPublishAddress() diff --git a/src/MassTransit/Scheduling/PublishScheduleMessageProvider.cs b/src/MassTransit/Scheduling/PublishScheduleMessageProvider.cs index a81bc6f681f..4ccf934e7d6 100644 --- a/src/MassTransit/Scheduling/PublishScheduleMessageProvider.cs +++ b/src/MassTransit/Scheduling/PublishScheduleMessageProvider.cs @@ -20,14 +20,14 @@ protected override Task ScheduleSend(ScheduleMessage message, IPipe(new { InVar.CorrelationId, InVar.Timestamp, TokenId = tokenId - }); + }, cancellationToken); } } } diff --git a/src/MassTransit/SqlTransport/Configuration/SqlScheduleMessageExtensions.cs b/src/MassTransit/SqlTransport/Configuration/SqlScheduleMessageExtensions.cs index 2da36e71c45..4b225d2d6f8 100644 --- a/src/MassTransit/SqlTransport/Configuration/SqlScheduleMessageExtensions.cs +++ b/src/MassTransit/SqlTransport/Configuration/SqlScheduleMessageExtensions.cs @@ -1,16 +1,31 @@ namespace MassTransit { using System; + using DependencyInjection; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DependencyInjection.Extensions; + using Scheduling; using SqlTransport.Configuration; + using Transports; public static class SqlScheduleMessageExtensions { /// - /// Uses the database transport's built-in message scheduler + /// Uses the SQL transport's built-in message scheduler /// /// + [Obsolete("Use the renamed UseSqlMessageScheduler instead")] public static void UseDbMessageScheduler(this IBusFactoryConfigurator configurator) + { + UseSqlMessageScheduler(configurator); + } + + /// + /// Uses the SQL transport's built-in message scheduler + /// + /// + public static void UseSqlMessageScheduler(this IBusFactoryConfigurator configurator) { if (configurator == null) throw new ArgumentNullException(nameof(configurator)); @@ -19,5 +34,43 @@ public static void UseDbMessageScheduler(this IBusFactoryConfigurator configurat configurator.AddPrePipeSpecification(pipeBuilderConfigurator); } + + /// + /// Add a to the container that uses the SQL Transport message enqueue time to schedule messages. + /// + /// + public static void AddSqlMessageScheduler(this IBusRegistrationConfigurator configurator) + { + configurator.TryAddScoped(provider => + { + var busInstance = provider.GetRequiredService>().Value; + var sendEndpointProvider = provider.GetRequiredService(); + + var hostConfiguration = busInstance.HostConfiguration as ISqlHostConfiguration + ?? throw new ArgumentException("The SQL transport configuration was not found"); + + return new MessageScheduler(new SqlScheduleMessageProvider(hostConfiguration, sendEndpointProvider), busInstance.Bus.Topology); + }); + } + + /// + /// Add a to the container that uses the SQL Transport message enqueue time to schedule messages. + /// + /// + public static void AddSqlMessageScheduler(this IBusRegistrationConfigurator configurator) + where TBus : class, IBus + { + configurator.TryAddScoped(provider => + { + var busInstance = provider.GetRequiredService>().Value; + var sendEndpointProvider = provider.GetRequiredService(); + + var hostConfiguration = busInstance.HostConfiguration as ISqlHostConfiguration + ?? throw new ArgumentException("The SQL transport configuration was not found"); + + return Bind.Create( + new MessageScheduler(new SqlScheduleMessageProvider(hostConfiguration, sendEndpointProvider), busInstance.Bus.Topology)); + }); + } } } diff --git a/src/MassTransit/SqlTransport/Scheduling/SqlScheduleMessageProvider.cs b/src/MassTransit/SqlTransport/Scheduling/SqlScheduleMessageProvider.cs index 3fc40bddccd..d8cc5c71825 100644 --- a/src/MassTransit/SqlTransport/Scheduling/SqlScheduleMessageProvider.cs +++ b/src/MassTransit/SqlTransport/Scheduling/SqlScheduleMessageProvider.cs @@ -5,16 +5,32 @@ namespace MassTransit.Scheduling using System.Threading; using System.Threading.Tasks; using SqlTransport; + using SqlTransport.Configuration; + using Transports; public class SqlScheduleMessageProvider : IScheduleMessageProvider { - readonly ConsumeContext _context; + readonly Func, CancellationToken, Task> _cancel; + readonly ConsumeContext? _context; + readonly ISqlHostConfiguration? _hostConfiguration; + readonly ISendEndpointProvider _sendEndpointProvider; public SqlScheduleMessageProvider(ConsumeContext context) { _context = context; + _sendEndpointProvider = context; + + _cancel = RetryUsingContext; + } + + public SqlScheduleMessageProvider(ISqlHostConfiguration hostConfiguration, ISendEndpointProvider sendEndpointProvider) + { + _hostConfiguration = hostConfiguration; + _sendEndpointProvider = sendEndpointProvider; + + _cancel = RetryUsingHostConfiguration; } public async Task> ScheduleSend(Uri destinationAddress, DateTime scheduledTime, T message, IPipe> pipe, @@ -30,7 +46,7 @@ public async Task> ScheduleSend(Uri destinationAddress, D schedulePipe.ScheduledMessageId = tokenId; - var endpoint = await _context.GetSendEndpoint(destinationAddress).ConfigureAwait(false); + var endpoint = await _sendEndpointProvider.GetSendEndpoint(destinationAddress).ConfigureAwait(false); await endpoint.Send(message, schedulePipe, cancellationToken).ConfigureAwait(false); @@ -40,24 +56,65 @@ public async Task> ScheduleSend(Uri destinationAddress, D return new ScheduledMessageHandle(schedulePipe.ScheduledMessageId ?? NewId.NextGuid(), scheduledTime, destinationAddress, message); } - public async Task CancelScheduledSend(Guid tokenId) + public Task CancelScheduledSend(Guid tokenId, CancellationToken cancellationToken) { - if (!_context.TryGetPayload(out ClientContext? clientContext)) - throw new ArgumentException("The client context was not available", nameof(_context)); + return _cancel(async clientContext => + { + var deleted = await clientContext.DeleteScheduledMessage(tokenId, cancellationToken).ConfigureAwait(false); + if (deleted) + LogContext.Debug?.Log("CANCEL {TokenId}", tokenId); + }, cancellationToken); + } - var deleted = await clientContext.DeleteScheduledMessage(tokenId).ConfigureAwait(false); - if (deleted) - LogContext.Debug?.Log("CANCEL {TokenId}", tokenId); + public Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) + { + return _cancel(async clientContext => + { + var deleted = await clientContext.DeleteScheduledMessage(tokenId, cancellationToken).ConfigureAwait(false); + if (deleted) + LogContext.Debug?.Log("CANCEL {DestinationAddress} {TokenId}", destinationAddress, tokenId); + }, cancellationToken); } - public async Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + Task RetryUsingContext(Func callback, CancellationToken cancellationToken) { - if (!_context.TryGetPayload(out ClientContext? clientContext)) + if (!_context!.TryGetPayload(out ClientContext? clientContext)) throw new ArgumentException("The client context was not available", nameof(_context)); - var deleted = await clientContext.DeleteScheduledMessage(tokenId).ConfigureAwait(false); - if (deleted) - LogContext.Debug?.Log("CANCEL {DestinationAddress} {TokenId}", destinationAddress, tokenId); + return callback(clientContext); + } + + Task RetryUsingHostConfiguration(Func callback, CancellationToken cancellationToken) + { + var pipe = new ClientContextPipe(callback, cancellationToken); + + return _hostConfiguration.Retry(() => _hostConfiguration!.ConnectionContextSupervisor.Send(pipe, cancellationToken), cancellationToken, + _hostConfiguration!.ConnectionContextSupervisor.Stopping); + } + + + class ClientContextPipe : + IPipe + { + readonly Func _callback; + readonly CancellationToken _cancellationToken; + + public ClientContextPipe(Func callback, CancellationToken cancellationToken) + { + _callback = callback; + _cancellationToken = cancellationToken; + } + + public Task Send(ConnectionContext context) + { + var clientContext = context.CreateClientContext(_cancellationToken); + + return _callback(clientContext); + } + + public void Probe(ProbeContext context) + { + } } } } diff --git a/src/MassTransit/SqlTransport/SqlTransport/ClientContext.cs b/src/MassTransit/SqlTransport/SqlTransport/ClientContext.cs index 191da221bee..c7aebf0d986 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/ClientContext.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/ClientContext.cs @@ -58,7 +58,7 @@ Task> ReceiveMessages(string queueName, SqlRece TimeSpan lockDuration); Task DeleteMessage(Guid lockId, long messageDeliveryId); - Task DeleteScheduledMessage(Guid tokenId); + Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken); Task MoveMessage(Guid lockId, long messageDeliveryId, string queueName, SqlQueueType queueType, SendHeaders sendHeaders); Task RenewLock(Guid lockId, long messageDeliveryId, TimeSpan duration); Task Unlock(Guid lockId, long messageDeliveryId, TimeSpan delay, SendHeaders sendHeaders); diff --git a/src/MassTransit/SqlTransport/SqlTransport/ScopeClientContext.cs b/src/MassTransit/SqlTransport/SqlTransport/ScopeClientContext.cs index 147eca8ed55..73e83224278 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/ScopeClientContext.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/ScopeClientContext.cs @@ -83,9 +83,9 @@ public Task DeleteMessage(Guid lockId, long messageDeliveryId) return _context.DeleteMessage(lockId, messageDeliveryId); } - public Task DeleteScheduledMessage(Guid tokenId) + public Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken) { - return _context.DeleteScheduledMessage(tokenId); + return _context.DeleteScheduledMessage(tokenId, cancellationToken); } public Task MoveMessage(Guid lockId, long messageDeliveryId, string queueName, SqlQueueType queueType, SendHeaders sendHeaders) diff --git a/src/MassTransit/SqlTransport/SqlTransport/SharedClientContext.cs b/src/MassTransit/SqlTransport/SqlTransport/SharedClientContext.cs index 6358afc3fb9..fb2df3fd1da 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/SharedClientContext.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/SharedClientContext.cs @@ -83,9 +83,9 @@ public Task DeleteMessage(Guid lockId, long messageDeliveryId) return _context.DeleteMessage(lockId, messageDeliveryId); } - public Task DeleteScheduledMessage(Guid tokenId) + public Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken) { - return _context.DeleteScheduledMessage(tokenId); + return _context.DeleteScheduledMessage(tokenId, cancellationToken); } public Task MoveMessage(Guid lockId, long messageDeliveryId, string queueName, SqlQueueType queueType, SendHeaders sendHeaders) diff --git a/src/MassTransit/SqlTransport/SqlTransport/SqlClientContext.cs b/src/MassTransit/SqlTransport/SqlTransport/SqlClientContext.cs index 401d505a624..2ab55c20ec4 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/SqlClientContext.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/SqlClientContext.cs @@ -39,7 +39,7 @@ public abstract Task> ReceiveMessages(string qu TimeSpan lockDuration); public abstract Task DeleteMessage(Guid lockId, long messageDeliveryId); - public abstract Task DeleteScheduledMessage(Guid tokenId); + public abstract Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken); public abstract Task MoveMessage(Guid lockId, long messageDeliveryId, string queueName, SqlQueueType queueType, SendHeaders sendHeaders); public abstract Task RenewLock(Guid lockId, long messageDeliveryId, TimeSpan duration); public abstract Task Unlock(Guid lockId, long messageDeliveryId, TimeSpan delay, SendHeaders sendHeaders); diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/ServiceBusMessageSchedulerBusExtensions.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/ServiceBusMessageSchedulerBusExtensions.cs index 152193099e9..5456428e054 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/ServiceBusMessageSchedulerBusExtensions.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/Configuration/ServiceBusMessageSchedulerBusExtensions.cs @@ -1,5 +1,6 @@ namespace MassTransit { + using DependencyInjection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Scheduling; @@ -35,7 +36,7 @@ public static IMessageScheduler CreateServiceBusMessageScheduler(this ISendEndpo } /// - /// Add a to the container that uses the Azure message enqueue time to schedule messages. + /// Add an to the container that uses the Azure message enqueue time to schedule messages. /// /// public static void AddServiceBusMessageScheduler(this IRegistrationConfigurator configurator) @@ -47,5 +48,20 @@ public static void AddServiceBusMessageScheduler(this IRegistrationConfigurator return sendEndpointProvider.CreateServiceBusMessageScheduler(bus.Topology); }); } + + /// + /// Add an to the container that uses the Azure message enqueue time to schedule messages. + /// + /// + public static void AddServiceBusMessageScheduler(this IBusRegistrationConfigurator configurator) + where TBus : class, IBus + { + configurator.TryAddScoped(provider => + { + var bus = provider.GetRequiredService(); + var sendEndpointProvider = provider.GetRequiredService(); + return Bind.Create(sendEndpointProvider.CreateServiceBusMessageScheduler(bus.Topology)); + }); + } } } diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/Scheduling/ServiceBusScheduleMessageProvider.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/Scheduling/ServiceBusScheduleMessageProvider.cs index 728831f2211..9b3d6acc636 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/Scheduling/ServiceBusScheduleMessageProvider.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/Scheduling/ServiceBusScheduleMessageProvider.cs @@ -39,12 +39,12 @@ public async Task> ScheduleSend(Uri destinationAddress, D return new ScheduledMessageHandle(scheduleMessagePipe.ScheduledMessageId ?? NewId.NextGuid(), scheduledTime, destinationAddress, message); } - public Task CancelScheduledSend(Guid tokenId) + public Task CancelScheduledSend(Guid tokenId, CancellationToken cancellationToken) { return Task.CompletedTask; } - public async Task CancelScheduledSend(Uri destinationAddress, Guid tokenId) + public async Task CancelScheduledSend(Uri destinationAddress, Guid tokenId, CancellationToken cancellationToken) { var endpoint = await _sendEndpointProvider.GetSendEndpoint(destinationAddress).ConfigureAwait(false); @@ -53,7 +53,7 @@ await endpoint.Send(new InVar.CorrelationId, InVar.Timestamp, TokenId = tokenId - }).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs index bb99840c7a1..5024d4a255c 100644 --- a/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs +++ b/src/Transports/MassTransit.SqlTransport.PostgreSql/SqlTransport/PostgreSql/PostgresClientContext.cs @@ -221,12 +221,12 @@ public override async Task DeleteMessage(Guid lockId, long messageDelivery return result == messageDeliveryId; } - public override async Task DeleteScheduledMessage(Guid tokenId) + public override async Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken) { IEnumerable? result = await _context.Query((x, t) => x.QueryAsync(_deleteScheduledMessageSql, new { token_id = tokenId, - }), CancellationToken); + }), cancellationToken); return result.Any(); } diff --git a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs index f11f4cf3b58..6bae951baab 100644 --- a/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs +++ b/src/Transports/MassTransit.SqlTransport.SqlServer/SqlTransport/SqlServer/SqlServerClientContext.cs @@ -230,12 +230,12 @@ public override async Task DeleteMessage(Guid lockId, long messageDelivery return result == messageDeliveryId; } - public override async Task DeleteScheduledMessage(Guid tokenId) + public override async Task DeleteScheduledMessage(Guid tokenId, CancellationToken cancellationToken) { IEnumerable result = await Query(_deleteScheduledMessageSql, new { tokenId, - }).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); return result.Any(); } @@ -305,5 +305,12 @@ Task> Query(string functionName, object values) return _context.Query((connection, transaction) => connection .QueryAsync(functionName, values, transaction, commandType: CommandType.StoredProcedure), CancellationToken); } + + Task> Query(string functionName, object values, CancellationToken cancellationToken) + where T : class + { + return _context.Query((connection, transaction) => connection + .QueryAsync(functionName, values, transaction, commandType: CommandType.StoredProcedure), cancellationToken); + } } } diff --git a/tests/MassTransit.SqlTransport.Tests/Scheduler_Specs.cs b/tests/MassTransit.SqlTransport.Tests/Scheduler_Specs.cs new file mode 100644 index 00000000000..3ff0c1c4bfa --- /dev/null +++ b/tests/MassTransit.SqlTransport.Tests/Scheduler_Specs.cs @@ -0,0 +1,132 @@ +namespace MassTransit.DbTransport.Tests; + +using System; +using System.Threading.Tasks; +using Internals; +using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; +using Testing; + + +[TestFixture(typeof(PostgresDatabaseTestConfiguration))] +[TestFixture(typeof(SqlServerDatabaseTestConfiguration))] +public class Canceling_a_scheduled_message_from_a_consumer + where T : IDatabaseTestConfiguration, new() +{ + [Test] + public async Task Should_be_supported() + { + var testId = NewId.NextGuid(); + + await using var provider = _configuration.Create() + .AddMassTransitTestHarness(x => + { + x.AddSqlMessageScheduler(); + + x.AddHandler(async context => + { + ScheduledMessage scheduledMessage = + await context.ScheduleSend(TimeSpan.FromSeconds(5), new SecondMessage { Id = testId }); + + await Task.Delay(1000); + + await context.CancelScheduledSend(scheduledMessage); + }) + .Endpoint(e => e.Name = "schedule-input"); + + x.AddHandler(async context => + { + await Task.Delay(1, context.CancellationToken); + }) + .Endpoint(e => e.Name = "schedule-input"); + + x.SetTestTimeouts(testInactivityTimeout: TimeSpan.FromSeconds(10)); + + _configuration.Configure(x, (context, cfg) => + { + cfg.UseSqlMessageScheduler(); + + cfg.ConfigureEndpoints(context); + }); + }) + .BuildServiceProvider(true); + + var harness = await provider.StartTestHarness(); + + await harness.Bus.Publish(new FirstMessage()); + + Assert.That(await harness.Consumed.Any()); + Assert.That(await harness.Sent.Any(x => x.Context.Message.Id == testId)); + + Assert.That(async () => await harness.Consumed.Any().OrTimeout(TimeSpan.FromSeconds(5)), Throws.TypeOf()); + } + + [Test] + public async Task Should_be_supported_from_outside_the_consumer() + { + var testId = NewId.NextGuid(); + + Guid? tokenId = default; + Uri destinationAddress = null; + + await using var provider = _configuration.Create() + .AddMassTransitTestHarness(x => + { + x.AddSqlMessageScheduler(); + + x.AddHandler(async context => + { + ScheduledMessage scheduledMessage = await context.ScheduleSend(TimeSpan.FromSeconds(5), new SecondMessage { Id = testId }); + + tokenId = scheduledMessage.TokenId; + destinationAddress = context.ReceiveContext.InputAddress; + }).Endpoint(e => e.Name = "schedule-input"); + + x.AddHandler(async context => + { + await Task.Delay(1, context.CancellationToken); + }) + .Endpoint(e => e.Name = "schedule-input"); + + x.SetTestTimeouts(testInactivityTimeout: TimeSpan.FromSeconds(10)); + + _configuration.Configure(x, (context, cfg) => + { + cfg.UseSqlMessageScheduler(); + + cfg.ConfigureEndpoints(context); + }); + }) + .BuildServiceProvider(true); + + var harness = await provider.StartTestHarness(); + + await harness.Bus.Publish(new FirstMessage()); + + Assert.That(await harness.Consumed.Any()); + Assert.That(await harness.Sent.Any(x => x.Context.Message.Id == testId)); + + await Task.Delay(500); + + var scheduler = harness.Scope.ServiceProvider.GetRequiredService(); + await scheduler.CancelScheduledSend(destinationAddress, tokenId.Value, harness.CancellationToken); + + Assert.That(async () => await harness.Consumed.Any().OrTimeout(TimeSpan.FromSeconds(5)), Throws.TypeOf()); + } + + readonly T _configuration; + + public Canceling_a_scheduled_message_from_a_consumer() + { + _configuration = new T(); + } + + + public record FirstMessage; + + + public record SecondMessage + { + public Guid Id { get; init; } + } +} From b2ce0ef60b240450ae640484f90ee0938f8559c5 Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Fri, 17 May 2024 09:53:53 -0500 Subject: [PATCH 26/27] Add requestTimeout to SendRequest on mediator. --- src/MassTransit.Abstractions/MediatorRequestExtensions.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/MassTransit.Abstractions/MediatorRequestExtensions.cs b/src/MassTransit.Abstractions/MediatorRequestExtensions.cs index 5656ae01e82..8d9702c712d 100644 --- a/src/MassTransit.Abstractions/MediatorRequestExtensions.cs +++ b/src/MassTransit.Abstractions/MediatorRequestExtensions.cs @@ -14,14 +14,16 @@ public static class MediatorRequestExtensions /// /// The request message /// + /// /// The response type /// The response object - public static async Task SendRequest(this IMediator mediator, Request request, CancellationToken cancellationToken = default) + public static async Task SendRequest(this IMediator mediator, Request request, CancellationToken cancellationToken = default, + RequestTimeout timeout = default) where T : class { try { - using RequestHandle> handle = mediator.CreateRequest(request, cancellationToken); + using RequestHandle> handle = mediator.CreateRequest(request, cancellationToken, timeout); Response response = await handle.GetResponse().ConfigureAwait(false); From be8e177e680d6ae2a79a1e15b3bea3b34f88446f Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Sat, 18 May 2024 10:03:09 -0500 Subject: [PATCH 27/27] Separated MessageTopology so that each bus gets its own topology to allow MultiBus with separate settings. --- .../TestConsumeContext.cs | 2 +- .../MediatorConfigurationExtensions.cs | 2 +- .../InMemoryTransport/InMemoryBus.cs | 16 +++++----- .../InMemoryRegistrationBusFactory.cs | 2 +- src/MassTransit/SqlTransport/SqlBusFactory.cs | 15 ++++----- .../SqlRegistrationBusFactory.cs | 2 +- .../Testing/InMemoryTestHarness.cs | 2 +- .../ActiveMqBusFactory.cs | 18 +++++------ .../ActiveMqRegistrationBusFactory.cs | 2 +- .../AmazonSqsBusFactory.cs | 18 +++++------ .../AmazonSqsRegistrationBusFactory.cs | 2 +- .../AzureBusFactory.cs | 17 +++++----- .../ServiceBusRegistrationBusFactory.cs | 2 +- .../RabbitMqBusFactory.cs | 31 ++++++++++--------- .../RabbitMqRegistrationBusFactory.cs | 2 +- .../RabbitMqAddressExtensions.cs | 2 +- .../BuildTopology_Specs.cs | 6 ++-- .../BuildTopology_Specs.cs | 6 ++-- .../ExcludeTopology_Specs.cs | 6 ++-- 19 files changed, 75 insertions(+), 78 deletions(-) diff --git a/src/MassTransit.TestFramework/TestConsumeContext.cs b/src/MassTransit.TestFramework/TestConsumeContext.cs index da058159341..06711ee6a50 100644 --- a/src/MassTransit.TestFramework/TestConsumeContext.cs +++ b/src/MassTransit.TestFramework/TestConsumeContext.cs @@ -20,7 +20,7 @@ public static class TestConsumeContext static InMemoryReceiveEndpointContext Build() { - var topologyConfiguration = new InMemoryTopologyConfiguration(InMemoryBus.MessageTopology); + var topologyConfiguration = new InMemoryTopologyConfiguration(InMemoryBus.CreateMessageTopology()); IInMemoryBusConfiguration busConfiguration = new InMemoryBusConfiguration(topologyConfiguration, null); var receiveEndpointConfiguration = busConfiguration.HostConfiguration.CreateReceiveEndpointConfiguration("input-queue"); diff --git a/src/MassTransit/Configuration/MediatorConfigurationExtensions.cs b/src/MassTransit/Configuration/MediatorConfigurationExtensions.cs index 81531a7eac5..87afdc37dc3 100644 --- a/src/MassTransit/Configuration/MediatorConfigurationExtensions.cs +++ b/src/MassTransit/Configuration/MediatorConfigurationExtensions.cs @@ -21,7 +21,7 @@ public static IMediator CreateMediator(this IBusFactorySelector selector, Action if (configure == null) throw new ArgumentNullException(nameof(configure)); - var topologyConfiguration = new InMemoryTopologyConfiguration(InMemoryBus.MessageTopology); + var topologyConfiguration = new InMemoryTopologyConfiguration(InMemoryBus.CreateMessageTopology()); var busConfiguration = new InMemoryBusConfiguration(topologyConfiguration, new Uri("loopback://localhost")); if (LogContext.Current != null) diff --git a/src/MassTransit/InMemoryTransport/InMemoryBus.cs b/src/MassTransit/InMemoryTransport/InMemoryBus.cs index ab82b01f6de..0df66b068bb 100644 --- a/src/MassTransit/InMemoryTransport/InMemoryBus.cs +++ b/src/MassTransit/InMemoryTransport/InMemoryBus.cs @@ -8,8 +8,6 @@ namespace MassTransit public static class InMemoryBus { - public static IMessageTopologyConfigurator MessageTopology => Cached.MessageTopologyValue.Value; - /// /// Configure and create an in-memory bus /// @@ -28,7 +26,7 @@ public static IBusControl Create(Action configu /// public static IBusControl Create(Uri baseAddress, Action configure) { - var topologyConfiguration = new InMemoryTopologyConfiguration(MessageTopology); + var topologyConfiguration = new InMemoryTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new InMemoryBusConfiguration(topologyConfiguration, baseAddress); var configurator = new InMemoryBusFactoryConfigurator(busConfiguration); @@ -38,17 +36,19 @@ public static IBusControl Create(Uri baseAddress, Action MessageTopologyValue = - new Lazy(() => new MessageTopology(_entityNameFormatter)); - - static readonly IEntityNameFormatter _entityNameFormatter; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - _entityNameFormatter = new MessageUrnEntityNameFormatter(); + EntityNameFormatter = new MessageUrnEntityNameFormatter(); } } } diff --git a/src/MassTransit/InMemoryTransport/InMemoryTransport/Configuration/InMemoryRegistrationBusFactory.cs b/src/MassTransit/InMemoryTransport/InMemoryTransport/Configuration/InMemoryRegistrationBusFactory.cs index 64d9a2eac15..7ab49bc66ac 100644 --- a/src/MassTransit/InMemoryTransport/InMemoryTransport/Configuration/InMemoryRegistrationBusFactory.cs +++ b/src/MassTransit/InMemoryTransport/InMemoryTransport/Configuration/InMemoryRegistrationBusFactory.cs @@ -13,7 +13,7 @@ public class InMemoryRegistrationBusFactory : readonly Action _configure; public InMemoryRegistrationBusFactory(Uri baseAddress, Action configure) - : this(new InMemoryBusConfiguration(new InMemoryTopologyConfiguration(InMemoryBus.MessageTopology), baseAddress), configure) + : this(new InMemoryBusConfiguration(new InMemoryTopologyConfiguration(InMemoryBus.CreateMessageTopology()), baseAddress), configure) { } diff --git a/src/MassTransit/SqlTransport/SqlBusFactory.cs b/src/MassTransit/SqlTransport/SqlBusFactory.cs index f1dc9704419..38c192b9ddc 100644 --- a/src/MassTransit/SqlTransport/SqlBusFactory.cs +++ b/src/MassTransit/SqlTransport/SqlBusFactory.cs @@ -1,7 +1,6 @@ namespace MassTransit { using System; - using System.Threading; using Configuration; using SqlTransport.Configuration; using SqlTransport.Topology; @@ -10,8 +9,6 @@ public static class SqlBusFactory { - public static IMessageTopologyConfigurator MessageTopology => Cached.MessageTopologyValue.Value; - /// /// Create a bus using the database transport /// @@ -19,7 +16,7 @@ public static class SqlBusFactory /// public static IBusControl Create(Action configure) { - var topologyConfiguration = new SqlTopologyConfiguration(MessageTopology); + var topologyConfiguration = new SqlTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new SqlBusConfiguration(topologyConfiguration); var configurator = new SqlBusFactoryConfigurator(busConfiguration); @@ -29,15 +26,19 @@ public static IBusControl Create(Action configure) return configurator.Build(busConfiguration); } + public static IMessageTopologyConfigurator CreateMessageTopology() + { + return new MessageTopology(Cached.EntityNameFormatter); + } + static class Cached { - internal static readonly Lazy MessageTopologyValue; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - IEntityNameFormatter formatter = new MessageNameFormatterEntityNameFormatter(new SqlMessageNameFormatter()); - MessageTopologyValue = new Lazy(() => new MessageTopology(formatter), LazyThreadSafetyMode.PublicationOnly); + EntityNameFormatter = new MessageNameFormatterEntityNameFormatter(new SqlMessageNameFormatter()); } } } diff --git a/src/MassTransit/SqlTransport/SqlTransport/Configuration/SqlRegistrationBusFactory.cs b/src/MassTransit/SqlTransport/SqlTransport/Configuration/SqlRegistrationBusFactory.cs index 5baafff7713..cf6ed2a778e 100644 --- a/src/MassTransit/SqlTransport/SqlTransport/Configuration/SqlRegistrationBusFactory.cs +++ b/src/MassTransit/SqlTransport/SqlTransport/Configuration/SqlRegistrationBusFactory.cs @@ -14,7 +14,7 @@ public class SqlRegistrationBusFactory : readonly Action? _configure; public SqlRegistrationBusFactory(Action? configure) - : this(new SqlBusConfiguration(new SqlTopologyConfiguration(SqlBusFactory.MessageTopology)), configure) + : this(new SqlBusConfiguration(new SqlTopologyConfiguration(SqlBusFactory.CreateMessageTopology())), configure) { } diff --git a/src/MassTransit/Testing/InMemoryTestHarness.cs b/src/MassTransit/Testing/InMemoryTestHarness.cs index 1b765edddc0..8aebf70b422 100644 --- a/src/MassTransit/Testing/InMemoryTestHarness.cs +++ b/src/MassTransit/Testing/InMemoryTestHarness.cs @@ -27,7 +27,7 @@ public InMemoryTestHarness(string virtualHost, IEnumerable Cached.MessageTopologyValue.Value; - /// /// Configure and create a bus for ActiveMQ /// @@ -19,7 +16,7 @@ public static class ActiveMqBusFactory /// public static IBusControl Create(Action configure) { - var topologyConfiguration = new ActiveMqTopologyConfiguration(MessageTopology); + var topologyConfiguration = new ActiveMqTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new ActiveMqBusConfiguration(topologyConfiguration); var configurator = new ActiveMqBusFactoryConfigurator(busConfiguration); @@ -29,18 +26,19 @@ public static IBusControl Create(Action configu return configurator.Build(busConfiguration); } + public static IMessageTopologyConfigurator CreateMessageTopology() + { + return new MessageTopology(Cached.EntityNameFormatter); + } + static class Cached { - internal static readonly Lazy MessageTopologyValue = - new Lazy(() => new MessageTopology(_entityNameFormatter), - LazyThreadSafetyMode.PublicationOnly); - - static readonly IEntityNameFormatter _entityNameFormatter; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(new ActiveMqMessageNameFormatter()); + EntityNameFormatter = new MessageNameFormatterEntityNameFormatter(new ActiveMqMessageNameFormatter()); } } } diff --git a/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Configuration/ActiveMqRegistrationBusFactory.cs b/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Configuration/ActiveMqRegistrationBusFactory.cs index bfecdb9c612..da255800374 100644 --- a/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Configuration/ActiveMqRegistrationBusFactory.cs +++ b/src/Transports/MassTransit.ActiveMqTransport/ActiveMqTransport/Configuration/ActiveMqRegistrationBusFactory.cs @@ -15,7 +15,7 @@ public class ActiveMqRegistrationBusFactory : readonly Action _configure; public ActiveMqRegistrationBusFactory(Action configure) - : this(new ActiveMqBusConfiguration(new ActiveMqTopologyConfiguration(ActiveMqBusFactory.MessageTopology)), configure) + : this(new ActiveMqBusConfiguration(new ActiveMqTopologyConfiguration(ActiveMqBusFactory.CreateMessageTopology())), configure) { } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsBusFactory.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsBusFactory.cs index c0a8636ef18..2881e695c01 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsBusFactory.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsBusFactory.cs @@ -1,7 +1,6 @@ namespace MassTransit { using System; - using System.Threading; using AmazonSqsTransport; using AmazonSqsTransport.Configuration; using Configuration; @@ -10,8 +9,6 @@ public static class AmazonSqsBusFactory { - public static IMessageTopologyConfigurator MessageTopology => Cached.MessageTopologyValue.Value; - /// /// Configure and create a bus for AmazonSQS /// @@ -19,7 +16,7 @@ public static class AmazonSqsBusFactory /// public static IBusControl Create(Action configure) { - var topologyConfiguration = new AmazonSqsTopologyConfiguration(MessageTopology); + var topologyConfiguration = new AmazonSqsTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new AmazonSqsBusConfiguration(topologyConfiguration); var configurator = new AmazonSqsBusFactoryConfigurator(busConfiguration); @@ -29,18 +26,19 @@ public static IBusControl Create(Action config return configurator.Build(busConfiguration); } + public static IMessageTopologyConfigurator CreateMessageTopology() + { + return new MessageTopology(Cached.EntityNameFormatter); + } + static class Cached { - internal static readonly Lazy MessageTopologyValue = - new Lazy(() => new MessageTopology(_entityNameFormatter), - LazyThreadSafetyMode.PublicationOnly); - - static readonly IEntityNameFormatter _entityNameFormatter; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(new AmazonSqsMessageNameFormatter()); + EntityNameFormatter = new MessageNameFormatterEntityNameFormatter(new AmazonSqsMessageNameFormatter()); } } } diff --git a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Configuration/AmazonSqsRegistrationBusFactory.cs b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Configuration/AmazonSqsRegistrationBusFactory.cs index 3590d5e7bcb..d9f535d775e 100644 --- a/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Configuration/AmazonSqsRegistrationBusFactory.cs +++ b/src/Transports/MassTransit.AmazonSqsTransport/AmazonSqsTransport/Configuration/AmazonSqsRegistrationBusFactory.cs @@ -17,7 +17,7 @@ public class AmazonSqsRegistrationBusFactory : readonly Action _configure; public AmazonSqsRegistrationBusFactory(Action configure) - : this(new AmazonSqsBusConfiguration(new AmazonSqsTopologyConfiguration(AmazonSqsBusFactory.MessageTopology)), configure) + : this(new AmazonSqsBusConfiguration(new AmazonSqsTopologyConfiguration(AmazonSqsBusFactory.CreateMessageTopology())), configure) { } diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureBusFactory.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureBusFactory.cs index 6ed3cc8b88a..84f7e6d5bd6 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureBusFactory.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureBusFactory.cs @@ -1,7 +1,6 @@ namespace MassTransit { using System; - using System.Threading; using AzureServiceBusTransport; using AzureServiceBusTransport.Configuration; using Configuration; @@ -10,8 +9,6 @@ public static class AzureBusFactory { - public static IMessageTopologyConfigurator MessageTopology => Cached.MessageTopologyValue.Value; - /// /// Configure and create a bus for Azure Service Bus (later, we'll use Event Hubs instead) /// @@ -19,7 +16,7 @@ public static class AzureBusFactory /// public static IBusControl CreateUsingServiceBus(Action configure) { - var topologyConfiguration = new ServiceBusTopologyConfiguration(MessageTopology); + var topologyConfiguration = new ServiceBusTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new ServiceBusBusConfiguration(topologyConfiguration); var configurator = new ServiceBusBusFactoryConfigurator(busConfiguration); @@ -29,17 +26,19 @@ public static IBusControl CreateUsingServiceBus(Action MessageTopologyValue = - new Lazy(() => new MessageTopology(_entityNameFormatter), LazyThreadSafetyMode.PublicationOnly); - - static readonly IEntityNameFormatter _entityNameFormatter; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(new ServiceBusMessageNameFormatter()); + EntityNameFormatter = new MessageNameFormatterEntityNameFormatter(new ServiceBusMessageNameFormatter()); } } } diff --git a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusRegistrationBusFactory.cs b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusRegistrationBusFactory.cs index 4d44c6777bf..d6eb976d75b 100644 --- a/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusRegistrationBusFactory.cs +++ b/src/Transports/MassTransit.Azure.ServiceBus.Core/AzureServiceBusTransport/ServiceBusRegistrationBusFactory.cs @@ -16,7 +16,7 @@ public class ServiceBusRegistrationBusFactory : readonly Action _configure; public ServiceBusRegistrationBusFactory(Action configure) - : this(new ServiceBusBusConfiguration(new ServiceBusTopologyConfiguration(AzureBusFactory.MessageTopology)), configure) + : this(new ServiceBusBusConfiguration(new ServiceBusTopologyConfiguration(AzureBusFactory.CreateMessageTopology())), configure) { } diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqBusFactory.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqBusFactory.cs index 810840b0a48..696f11c82f4 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqBusFactory.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqBusFactory.cs @@ -1,15 +1,14 @@ -using System; -using MassTransit.Configuration; -using MassTransit.RabbitMqTransport; -using MassTransit.RabbitMqTransport.Configuration; -using MassTransit.Topology; - -namespace MassTransit +namespace MassTransit { + using System; + using Configuration; + using RabbitMqTransport; + using RabbitMqTransport.Configuration; + using Topology; + + public static class RabbitMqBusFactory { - public static IMessageTopologyConfigurator MessageTopology => Cached.MessageTopologyValue.Value; - /// /// Configure and create a bus for RabbitMQ /// @@ -17,7 +16,7 @@ public static class RabbitMqBusFactory /// public static IBusControl Create(Action configure = null) { - var topologyConfiguration = new RabbitMqTopologyConfiguration(MessageTopology); + var topologyConfiguration = new RabbitMqTopologyConfiguration(CreateMessageTopology()); var busConfiguration = new RabbitMqBusConfiguration(topologyConfiguration); var configurator = new RabbitMqBusFactoryConfigurator(busConfiguration); @@ -27,17 +26,19 @@ public static IBusControl Create(Action configu return configurator.Build(busConfiguration); } + public static IMessageTopologyConfigurator CreateMessageTopology() + { + return new MessageTopology(Cached.EntityNameFormatter); + } + static class Cached { - internal static readonly Lazy MessageTopologyValue = - new Lazy(() => new MessageTopology(_entityNameFormatter)); - - static readonly IEntityNameFormatter _entityNameFormatter; + internal static readonly IEntityNameFormatter EntityNameFormatter; static Cached() { - _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(new RabbitMqMessageNameFormatter()); + EntityNameFormatter = new MessageNameFormatterEntityNameFormatter(new RabbitMqMessageNameFormatter()); } } } diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqRegistrationBusFactory.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqRegistrationBusFactory.cs index 358bd32522f..17c5130c4bb 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqRegistrationBusFactory.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/Configuration/RabbitMqRegistrationBusFactory.cs @@ -16,7 +16,7 @@ public class RabbitMqRegistrationBusFactory : readonly Action _configure; public RabbitMqRegistrationBusFactory(Action configure) - : this(new RabbitMqBusConfiguration(new RabbitMqTopologyConfiguration(RabbitMqBusFactory.MessageTopology)), configure) + : this(new RabbitMqBusConfiguration(new RabbitMqTopologyConfiguration(RabbitMqBusFactory.CreateMessageTopology())), configure) { } diff --git a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs index c01b280db5f..03c58b2f1d0 100644 --- a/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs +++ b/src/Transports/MassTransit.RabbitMqTransport/RabbitMqTransport/RabbitMqAddressExtensions.cs @@ -17,7 +17,7 @@ public static ReceiveSettings GetReceiveSettings(this Uri address) var hostAddress = new RabbitMqHostAddress(address); var endpointAddress = new RabbitMqEndpointAddress(hostAddress, address); - var topologyConfiguration = new RabbitMqTopologyConfiguration(RabbitMqBusFactory.MessageTopology); + var topologyConfiguration = new RabbitMqTopologyConfiguration(RabbitMqBusFactory.CreateMessageTopology()); var endpointConfiguration = new RabbitMqEndpointConfiguration(topologyConfiguration); var settings = new RabbitMqReceiveSettings(endpointConfiguration, endpointAddress.Name, endpointAddress.ExchangeType, endpointAddress.Durable, endpointAddress.AutoDelete) diff --git a/tests/MassTransit.Azure.ServiceBus.Core.Tests/BuildTopology_Specs.cs b/tests/MassTransit.Azure.ServiceBus.Core.Tests/BuildTopology_Specs.cs index de921cb0447..f3e97b62f0e 100644 --- a/tests/MassTransit.Azure.ServiceBus.Core.Tests/BuildTopology_Specs.cs +++ b/tests/MassTransit.Azure.ServiceBus.Core.Tests/BuildTopology_Specs.cs @@ -167,7 +167,7 @@ public void Setup() { _nameFormatter = new ServiceBusMessageNameFormatter(); _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(_nameFormatter); - _consumeTopology = new ServiceBusConsumeTopology(AzureBusFactory.MessageTopology, new ServiceBusPublishTopology(AzureBusFactory.MessageTopology)); + _consumeTopology = new ServiceBusConsumeTopology(AzureBusFactory.CreateMessageTopology(), new ServiceBusPublishTopology(AzureBusFactory.CreateMessageTopology())); _builder = new ReceiveEndpointBrokerTopologyBuilder(); @@ -236,7 +236,7 @@ public void Setup() { _nameFormatter = new ServiceBusMessageNameFormatter(); _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(_nameFormatter); - _publishTopology = new ServiceBusPublishTopology(AzureBusFactory.MessageTopology); + _publishTopology = new ServiceBusPublishTopology(AzureBusFactory.CreateMessageTopology()); _builder = new PublishEndpointBrokerTopologyBuilder(_publishTopology); } @@ -338,7 +338,7 @@ public void Setup() { _nameFormatter = new ServiceBusMessageNameFormatter(); _entityNameFormatter = new MessageNameFormatterEntityNameFormatter(_nameFormatter); - _publishTopology = new ServiceBusPublishTopology(AzureBusFactory.MessageTopology); + _publishTopology = new ServiceBusPublishTopology(AzureBusFactory.CreateMessageTopology()); _builder = new PublishEndpointBrokerTopologyBuilder(_publishTopology); } diff --git a/tests/MassTransit.RabbitMqTransport.Tests/BuildTopology_Specs.cs b/tests/MassTransit.RabbitMqTransport.Tests/BuildTopology_Specs.cs index 0cefbed4360..64769ff9770 100644 --- a/tests/MassTransit.RabbitMqTransport.Tests/BuildTopology_Specs.cs +++ b/tests/MassTransit.RabbitMqTransport.Tests/BuildTopology_Specs.cs @@ -152,7 +152,7 @@ public void Should_include_a_binding_for_the_single_interface() public void Setup() { _nameFormatter = new RabbitMqMessageNameFormatter(); - _consumeTopology = new RabbitMqConsumeTopology(RabbitMqBusFactory.MessageTopology, new RabbitMqPublishTopology(RabbitMqBusFactory.MessageTopology)); + _consumeTopology = new RabbitMqConsumeTopology(RabbitMqBusFactory.CreateMessageTopology(), new RabbitMqPublishTopology(RabbitMqBusFactory.CreateMessageTopology())); _builder = new ReceiveEndpointBrokerTopologyBuilder(); @@ -222,7 +222,7 @@ public void Should_include_a_binding_for_the_single_interface() public void Setup() { _nameFormatter = new RabbitMqMessageNameFormatter(); - _publishTopology = new RabbitMqPublishTopology(RabbitMqBusFactory.MessageTopology); + _publishTopology = new RabbitMqPublishTopology(RabbitMqBusFactory.CreateMessageTopology()); _builder = new PublishEndpointBrokerTopologyBuilder(); } @@ -319,7 +319,7 @@ public void Should_include_a_binding_for_the_third_interface_as_well() public void Setup() { _nameFormatter = new RabbitMqMessageNameFormatter(); - _publishTopology = new RabbitMqPublishTopology(RabbitMqBusFactory.MessageTopology); + _publishTopology = new RabbitMqPublishTopology(RabbitMqBusFactory.CreateMessageTopology()); _builder = new PublishEndpointBrokerTopologyBuilder(PublishBrokerTopologyOptions.MaintainHierarchy); } diff --git a/tests/MassTransit.RabbitMqTransport.Tests/ExcludeTopology_Specs.cs b/tests/MassTransit.RabbitMqTransport.Tests/ExcludeTopology_Specs.cs index 0dcf8c10554..792a74efae6 100644 --- a/tests/MassTransit.RabbitMqTransport.Tests/ExcludeTopology_Specs.cs +++ b/tests/MassTransit.RabbitMqTransport.Tests/ExcludeTopology_Specs.cs @@ -36,7 +36,7 @@ public async Task Should_not_create_the_exchange() using var model = connection.CreateModel(); - var eventExchangeName = RabbitMqBusFactory.MessageTopology.EntityNameFormatter.FormatEntityName(); + var eventExchangeName = RabbitMqBusFactory.CreateMessageTopology().EntityNameFormatter.FormatEntityName(); var exception = Assert.Throws(() => model.ExchangeDeclarePassive(eventExchangeName)); @@ -62,10 +62,10 @@ protected override void OnCleanupVirtualHost(IModel model) { base.OnCleanupVirtualHost(model); - var eventExchangeName = RabbitMqBusFactory.MessageTopology.EntityNameFormatter.FormatEntityName(); + var eventExchangeName = RabbitMqBusFactory.CreateMessageTopology().EntityNameFormatter.FormatEntityName(); model.ExchangeDelete(eventExchangeName); - var routedEventExchangeName = RabbitMqBusFactory.MessageTopology.EntityNameFormatter.FormatEntityName(); + var routedEventExchangeName = RabbitMqBusFactory.CreateMessageTopology().EntityNameFormatter.FormatEntityName(); model.ExchangeDelete(routedEventExchangeName); } }