From 388583fd21a8d31ab14a0b02ed23b5633cbc159a Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Fri, 25 Oct 2019 11:05:48 +0800 Subject: [PATCH 01/31] Basic structure --- .../ConfigureEndpointSqlServerTransport.cs | 20 +++++++++--------- ..._custom_schema_configured_for_publisher.cs | 9 ++++---- .../APIApprovals.Approve.approved.txt | 2 +- .../PubSub/SubscriptionManager.cs | 21 +++++++++++++++++++ .../Sending/MessageDispatcher.cs | 1 + .../SqlServerTransport.cs | 2 +- .../SqlServerTransportInfrastructure.cs | 4 ++-- 7 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index 9ed95dc00..595685281 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -25,16 +25,16 @@ public Task Configure(string endpointName, EndpointConfiguration configuration, #if !NET452 transportConfig.Transactions(TransportTransactionMode.SendsAtomicWithReceive); #endif - - var routingConfig = transportConfig.Routing(); - - foreach (var publisher in publisherMetadata.Publishers) - { - foreach (var eventType in publisher.Events) - { - routingConfig.RegisterPublisher(eventType, publisher.PublisherName); - } - } + // TODO: Put this back when we support compatability mode + //var routingConfig = transportConfig.Routing(); + + //foreach (var publisher in publisherMetadata.Publishers) + //{ + // foreach (var eventType in publisher.Events) + // { + // routingConfig.RegisterPublisher(eventType, publisher.PublisherName); + // } + //} return Task.FromResult(0); } diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs index 78a8366b4..f2424dcf2 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs @@ -50,10 +50,11 @@ public Subscriber() b.UseTransport() .DefaultSchema("receiver") - .UseSchemaForEndpoint(publisherEndpoint, "sender") - .Routing().RegisterPublisher( - eventType: typeof(Event), - publisherEndpoint: publisherEndpoint); + .UseSchemaForEndpoint(publisherEndpoint, "sender"); + // TODO: Use this for compatibility mode + //.Routing().RegisterPublisher( + // eventType: typeof(Event), + // publisherEndpoint: publisherEndpoint); }); } diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 28dbd638d..12b42e449 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -3,7 +3,7 @@ [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] namespace NServiceBus { - public class SqlServerTransport : NServiceBus.Transport.TransportDefinition, NServiceBus.Routing.IMessageDrivenSubscriptionTransport + public class SqlServerTransport : NServiceBus.Transport.TransportDefinition { public SqlServerTransport() { } public override string ExampleConnectionStringForErrorMessage { get; } diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs new file mode 100644 index 000000000..2147c207c --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Threading.Tasks; + using Extensibility; + + class SubscriptionManager : IManageSubscriptions + { + public Task Subscribe(Type eventType, ContextBag context) + { + // TODO: Add subscription to table + throw new NotImplementedException(); + } + + public Task Unsubscribe(Type eventType, ContextBag context) + { + // TODO: Remove subscription from table + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index 5875c3606..613e1a2b1 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -18,6 +18,7 @@ public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator add // We need to check if we can support cancellation in here as well? public async Task Dispatch(TransportOperations operations, TransportTransaction transportTransaction, ContextBag context) { + // TODO: Convert Multicast operations into unicast operations await DeduplicateAndDispatch(operations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); await DeduplicateAndDispatch(operations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); } diff --git a/src/NServiceBus.SqlServer/SqlServerTransport.cs b/src/NServiceBus.SqlServer/SqlServerTransport.cs index 87c1f2148..b8cd13c33 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransport.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransport.cs @@ -12,7 +12,7 @@ namespace NServiceBus /// /// SqlServer Transport /// - public class SqlServerTransport : TransportDefinition, IMessageDrivenSubscriptionTransport + public class SqlServerTransport : TransportDefinition { /// /// diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 5ded99732..c6d2496a8 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -55,7 +55,7 @@ public override IEnumerable DeliveryConstraints public override TransportTransactionMode TransactionMode { get; } = TransportTransactionMode.TransactionScope; - public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Multicast, OutboundRoutingType.Unicast); public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() { @@ -300,7 +300,7 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { - throw new NotImplementedException(); + return new TransportSubscriptionInfrastructure(() => new SubscriptionManager()); } public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) From 6f62527c802dc0044588fd5a783915a2ae94ef9e Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Mon, 28 Oct 2019 15:39:31 +0800 Subject: [PATCH 02/31] Basic Table-Based Pub-Sub --- .../TestSuiteConstraints.cs | 2 +- .../When_dispatching_messages.cs | 11 ++- .../When_using_ttbr.cs | 12 ++- .../APIApprovals.Approve.approved.txt | 3 + .../Sending/MessageDispatcherTests.cs | 2 +- .../PubSub/IKnowWhereTheSubscriptionsAre.cs | 10 ++ .../PubSub/SubscriptionManager.cs | 25 ++++- .../PubSub/TableBasedSubscriptions.cs | 91 +++++++++++++++++++ .../Queuing/SqlConstants.cs | 55 +++++++++++ .../Sending/MessageDispatcher.cs | 38 ++++++-- .../SqlServerTransport.cs | 1 - .../SqlServerTransportInfrastructure.cs | 38 ++++---- 12 files changed, 257 insertions(+), 31 deletions(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/TestSuiteConstraints.cs b/src/NServiceBus.SqlServer.AcceptanceTests/TestSuiteConstraints.cs index 9a5c10d99..73b57b1f3 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/TestSuiteConstraints.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/TestSuiteConstraints.cs @@ -10,7 +10,7 @@ public partial class TestSuiteConstraints public bool SupportsDtc => false; #endif public bool SupportsCrossQueueTransactions => true; - public bool SupportsNativePubSub => false; + public bool SupportsNativePubSub => true; public bool SupportsNativeDeferral => true; public bool SupportsOutbox => true; public IConfigureEndpointTestExecution CreateTransportConfiguration() => new ConfigureEndpointSqlServerTransport(); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index 8e8755955..363833ce1 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -107,7 +107,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) @@ -136,6 +136,15 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato SqlConnectionFactory sqlConnectionFactory; + // TODO: Figure out if this is appropriate in this test + class NoOpSubscriptionStore : IKnowWhereTheSubscriptionsAre + { + public Task> GetSubscribersForEvent(string eventType) + { + return Task.FromResult(new List()); + } + } + interface IContextProvider : IDisposable { ContextBag Context { get; } diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index 4d402af67..686911d94 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -134,7 +134,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressParser) @@ -161,5 +161,15 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S SqlConnectionFactory sqlConnectionFactory; const string validAddress = "TTBRTests"; + + // TODO: Figure out if this is appropriate in this test + class NoOpSubscriptionStore : IKnowWhereTheSubscriptionsAre + { + public Task> GetSubscribersForEvent(string eventType) + { + return Task.FromResult(new List()); + } + } + } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 12b42e449..0921f0b4f 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -64,11 +64,14 @@ CREATE NONCLUSTERED INDEX [Index_Due] ON {0} ) EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static readonly string CreateQueueText; + public static readonly string CreateSubscriptionTableText; public static readonly string PeekText; public static readonly string PurgeBatchOfExpiredMessagesText; public static readonly string PurgeText; public static readonly string ReceiveText; public static readonly string SendText; + public static readonly string SubscribeText; + public static readonly string UnsubscribeText; } public class static SqlServerTransportSettingsExtensions { diff --git a/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs b/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs index b3e9ec254..886df1239 100644 --- a/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs +++ b/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs @@ -17,7 +17,7 @@ public async Task It_deduplicates_based_on_message_id_and_address(TransportOpera { var queueDispatcher = new FakeTableBasedQueueDispatcher(); - var dispatcher = new MessageDispatcher(queueDispatcher, new QueueAddressTranslator("nservicebus", "dbo", null, null)); + var dispatcher = new MessageDispatcher(queueDispatcher, new QueueAddressTranslator("nservicebus", "dbo", null, null), null /* TODO: Put something in here */); await dispatcher.Dispatch(transportOperations, new TransportTransaction(), new ContextBag()); diff --git a/src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs b/src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs new file mode 100644 index 000000000..1a6e9ce68 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs @@ -0,0 +1,10 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + interface IKnowWhereTheSubscriptionsAre + { + Task> GetSubscribersForEvent(string eventType); + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index 2147c207c..9787ec0b3 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -3,19 +3,36 @@ namespace NServiceBus.Transport.SQLServer using System; using System.Threading.Tasks; using Extensibility; + using Settings; class SubscriptionManager : IManageSubscriptions { + TableBasedSubscriptions tableBasedSubscriptions; + string endpointName; + string localAddress; + + public SubscriptionManager(TableBasedSubscriptions tableBasedSubscriptions, ReadOnlySettings settings) + { + this.tableBasedSubscriptions = tableBasedSubscriptions; + endpointName = settings.EndpointName(); + localAddress = settings.LocalAddress(); + } + public Task Subscribe(Type eventType, ContextBag context) { - // TODO: Add subscription to table - throw new NotImplementedException(); + return tableBasedSubscriptions.Subscribe( + endpointName, + localAddress, + eventType.ToString() + ); } public Task Unsubscribe(Type eventType, ContextBag context) { - // TODO: Remove subscription from table - throw new NotImplementedException(); + return tableBasedSubscriptions.Unsubscribe( + endpointName, + eventType.ToString() + ); } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs new file mode 100644 index 000000000..6145f2dfd --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs @@ -0,0 +1,91 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + using System.Data; + using System.Threading.Tasks; + + class TableBasedSubscriptions : IKnowWhereTheSubscriptionsAre + { + SqlConnectionFactory connectionFactory; + string subscribeCommand; + string unsubscribeCommand; + string getSubscribersCommand; + string createSubscriptionsTableCommand; + + + public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) + { + this.connectionFactory = connectionFactory; + // TODO: Be able to change the subscriptions table name and schema +#pragma warning disable 618 + subscribeCommand = SqlConstants.SubscribeText; + unsubscribeCommand = SqlConstants.UnsubscribeText; + createSubscriptionsTableCommand = SqlConstants.CreateSubscriptionTableText; + getSubscribersCommand = SqlConstants.GetSubscribersText; +#pragma warning restore 618 + } + + public async Task Subscribe(string endpointName, string endpointAddress, string eventType) + { + // TODO: Do we need a specific transaction scope here? + // TODO: Do we need to pull an existing connection out of the context here? + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using(var command = connection.CreateCommand()) + { + command.CommandText = subscribeCommand; + command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; + + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + } + + public async Task Unsubscribe(string endpointName, string eventType) + { + // TODO: Do we need a specific transaction scope here? + // TODO: Do we need to pull an existing connection out of the context here? + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using(var command = connection.CreateCommand()) + { + command.CommandText = unsubscribeCommand; + command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + } + + public async Task> GetSubscribersForEvent(string eventType) + { + var results = new List(); + using(var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var command = connection.CreateCommand()) + { + command.CommandText = getSubscribersCommand; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + + using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) + { + while (await reader.ReadAsync().ConfigureAwait(false)) + { + results.Add(reader.GetString(1)); + } + } + return results; + } + } + + public async Task CreateSubscriptionTable() + { + // TODO: Call this from somewhere + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var command = connection.CreateCommand()) + { + command.CommandText = createSubscriptionsTableCommand; + + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index e2686b91e..1f88a7b48 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -255,5 +255,60 @@ FROM sys.columns c WHERE c.object_id = OBJECT_ID('{0}') AND c.name = 'Headers'"; + public static readonly string CreateSubscriptionTableText = @" +IF NOT EXISTS +( + SELECT * + FROM sys.objects + WHERE + object_id = object_id('[dbo].[SubscriptionRouting]') AND + type IN ('U') +) +BEGIN + CREATE TABLE [dbo].[SubscriptionRouting] ( + Subscriber NVARCHAR(200) NOT NULL, + Endpoint NVARCHAR(200), + MessageType NVARCHAR(200) NOT NULL, + PRIMARY KEY CLUSTERED + ( + Subscriber, + MessageType + ) + ) +END"; + + public static readonly string SubscribeText = @" +MERGE [dbo].[SubscriptionRouting] WITH (HOLDLOCK, TABLOCK) AS target +USING(SELECT @Endpoint AS Endpoint, @Subscriber AS Subscriber, @MessageType AS MessageType) AS source +ON target.Subscriber = source.Subscriber +AND target.MessageType = source.MessageType +WHEN MATCHED AND source.Endpoint IS NOT NULL AND (target.Endpoint IS NULL OR target.Endpoint <> source.Endpoint) THEN +UPDATE SET Endpoint = @Endpoint +WHEN NOT MATCHED THEN +INSERT +( + Subscriber, + MessageType, + Endpoint +) +VALUES +( + @Subscriber, + @MessageType, + @Endpoint +);"; + + public static readonly string GetSubscribersText = @" +SELECT DISTINCT Subscriber, Endpoint +FROM [dbo].[SubscriptionRouting] +WHERE MessageType IN (@MessageType) +"; + + public static readonly string UnsubscribeText = @" +DELETE FROM [dbo].[SubscriptionRouting] +WHERE + Subscriber = @Subscriber and + MessageType = @MessageType"; + } } diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index 613e1a2b1..9c00f1ff1 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -9,23 +9,27 @@ class MessageDispatcher : IDispatchMessages { - public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator) + public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IKnowWhereTheSubscriptionsAre subscriptions) { this.dispatcher = dispatcher; this.addressTranslator = addressTranslator; + this.subscriptions = subscriptions; } // We need to check if we can support cancellation in here as well? public async Task Dispatch(TransportOperations operations, TransportTransaction transportTransaction, ContextBag context) { - // TODO: Convert Multicast operations into unicast operations - await DeduplicateAndDispatch(operations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); - await DeduplicateAndDispatch(operations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); + await DeduplicateAndDispatch(operations.UnicastTransportOperations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); + await DeduplicateAndDispatch(operations.UnicastTransportOperations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); + + var multicastOperations = await ConvertToUnicastOperations(operations).ConfigureAwait(false); + await DeduplicateAndDispatch(multicastOperations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); + await DeduplicateAndDispatch(multicastOperations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); } - Task DeduplicateAndDispatch(TransportOperations operations, Func, Task> dispatchMethod, DispatchConsistency dispatchConsistency) + Task DeduplicateAndDispatch(IEnumerable transportOperations, Func, Task> dispatchMethod, DispatchConsistency dispatchConsistency) { - var operationsToDispatch = operations.UnicastTransportOperations + var operationsToDispatch = transportOperations .Where(o => o.RequiredDispatchConsistency == dispatchConsistency) .GroupBy(o => new DeduplicationKey(o.Message.MessageId, addressTranslator.Parse(o.Destination).Address)) .Select(g => g.First()) @@ -34,8 +38,30 @@ Task DeduplicateAndDispatch(TransportOperations operations, Func> ConvertToUnicastOperations(TransportOperations operations) + { + var tasks = operations.MulticastTransportOperations.Select(m => ConvertToUnicastOperations(m)).ToArray(); + await Task.WhenAll(tasks).ConfigureAwait(false); + return tasks.SelectMany(t => t.Result).ToList(); + } + + async Task> ConvertToUnicastOperations(MulticastTransportOperation transportOperation) + { + var destinations = await subscriptions.GetSubscribersForEvent(transportOperation.MessageType.ToString()).ConfigureAwait(false); + + return (from destination in destinations + select new UnicastTransportOperation( + transportOperation.Message, + destination, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints + )).ToList(); + } + IQueueDispatcher dispatcher; QueueAddressTranslator addressTranslator; + IKnowWhereTheSubscriptionsAre subscriptions; class DeduplicationKey { diff --git a/src/NServiceBus.SqlServer/SqlServerTransport.cs b/src/NServiceBus.SqlServer/SqlServerTransport.cs index b8cd13c33..44346bd12 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransport.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransport.cs @@ -4,7 +4,6 @@ namespace NServiceBus using System.Data.Common; using System.Data.SqlClient; using System.Threading.Tasks; - using Routing; using Settings; using Transport; using Transport.SQLServer; diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index c6d2496a8..3fdb60741 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -38,6 +38,8 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); } + + tableBasedSubscriptions = new TableBasedSubscriptions(CreateConnectionFactory()); } public override IEnumerable DeliveryConstraints @@ -219,7 +221,7 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() { queueOperationsReader = new DelayedDeliveryTableBasedQueueOperationsReader(CreateDelayedMessageTable(), queueOperationsReader); } - var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator); + var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, tableBasedSubscriptions); return dispatcher; }, () => Task.FromResult(DelayedDeliveryInfrastructure.CheckForInvalidSettings(settings))); @@ -262,8 +264,11 @@ CanonicalQueueAddress GetDelayedQueueTableName() return addressTranslator.GetCanonicalForm(delayedQueueAddress); } - public override Task Start() + public override async Task Start() { + // TODO: Move this to an installer? + await tableBasedSubscriptions.CreateSubscriptionTable().ConfigureAwait(false); + foreach (var diagnosticSection in diagnostics) { settings.AddStartupDiagnosticsSection(diagnosticSection.Key, diagnosticSection.Value); @@ -275,22 +280,22 @@ public override Task Start() { Native = false }); - return Task.FromResult(0); } - - settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new + else { - Native = true, - delayedDeliverySettings.Suffix, - delayedDeliverySettings.Interval, - BatchSize = delayedDeliverySettings.MatureBatchSize, - TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" - }); + settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new + { + Native = true, + delayedDeliverySettings.Suffix, + delayedDeliverySettings.Interval, + BatchSize = delayedDeliverySettings.MatureBatchSize, + TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" + }); - var delayedMessageTable = CreateDelayedMessageTable(); - delayedMessageHandler = new DelayedMessageHandler(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); - delayedMessageHandler.Start(); - return Task.FromResult(0); + var delayedMessageTable = CreateDelayedMessageTable(); + delayedMessageHandler = new DelayedMessageHandler(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); + delayedMessageHandler.Start(); + } } public override Task Stop() @@ -300,7 +305,7 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { - return new TransportSubscriptionInfrastructure(() => new SubscriptionManager()); + return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(tableBasedSubscriptions, settings)); } public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) @@ -336,5 +341,6 @@ public override string MakeCanonicalForm(string transportAddress) DelayedMessageHandler delayedMessageHandler; DelayedDeliverySettings delayedDeliverySettings; Dictionary diagnostics = new Dictionary(); + TableBasedSubscriptions tableBasedSubscriptions; } } \ No newline at end of file From 3f0081533319d48235d2cffd6e2a0c5da0d2e31c Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Mon, 28 Oct 2019 12:41:53 +0100 Subject: [PATCH 03/31] Suppress existing transaction context when executing subscription store operations --- .../PubSub/TableBasedSubscriptions.cs | 59 +++++++++++-------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs index 6145f2dfd..4d8749ce6 100644 --- a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs @@ -3,6 +3,7 @@ namespace NServiceBus.Transport.SQLServer using System.Collections.Generic; using System.Data; using System.Threading.Tasks; + using System.Transactions; class TableBasedSubscriptions : IKnowWhereTheSubscriptionsAre { @@ -27,52 +28,58 @@ public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) public async Task Subscribe(string endpointName, string endpointAddress, string eventType) { - // TODO: Do we need a specific transaction scope here? - // TODO: Do we need to pull an existing connection out of the context here? - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using(var command = connection.CreateCommand()) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - command.CommandText = subscribeCommand; - command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; - command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var command = connection.CreateCommand()) + { + command.CommandText = subscribeCommand; + command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; - await command.ExecuteNonQueryAsync().ConfigureAwait(false); + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } } } public async Task Unsubscribe(string endpointName, string eventType) { - // TODO: Do we need a specific transaction scope here? - // TODO: Do we need to pull an existing connection out of the context here? - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using(var command = connection.CreateCommand()) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - command.CommandText = unsubscribeCommand; - command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var command = connection.CreateCommand()) + { + command.CommandText = unsubscribeCommand; + command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; - await command.ExecuteNonQueryAsync().ConfigureAwait(false); + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } } } public async Task> GetSubscribersForEvent(string eventType) { var results = new List(); - using(var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using (var command = connection.CreateCommand()) + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { - command.CommandText = getSubscribersCommand; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; - - using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var command = connection.CreateCommand()) { - while (await reader.ReadAsync().ConfigureAwait(false)) + command.CommandText = getSubscribersCommand; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + + using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) { - results.Add(reader.GetString(1)); + while (await reader.ReadAsync().ConfigureAwait(false)) + { + results.Add(reader.GetString(1)); + } } + + return results; } - return results; } } From de640f92d9bb9656fb9aefbab4792c5a27c87af2 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Mon, 28 Oct 2019 22:10:55 +0100 Subject: [PATCH 04/31] Merge delayed delivery into core of the transport --- .../When_configuring_delayed_delivery.cs | 6 +- ...When_deferring_a_message_in_native_mode.cs | 2 +- ...e_in_timeout_manager_compatibility_mode.cs | 2 +- ...ng_a_message_to_the_past_in_native_mode.cs | 2 +- ...TBR_for_deferred_message_in_native_mode.cs | 2 +- .../When_checking_schema.cs | 2 +- .../When_dispatching_messages.cs | 4 +- .../When_message_receive_takes_long.cs | 2 +- .../When_receiving_messages.cs | 2 +- .../When_using_ttbr.cs | 4 +- .../DelayedDeiveryTableBasedQueueFactory.cs | 80 -------------- .../DelayedDeliveryInfrastructure.cs | 34 ------ .../DelayedDeliveryMessagePump.cs | 44 -------- .../DelayedDeliveryQueueCreator.cs | 63 ----------- .../DelayedMessageProcessor.cs | 49 --------- .../DelayedDelivery/DelayedMessageTable.cs | 8 +- ...ndler.cs => DueDelayedMessageProcessor.cs} | 8 +- .../Queuing/SqlConstants.cs | 2 +- .../TableBasedQueueOperationsReader.cs | 54 ++++++++- .../Receiving/ProcessWithNativeTransaction.cs | 3 +- .../Receiving/ProcessWithNoTransaction.cs | 3 +- .../Receiving/ProcessWithTransactionScope.cs | 3 +- .../Receiving/QueueCreator.cs | 40 ++++++- .../Receiving/ReceiveStrategy.cs | 32 ++++++ .../SqlServerTransportInfrastructure.cs | 104 ++++++------------ .../SqlServerTransportSettingsExtensions.cs | 4 +- 26 files changed, 186 insertions(+), 373 deletions(-) delete mode 100644 src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeiveryTableBasedQueueFactory.cs delete mode 100644 src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryInfrastructure.cs delete mode 100644 src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryMessagePump.cs delete mode 100644 src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryQueueCreator.cs delete mode 100644 src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageProcessor.cs rename src/NServiceBus.SqlServer/DelayedDelivery/{DelayedMessageHandler.cs => DueDelayedMessageProcessor.cs} (86%) diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_configuring_delayed_delivery.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_configuring_delayed_delivery.cs index 4b8cefd91..f03009867 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_configuring_delayed_delivery.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_configuring_delayed_delivery.cs @@ -87,7 +87,7 @@ public EndpointWithTimeoutManagerAndNative() EndpointSetup(config => { config.EnableFeature(); - config.UseTransport().UseNativeDelayedDelivery(); + config.UseTransport().NativeDelayedDelivery(); }); } } @@ -98,7 +98,7 @@ public EndpointWithOnlyNative() { EndpointSetup(config => { - var settings = config.UseTransport().UseNativeDelayedDelivery(); + var settings = config.UseTransport().NativeDelayedDelivery(); settings.DisableTimeoutManagerCompatibility(); }); } @@ -112,7 +112,7 @@ public EndpointWithTimeoutManagerAndNativeEnabledButCompatibilityDisabled() { config.EnableFeature(); - var settings = config.UseTransport().UseNativeDelayedDelivery(); + var settings = config.UseTransport().NativeDelayedDelivery(); settings.DisableTimeoutManagerCompatibility(); }); } diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_native_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_native_mode.cs index 1256c0370..fd4a4302e 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_native_mode.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_native_mode.cs @@ -43,7 +43,7 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(config => config.UseTransport().UseNativeDelayedDelivery()); + EndpointSetup(config => config.UseTransport().NativeDelayedDelivery()); } public class MyMessageHandler : IHandleMessages diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_timeout_manager_compatibility_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_timeout_manager_compatibility_mode.cs index 77fbf7273..074c4409b 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_timeout_manager_compatibility_mode.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_in_timeout_manager_compatibility_mode.cs @@ -71,7 +71,7 @@ public CompatibilityModeEndpoint() EndpointSetup(c => { c.EnableFeature(); //Because the acceptance tests framework disables it by default. - c.UseTransport().UseNativeDelayedDelivery(); + c.UseTransport().NativeDelayedDelivery(); }); } } diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_to_the_past_in_native_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_to_the_past_in_native_mode.cs index a065a41fa..3e0ca8f77 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_to_the_past_in_native_mode.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_deferring_a_message_to_the_past_in_native_mode.cs @@ -37,7 +37,7 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(config => config.UseTransport().UseNativeDelayedDelivery()); + EndpointSetup(config => config.UseTransport().NativeDelayedDelivery()); } public class MyMessageHandler : IHandleMessages diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_using_TTBR_for_deferred_message_in_native_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_using_TTBR_for_deferred_message_in_native_mode.cs index 2e625aaa5..715c40257 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_using_TTBR_for_deferred_message_in_native_mode.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativeTimeouts/When_using_TTBR_for_deferred_message_in_native_mode.cs @@ -36,7 +36,7 @@ public class Endpoint : EndpointConfigurationBuilder { public Endpoint() { - EndpointSetup(config => config.UseTransport().UseNativeDelayedDelivery()); + EndpointSetup(config => config.UseTransport().NativeDelayedDelivery()); } public class MyMessageHandler : IHandleMessages diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs index e2acf1d8f..93db9a991 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs @@ -44,7 +44,7 @@ public async Task It_returns_type_for_headers_column() static async Task ResetQueue(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(QueueTableName); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index 363833ce1..0bf53d35b 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -107,7 +107,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser, null)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) @@ -121,7 +121,7 @@ Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(validAddress); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_message_receive_takes_long.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_message_receive_takes_long.cs index 94aa14a63..46f052328 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_message_receive_takes_long.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_message_receive_takes_long.cs @@ -95,7 +95,7 @@ static async Task ReceiveWithLongHandling(TableBasedQueue tableBasedQueue, SqlCo static Task CreateQueueIfNotExists(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(QueueTableName); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_receiving_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_receiving_messages.cs index 3c9cd6894..3ac3994aa 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_receiving_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_receiving_messages.cs @@ -30,7 +30,7 @@ public async Task Should_stop_pumping_messages_after_first_unsuccessful_receive( var sqlConnectionFactory = SqlConnectionFactory.Default(connectionString); var pump = new MessagePump( - m => new ProcessWithNoTransaction(sqlConnectionFactory), + m => new ProcessWithNoTransaction(sqlConnectionFactory, null), qa => qa == "input" ? (TableBasedQueue)inputQueue : new TableBasedQueue(parser.Parse(qa).QualifiedTableName, qa), new QueuePurger(sqlConnectionFactory), new ExpiredMessagesPurger(_ => sqlConnectionFactory.OpenNewConnection(), 0, false), diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index 686911d94..8007b1f4a 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -134,7 +134,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser, null)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressParser) @@ -148,7 +148,7 @@ Task PurgeOutputQueue(QueueAddressTranslator addressParser) static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressParser); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressParser, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(validAddress); diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeiveryTableBasedQueueFactory.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeiveryTableBasedQueueFactory.cs deleted file mode 100644 index 89ee309ad..000000000 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeiveryTableBasedQueueFactory.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Data.SqlClient; - using System.Linq; - using System.Threading.Tasks; - using DelayedDelivery; - using DeliveryConstraints; - using Performance.TimeToBeReceived; - - class DelayedDeliveryTableBasedQueueOperationsReader : ITableBasedQueueOperationsReader - { - public DelayedDeliveryTableBasedQueueOperationsReader(DelayedMessageTable delayedMessageTable, ITableBasedQueueOperationsReader immediateDeliveryQueueOperationsReader) - { - this.delayedMessageTable = delayedMessageTable; - this.immediateDeliveryQueueOperationsReader = immediateDeliveryQueueOperationsReader; - } - - public Func Get(UnicastTransportOperation operation) - { - var behavior = GetDueTime(operation); - TryGetConstraint(operation, out DiscardIfNotReceivedBefore discardIfNotReceivedBefore); - if (behavior.Defer) - { - // align with TimeoutManager behavior - if (discardIfNotReceivedBefore != null && discardIfNotReceivedBefore.MaxTime < TimeSpan.MaxValue) - { - throw new Exception("Delayed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to delay messages of this type."); - } - - return (conn, trans) => delayedMessageTable.Store(operation.Message, behavior.DueAfter, behavior.Destination, conn, trans); - } - return immediateDeliveryQueueOperationsReader.Get(operation); - } - - static DispatchBehavior GetDueTime(UnicastTransportOperation operation) - { - if (TryGetConstraint(operation, out DoNotDeliverBefore doNotDeliverBefore)) - { - return DispatchBehavior.Deferred(doNotDeliverBefore.At - DateTime.UtcNow, operation.Destination); - } - if (TryGetConstraint(operation, out DelayDeliveryWith delayDeliveryWith)) - { - return DispatchBehavior.Deferred(delayDeliveryWith.Delay, operation.Destination); - } - return DispatchBehavior.Immediately(); - } - - static bool TryGetConstraint(IOutgoingTransportOperation operation, out T constraint) where T : DeliveryConstraint - { - constraint = operation.DeliveryConstraints.OfType().FirstOrDefault(); - return constraint != null; - } - - DelayedMessageTable delayedMessageTable; - ITableBasedQueueOperationsReader immediateDeliveryQueueOperationsReader; - - struct DispatchBehavior - { - public bool Defer; - public TimeSpan DueAfter; - public string Destination; - - public static DispatchBehavior Immediately() - { - return new DispatchBehavior(); - } - - public static DispatchBehavior Deferred(TimeSpan dueAfter, string destination) - { - return new DispatchBehavior - { - DueAfter = dueAfter < TimeSpan.Zero ? TimeSpan.Zero : dueAfter, - Defer = true, - Destination = destination - }; - } - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryInfrastructure.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryInfrastructure.cs deleted file mode 100644 index 992f7ef39..000000000 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryInfrastructure.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using Features; - using Logging; - using Settings; - - static class DelayedDeliveryInfrastructure - { - public static StartupCheckResult CheckForInvalidSettings(SettingsHolder settings) - { - var delayedDeliverySettings = settings.GetOrDefault(); - if (delayedDeliverySettings != null) - { - var sendOnlyEndpoint = settings.GetOrDefault("Endpoint.SendOnly"); - if (sendOnlyEndpoint) - { - return StartupCheckResult.Failed("Native delayed delivery is only supported for endpoints capable of receiving messages."); - } - } - else - { - var timeoutManagerEnabled = settings.IsFeatureActive(typeof(TimeoutManager)); - if (timeoutManagerEnabled) - { - Logger.Warn("Current configuration of the endpoint uses the TimeoutManager feature for delayed delivery - an option which is not recommended for new deployments. SqlTransport native delayed delivery should be used instead. It can be enabled by calling `UseNativeDelayedDelivery()`."); - } - } - - return StartupCheckResult.Success; - } - - static ILog Logger = LogManager.GetLogger("DelayedDeliveryInfrastructure"); - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryMessagePump.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryMessagePump.cs deleted file mode 100644 index 26ca365ea..000000000 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryMessagePump.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Threading.Tasks; - - class DelayedDeliveryMessagePump : IPushMessages - { - public DelayedDeliveryMessagePump(IPushMessages pump, DelayedMessageProcessor delayedMessageProcessor) - { - this.pump = pump; - this.delayedMessageProcessor = delayedMessageProcessor; - } - - public Task Init(Func onMessage, Func> onError, CriticalError criticalError, PushSettings settings) - { - delayedMessageProcessor.Init(settings.InputQueue); - return pump.Init(async context => - { - if (await delayedMessageProcessor.Handle(context).ConfigureAwait(false)) - { - return; - } - await onMessage(context).ConfigureAwait(false); - }, context => - { - delayedMessageProcessor.HandleError(context); - return onError(context); - }, criticalError, settings); - } - - public void Start(PushRuntimeSettings limitations) - { - pump.Start(limitations); - } - - public Task Stop() - { - return pump.Stop(); - } - - IPushMessages pump; - DelayedMessageProcessor delayedMessageProcessor; - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryQueueCreator.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryQueueCreator.cs deleted file mode 100644 index 2ec9ae79c..000000000 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedDeliveryQueueCreator.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System.Data; - using System.Data.SqlClient; - using System.Threading.Tasks; - - class DelayedDeliveryQueueCreator : ICreateQueues - { - public DelayedDeliveryQueueCreator(SqlConnectionFactory connectionFactory, ICreateQueues queueCreator, CanonicalQueueAddress delayedMessageTable, bool createMessageBodyComputedColumn = false) - { - this.connectionFactory = connectionFactory; - this.queueCreator = queueCreator; - this.delayedMessageTable = delayedMessageTable; - this.createMessageBodyComputedColumn = createMessageBodyComputedColumn; - } - - public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string identity) - { - await queueCreator.CreateQueueIfNecessary(queueBindings, identity).ConfigureAwait(false); - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using (var transaction = connection.BeginTransaction()) - { - await CreateDelayedMessageQueue(delayedMessageTable, connection, transaction, createMessageBodyComputedColumn).ConfigureAwait(false); - - transaction.Commit(); - } - } - - static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) - { -#pragma warning disable 618 - var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); -#pragma warning restore 618 - using (var command = new SqlCommand(sql, connection, transaction) - { - CommandType = CommandType.Text - }) - { - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - if (createMessageBodyComputedColumn) - { -#pragma warning disable 618 - var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); -#pragma warning restore 618 - using (var command = new SqlCommand(bodyStringSql, connection, transaction) - { - CommandType = CommandType.Text - }) - { - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - - } - } - - - SqlConnectionFactory connectionFactory; - ICreateQueues queueCreator; - CanonicalQueueAddress delayedMessageTable; - bool createMessageBodyComputedColumn; - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageProcessor.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageProcessor.cs deleted file mode 100644 index 3b98d65e8..000000000 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageProcessor.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System.Threading.Tasks; - using Routing; - - class DelayedMessageProcessor - { - const string ForwardHeader = "NServiceBus.SqlServer.ForwardDestination"; - - public DelayedMessageProcessor(IDispatchMessages dispatcher) - { - this.dispatcher = dispatcher; - } - - public void Init(string localAddress) - { - this.localAddress = localAddress; - } - - public async Task Handle(MessageContext context) - { - context.Headers.TryGetValue(ForwardHeader, out var forwardDestination); - if (forwardDestination == null) - { - //This is not a delayed message. Process in local endpoint instance. - return false; - } - if (forwardDestination == localAddress) - { - context.Headers.Remove(ForwardHeader); - //Do not forward the message. Process in local endpoint instance. - return false; - } - var outgoingMessage = new OutgoingMessage(context.MessageId, context.Headers, context.Body); - var transportOperation = new TransportOperation(outgoingMessage, new UnicastAddressTag(forwardDestination)); - context.Headers.Remove(ForwardHeader); - await dispatcher.Dispatch(new TransportOperations(transportOperation), context.TransportTransaction, context.Extensions).ConfigureAwait(false); - return true; - } - - public void HandleError(ErrorContext context) - { - context.Message.Headers.Remove(ForwardHeader); - } - - IDispatchMessages dispatcher; - string localAddress; - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs index ef8112804..1ad41dcfe 100644 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs +++ b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs @@ -12,7 +12,7 @@ public DelayedMessageTable(string delayedQueueTable, string inputQueueTable) { #pragma warning disable 618 storeCommand = string.Format(SqlConstants.StoreDelayedMessageText, delayedQueueTable); - moveMaturedCommand = string.Format(SqlConstants.MoveMaturedDelayedMessageText, delayedQueueTable, inputQueueTable); + moveDueCommand = string.Format(SqlConstants.MoveDueDelayedMessageText, delayedQueueTable, inputQueueTable); #pragma warning restore 618 } @@ -26,9 +26,9 @@ public async Task Store(OutgoingMessage message, TimeSpan dueAfter, string desti } } - public async Task MoveMaturedMessages(int batchSize, SqlConnection connection, SqlTransaction transaction, CancellationToken cancellationToken) + public async Task MoveDueMessages(int batchSize, SqlConnection connection, SqlTransaction transaction, CancellationToken cancellationToken) { - using (var command = new SqlCommand(moveMaturedCommand, connection, transaction)) + using (var command = new SqlCommand(moveDueCommand, connection, transaction)) { command.Parameters.AddWithValue("BatchSize", batchSize); await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false); @@ -36,6 +36,6 @@ public async Task MoveMaturedMessages(int batchSize, SqlConnection connection, S } string storeCommand; - string moveMaturedCommand; + string moveDueCommand; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageHandler.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DueDelayedMessageProcessor.cs similarity index 86% rename from src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageHandler.cs rename to src/NServiceBus.SqlServer/DelayedDelivery/DueDelayedMessageProcessor.cs index 192e71260..cf9e7a08c 100644 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageHandler.cs +++ b/src/NServiceBus.SqlServer/DelayedDelivery/DueDelayedMessageProcessor.cs @@ -6,9 +6,9 @@ namespace NServiceBus.Transport.SQLServer using System.Threading.Tasks; using Logging; - class DelayedMessageHandler + class DueDelayedMessageProcessor { - public DelayedMessageHandler(DelayedMessageTable table, SqlConnectionFactory connectionFactory, TimeSpan interval, int batchSize) + public DueDelayedMessageProcessor(DelayedMessageTable table, SqlConnectionFactory connectionFactory, TimeSpan interval, int batchSize) { this.table = table; this.connectionFactory = connectionFactory; @@ -44,7 +44,7 @@ async Task MoveMaturedDelayedMessages() { using (var transaction = connection.BeginTransaction()) { - await table.MoveMaturedMessages(batchSize, connection, transaction, cancellationToken).ConfigureAwait(false); + await table.MoveDueMessages(batchSize, connection, transaction, cancellationToken).ConfigureAwait(false); transaction.Commit(); } } @@ -81,6 +81,6 @@ await Task.Delay(interval, cancellationToken).IgnoreCancellation() CancellationTokenSource cancellationTokenSource; Task task; - static ILog Logger = LogManager.GetLogger(); + static ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index 1f88a7b48..0a71bba43 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -90,7 +90,7 @@ ELSE 1 IF (@NOCOUNT = 'ON') SET NOCOUNT ON; IF (@NOCOUNT = 'OFF') SET NOCOUNT OFF;"; - internal const string MoveMaturedDelayedMessageText = @" + internal const string MoveDueDelayedMessageText = @" DECLARE @NOCOUNT VARCHAR(3) = 'OFF'; IF ( (512 & @@OPTIONS) = 512 ) SET @NOCOUNT = 'ON'; SET NOCOUNT ON; diff --git a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs index e68225ceb..12120004b 100644 --- a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs +++ b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs @@ -5,19 +5,32 @@ namespace NServiceBus.Transport.SQLServer using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; + using DelayedDelivery; using DeliveryConstraints; using Performance.TimeToBeReceived; class TableBasedQueueOperationsReader : ITableBasedQueueOperationsReader { - public TableBasedQueueOperationsReader(QueueAddressTranslator addressTranslator) + public TableBasedQueueOperationsReader(QueueAddressTranslator addressTranslator, DelayedMessageTable delayedMessageTable) { this.addressTranslator = addressTranslator; + this.delayedMessageTable = delayedMessageTable; } public Func Get(UnicastTransportOperation operation) { + var behavior = GetDueTime(operation); TryGetConstraint(operation, out DiscardIfNotReceivedBefore discardIfNotReceivedBefore); + if (behavior.Defer) + { + // align with TimeoutManager behavior + if (discardIfNotReceivedBefore != null && discardIfNotReceivedBefore.MaxTime < TimeSpan.MaxValue) + { + throw new Exception("Delayed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to delay messages of this type."); + } + + return (conn, trans) => delayedMessageTable.Store(operation.Message, behavior.DueAfter, behavior.Destination, conn, trans); + } var address = addressTranslator.Parse(operation.Destination); var key = Tuple.Create(address.QualifiedTableName, address.Address); @@ -26,11 +39,48 @@ public Func Get(UnicastTransportOperation o return (conn, trans) => queue.Send(operation.Message, discardIfNotReceivedBefore?.MaxTime ?? TimeSpan.MaxValue, conn, trans); } - static void TryGetConstraint(IOutgoingTransportOperation operation, out T constraint) where T : DeliveryConstraint + static DispatchBehavior GetDueTime(UnicastTransportOperation operation) + { + if (TryGetConstraint(operation, out DoNotDeliverBefore doNotDeliverBefore)) + { + return DispatchBehavior.Deferred(doNotDeliverBefore.At - DateTime.UtcNow, operation.Destination); + } + if (TryGetConstraint(operation, out DelayDeliveryWith delayDeliveryWith)) + { + return DispatchBehavior.Deferred(delayDeliveryWith.Delay, operation.Destination); + } + return DispatchBehavior.Immediately(); + } + + static bool TryGetConstraint(IOutgoingTransportOperation operation, out T constraint) where T : DeliveryConstraint { constraint = operation.DeliveryConstraints.OfType().FirstOrDefault(); + return constraint != null; + } + + struct DispatchBehavior + { + public bool Defer; + public TimeSpan DueAfter; + public string Destination; + + public static DispatchBehavior Immediately() + { + return new DispatchBehavior(); + } + + public static DispatchBehavior Deferred(TimeSpan dueAfter, string destination) + { + return new DispatchBehavior + { + DueAfter = dueAfter < TimeSpan.Zero ? TimeSpan.Zero : dueAfter, + Defer = true, + Destination = destination + }; + } } + DelayedMessageTable delayedMessageTable; QueueAddressTranslator addressTranslator; ConcurrentDictionary, TableBasedQueue> cache = new ConcurrentDictionary, TableBasedQueue>(); } diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs index e8af67c15..c1c73ec20 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs @@ -9,7 +9,8 @@ class ProcessWithNativeTransaction : ReceiveStrategy { - public ProcessWithNativeTransaction(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, bool transactionForReceiveOnly = false) + public ProcessWithNativeTransaction(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, IQueueDispatcher queueDispatcher, bool transactionForReceiveOnly = false) + : base(queueDispatcher) { this.connectionFactory = connectionFactory; this.failureInfoStorage = failureInfoStorage; diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs index c70970483..21cbd819d 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs @@ -7,7 +7,8 @@ namespace NServiceBus.Transport.SQLServer class ProcessWithNoTransaction : ReceiveStrategy { - public ProcessWithNoTransaction(SqlConnectionFactory connectionFactory) + public ProcessWithNoTransaction(SqlConnectionFactory connectionFactory, IQueueDispatcher dispatcher) + : base(dispatcher) { this.connectionFactory = connectionFactory; } diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs index 3b18dafae..9d86ff32a 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs @@ -7,7 +7,8 @@ class ProcessWithTransactionScope : ReceiveStrategy { - public ProcessWithTransactionScope(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage) + public ProcessWithTransactionScope(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, IQueueDispatcher dispatcher) + : base(dispatcher) { this.transactionOptions = transactionOptions; this.connectionFactory = connectionFactory; diff --git a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs index eddc57eb6..6a45d31a9 100644 --- a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs +++ b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs @@ -8,10 +8,11 @@ namespace NServiceBus.Transport.SQLServer class QueueCreator : ICreateQueues { - public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, bool createMessageBodyColumn = false) + public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, CanonicalQueueAddress delayedMessageTable, bool createMessageBodyColumn = false) { this.connectionFactory = connectionFactory; this.addressTranslator = addressTranslator; + this.delayedMessageTable = delayedMessageTable; this.createMessageBodyColumn = createMessageBodyColumn; } @@ -31,6 +32,14 @@ public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string ide } transaction.Commit(); } + + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var transaction = connection.BeginTransaction()) + { + await CreateDelayedMessageQueue(delayedMessageTable, connection, transaction, createMessageBodyColumn).ConfigureAwait(false); + + transaction.Commit(); + } } static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyColumn) @@ -57,8 +66,37 @@ static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlCo } } + static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) + { +#pragma warning disable 618 + var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); +#pragma warning restore 618 + using (var command = new SqlCommand(sql, connection, transaction) + { + CommandType = CommandType.Text + }) + { + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + if (createMessageBodyComputedColumn) + { +#pragma warning disable 618 + var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); +#pragma warning restore 618 + using (var command = new SqlCommand(bodyStringSql, connection, transaction) + { + CommandType = CommandType.Text + }) + { + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + + } + } + SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; + CanonicalQueueAddress delayedMessageTable; bool createMessageBodyColumn; } } diff --git a/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs b/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs index 6237a9fd2..ef949d5cc 100644 --- a/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs +++ b/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs @@ -1,6 +1,7 @@ namespace NServiceBus.Transport.SQLServer { using System; + using System.Collections.Generic; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; @@ -14,6 +15,11 @@ abstract class ReceiveStrategy Func onMessage; Func> onError; + protected ReceiveStrategy(IQueueDispatcher queueDispatcher) + { + this.queueDispatcher = queueDispatcher; + } + public void Init(TableBasedQueue inputQueue, TableBasedQueue errorQueue, Func onMessage, Func> onError, CriticalError criticalError) { InputQueue = inputQueue; @@ -69,6 +75,7 @@ protected async Task HandleError(Exception exception, Message try { var errorContext = new ErrorContext(exception, message.Headers, message.TransportId, message.Body, transportTransaction, processingAttempts); + errorContext.Message.Headers.Remove(ForwardHeader); return await onError(errorContext).ConfigureAwait(false); } @@ -80,6 +87,31 @@ protected async Task HandleError(Exception exception, Message } } + public async Task TryHandleDelayedMessage(MessageContext context) + { + context.Headers.TryGetValue(ForwardHeader, out var forwardDestination); + if (forwardDestination == null) + { + //This is not a delayed message. Process in local endpoint instance. + return false; + } + if (forwardDestination == InputQueue.Name) + { + context.Headers.Remove(ForwardHeader); + //Do not forward the message. Process in local endpoint instance. + return false; + } + var outgoingMessage = new OutgoingMessage(context.MessageId, context.Headers, context.Body); + context.Headers.Remove(ForwardHeader); + await queueDispatcher.DispatchAsNonIsolated(new List + { + new UnicastTransportOperation(outgoingMessage, forwardDestination) + }, context.TransportTransaction).ConfigureAwait(false); + return true; + } + + const string ForwardHeader = "NServiceBus.SqlServer.ForwardDestination"; + IQueueDispatcher queueDispatcher; CriticalError criticalError; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 3fdb60741..d74f5c110 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -21,7 +21,7 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat this.connectionString = connectionString; schemaAndCatalogSettings = settings.GetOrCreate(); - delayedDeliverySettings = settings.GetOrDefault(); + delayedDeliverySettings = settings.GetOrCreate(); var timeoutManagerFeatureDisabled = !settings.IsFeatureEnabled(typeof(TimeoutManager)); diagnostics.Add("NServiceBus.Transport.SqlServer.TimeoutManager", new @@ -29,16 +29,13 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat FeatureEnabled = !timeoutManagerFeatureDisabled }); - if (delayedDeliverySettings != null) + if (timeoutManagerFeatureDisabled) { - if (timeoutManagerFeatureDisabled) - { - delayedDeliverySettings.DisableTimeoutManagerCompatibility(); - } - - settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); + delayedDeliverySettings.DisableTimeoutManagerCompatibility(); } + settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); + tableBasedSubscriptions = new TableBasedSubscriptions(CreateConnectionFactory()); } @@ -47,11 +44,8 @@ public override IEnumerable DeliveryConstraints get { yield return typeof(DiscardIfNotReceivedBefore); - if (delayedDeliverySettings != null) - { - yield return typeof(DoNotDeliverBefore); - yield return typeof(DelayDeliveryWith); - } + yield return typeof(DoNotDeliverBefore); + yield return typeof(DelayDeliveryWith); } } @@ -105,28 +99,9 @@ public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() var delayedMessageStore = GetDelayedQueueTableName(); - var sendInfra = ConfigureSendInfrastructure(); return new TransportReceiveInfrastructure( - () => - { - var pump = new MessagePump(receiveStrategyFactory, queueFactory, queuePurger, expiredMessagesPurger, queuePeeker, schemaVerification, waitTimeCircuitBreaker); - if (delayedDeliverySettings == null) - { - return pump; - } - var dispatcher = sendInfra.DispatcherFactory(); - var delayedMessageProcessor = new DelayedMessageProcessor(dispatcher); - return new DelayedDeliveryMessagePump(pump, delayedMessageProcessor); - }, - () => - { - var creator = new QueueCreator(connectionFactory, addressTranslator, createMessageBodyComputedColumn); - if (delayedDeliverySettings == null) - { - return creator; - } - return new DelayedDeliveryQueueCreator(connectionFactory, creator, delayedMessageStore, createMessageBodyComputedColumn); - }, + () => new MessagePump(receiveStrategyFactory, queueFactory, queuePurger, expiredMessagesPurger, queuePeeker, schemaVerification, waitTimeCircuitBreaker), + () => new QueueCreator(connectionFactory, addressTranslator, delayedMessageStore, createMessageBodyComputedColumn), () => CheckForAmbientTransactionEnlistmentSupport(connectionFactory, scopeOptions.TransactionOptions)); } @@ -140,24 +115,26 @@ SqlConnectionFactory CreateConnectionFactory() return SqlConnectionFactory.Default(connectionString); } - static ReceiveStrategy SelectReceiveStrategy(TransportTransactionMode minimumConsistencyGuarantee, TransactionOptions options, SqlConnectionFactory connectionFactory) + ReceiveStrategy SelectReceiveStrategy(TransportTransactionMode minimumConsistencyGuarantee, TransactionOptions options, SqlConnectionFactory connectionFactory) { + var dispatcher = new TableBasedQueueDispatcher(connectionFactory, new TableBasedQueueOperationsReader(addressTranslator, CreateDelayedMessageTable())); + if (minimumConsistencyGuarantee == TransportTransactionMode.TransactionScope) { - return new ProcessWithTransactionScope(options, connectionFactory, new FailureInfoStorage(10000)); + return new ProcessWithTransactionScope(options, connectionFactory, new FailureInfoStorage(10000), dispatcher); } if (minimumConsistencyGuarantee == TransportTransactionMode.SendsAtomicWithReceive) { - return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000)); + return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), dispatcher); } if (minimumConsistencyGuarantee == TransportTransactionMode.ReceiveOnly) { - return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), transactionForReceiveOnly: true); + return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), dispatcher, transactionForReceiveOnly: true); } - return new ProcessWithNoTransaction(connectionFactory); + return new ProcessWithNoTransaction(connectionFactory, dispatcher); } ExpiredMessagesPurger CreateExpiredMessagesPurger(SqlConnectionFactory connectionFactory) @@ -216,17 +193,14 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() return new TransportSendInfrastructure( () => { - ITableBasedQueueOperationsReader queueOperationsReader = new TableBasedQueueOperationsReader(addressTranslator); - if (delayedDeliverySettings != null) - { - queueOperationsReader = new DelayedDeliveryTableBasedQueueOperationsReader(CreateDelayedMessageTable(), queueOperationsReader); - } + ITableBasedQueueOperationsReader queueOperationsReader = new TableBasedQueueOperationsReader(addressTranslator, CreateDelayedMessageTable()); var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, tableBasedSubscriptions); return dispatcher; }, - () => Task.FromResult(DelayedDeliveryInfrastructure.CheckForInvalidSettings(settings))); + () => Task.FromResult(StartupCheckResult.Success)); } + DelayedMessageTable CreateDelayedMessageTable() { var deletedQueueTableName = GetDelayedQueueTableName(); @@ -251,10 +225,6 @@ LogicalAddress GetLogicalAddress() CanonicalQueueAddress GetDelayedQueueTableName() { - if (delayedDeliverySettings == null) - { - return null; - } if (string.IsNullOrEmpty(delayedDeliverySettings.Suffix)) { throw new Exception("Native delayed delivery feature requires configuring a table suffix."); @@ -274,33 +244,23 @@ public override async Task Start() settings.AddStartupDiagnosticsSection(diagnosticSection.Key, diagnosticSection.Value); } - if (delayedDeliverySettings == null) + settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new { - settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new - { - Native = false - }); - } - else - { - settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new - { - Native = true, - delayedDeliverySettings.Suffix, - delayedDeliverySettings.Interval, - BatchSize = delayedDeliverySettings.MatureBatchSize, - TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" - }); - - var delayedMessageTable = CreateDelayedMessageTable(); - delayedMessageHandler = new DelayedMessageHandler(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); - delayedMessageHandler.Start(); - } + Native = true, + delayedDeliverySettings.Suffix, + delayedDeliverySettings.Interval, + BatchSize = delayedDeliverySettings.MatureBatchSize, + TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" + }); + + var delayedMessageTable = CreateDelayedMessageTable(); + dueDelayedMessageProcessor = new DueDelayedMessageProcessor(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); + dueDelayedMessageProcessor.Start(); } public override Task Stop() { - return delayedMessageHandler?.Stop() ?? Task.FromResult(0); + return dueDelayedMessageProcessor?.Stop() ?? Task.FromResult(0); } public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() @@ -338,7 +298,7 @@ public override string MakeCanonicalForm(string transportAddress) string connectionString; SettingsHolder settings; EndpointSchemaAndCatalogSettings schemaAndCatalogSettings; - DelayedMessageHandler delayedMessageHandler; + DueDelayedMessageProcessor dueDelayedMessageProcessor; DelayedDeliverySettings delayedDeliverySettings; Dictionary diagnostics = new Dictionary(); TableBasedSubscriptions tableBasedSubscriptions; diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index b2cca8110..a16dfb38e 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -180,12 +180,12 @@ public static TransportExtensions WithPeekDelay(this Transpo /// /// Enables native delayed delivery. /// - public static DelayedDeliverySettings UseNativeDelayedDelivery(this TransportExtensions transportExtensions) + public static DelayedDeliverySettings NativeDelayedDelivery(this TransportExtensions transportExtensions) { var sendOnlyEndpoint = transportExtensions.GetSettings().GetOrDefault("Endpoint.SendOnly"); if (sendOnlyEndpoint) { - throw new Exception("Native delayed delivery is only supported for endpoints capable of receiving messages."); + throw new Exception("Delayed delivery is only supported for endpoints capable of receiving messages."); } var settings = transportExtensions.GetSettings().GetOrCreate(); From 8a2f60417150d8f71ad5a1e3565dbdd448ee7daf Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 29 Oct 2019 14:32:00 +0800 Subject: [PATCH 05/31] Subscription Caching --- .../When_dispatching_messages.cs | 12 +++- .../When_using_ttbr.cs | 12 +++- .../APIApprovals.Approve.approved.txt | 4 +- .../Configuration/TransportPubSubOptions.cs | 9 +++ ...re.cs => IManageTransportSubscriptions.cs} | 4 +- .../PubSub/SubscriptionCache.cs | 62 +++++++++++++++++++ .../PubSub/SubscriptionManager.cs | 10 +-- .../PubSub/TableBasedSubscriptions.cs | 15 +---- .../Receiving/QueueCreator.cs | 16 ++++- .../Sending/MessageDispatcher.cs | 4 +- .../SqlServerTransportInfrastructure.cs | 22 ++++--- .../SqlServerTransportSettingsExtensions.cs | 10 +++ 12 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs rename src/NServiceBus.SqlServer/PubSub/{IKnowWhereTheSubscriptionsAre.cs => IManageTransportSubscriptions.cs} (50%) create mode 100644 src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index 0bf53d35b..ee86b5999 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -137,12 +137,22 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato SqlConnectionFactory sqlConnectionFactory; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : IKnowWhereTheSubscriptionsAre + class NoOpSubscriptionStore : IManageTransportSubscriptions { public Task> GetSubscribersForEvent(string eventType) { return Task.FromResult(new List()); } + + public Task Subscribe(string endpointName, string endpointAddress, string eventType) + { + return Task.FromResult(0); + } + + public Task Unsubscribe(string endpointName, string eventType) + { + return Task.FromResult(0); + } } interface IContextProvider : IDisposable diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index 8007b1f4a..a6924dad3 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -163,12 +163,22 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S const string validAddress = "TTBRTests"; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : IKnowWhereTheSubscriptionsAre + class NoOpSubscriptionStore : IManageTransportSubscriptions { public Task> GetSubscribersForEvent(string eventType) { return Task.FromResult(new List()); } + + public Task Subscribe(string endpointName, string endpointAddress, string eventType) + { + return Task.FromResult(0); + } + + public Task Unsubscribe(string endpointName, string eventType) + { + return Task.FromResult(0); + } } } diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 0921f0b4f..d9fd5dfc3 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -65,6 +65,7 @@ CREATE NONCLUSTERED INDEX [Index_Due] ON {0} EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static readonly string CreateQueueText; public static readonly string CreateSubscriptionTableText; + public static readonly string GetSubscribersText; public static readonly string PeekText; public static readonly string PurgeBatchOfExpiredMessagesText; public static readonly string PurgeText; @@ -75,19 +76,20 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; } public class static SqlServerTransportSettingsExtensions { + public static void CacheSubscriptionsFor(this NServiceBus.TransportExtensions transportExtensions, System.TimeSpan timeToCacheSubscriptions) { } public static NServiceBus.TransportExtensions CreateMessageBodyComputedColumn(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions DefaultSchema(this NServiceBus.TransportExtensions transportExtensions, string schemaName) { } [System.ObsoleteAttribute("Multi-instance mode has been deprecated. Use Transport Bridge and/or multi-catalo" + "g addressing instead. The member currently throws a NotImplementedException. Wil" + "l be removed in version 5.0.0.", true)] public static NServiceBus.TransportExtensions EnableLegacyMultiInstanceMode(this NServiceBus.TransportExtensions transportExtensions, System.Func> sqlConnectionFactory) { } + public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings NativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions PurgeExpiredMessagesOnStartup(this NServiceBus.TransportExtensions transportExtensions, System.Nullable purgeBatchSize) { } public static NServiceBus.TransportExtensions TimeToWaitBeforeTriggeringCircuitBreaker(this NServiceBus.TransportExtensions transportExtensions, System.TimeSpan waitTime) { } public static NServiceBus.TransportExtensions TransactionScopeOptions(this NServiceBus.TransportExtensions transportExtensions, System.Nullable timeout = null, System.Nullable isolationLevel = null) { } public static NServiceBus.TransportExtensions UseCatalogForEndpoint(this NServiceBus.TransportExtensions transportExtensions, string endpointName, string catalog) { } public static NServiceBus.TransportExtensions UseCatalogForQueue(this NServiceBus.TransportExtensions transportExtensions, string queueName, string catalog) { } public static NServiceBus.TransportExtensions UseCustomSqlConnectionFactory(this NServiceBus.TransportExtensions transportExtensions, System.Func> sqlConnectionFactory) { } - public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings UseNativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions UseSchemaForEndpoint(this NServiceBus.TransportExtensions transportExtensions, string endpointName, string schema) { } public static NServiceBus.TransportExtensions UseSchemaForQueue(this NServiceBus.TransportExtensions transportExtensions, string queueName, string schema) { } public static NServiceBus.TransportExtensions WithPeekDelay(this NServiceBus.TransportExtensions transportExtensions, System.Nullable delay = null) { } diff --git a/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs b/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs new file mode 100644 index 000000000..cf7f0758c --- /dev/null +++ b/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + + class TransportPubSubOptions + { + public TimeSpan? TimeToCacheSubscription { get; set; } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs b/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs similarity index 50% rename from src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs rename to src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs index 1a6e9ce68..66676165f 100644 --- a/src/NServiceBus.SqlServer/PubSub/IKnowWhereTheSubscriptionsAre.cs +++ b/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs @@ -3,8 +3,10 @@ namespace NServiceBus.Transport.SQLServer using System.Collections.Generic; using System.Threading.Tasks; - interface IKnowWhereTheSubscriptionsAre + interface IManageTransportSubscriptions { Task> GetSubscribersForEvent(string eventType); + Task Subscribe(string endpointName, string endpointAddress, string eventType); + Task Unsubscribe(string endpointName, string eventType); } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs new file mode 100644 index 000000000..0b93ad83b --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs @@ -0,0 +1,62 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Threading.Tasks; + + class SubscriptionCache : IManageTransportSubscriptions + { + public SubscriptionCache(IManageTransportSubscriptions inner, TimeSpan cacheFor) + { + this.inner = inner; + this.cacheFor = cacheFor; + } + + public Task> GetSubscribersForEvent(string eventType) + { + var cacheItem = Cache.GetOrAdd(eventType, + _ => new CacheItem + { + Stored = DateTime.UtcNow, + Subscribers = inner.GetSubscribersForEvent(eventType) + }); + + var age = DateTime.UtcNow - cacheItem.Stored; + if (age >= cacheFor) + { + cacheItem.Subscribers = inner.GetSubscribersForEvent(eventType); + cacheItem.Stored = DateTime.UtcNow; + } + + return cacheItem.Subscribers; + } + + public async Task Subscribe(string endpointName, string endpointAddress, string eventType) + { + await inner.Subscribe(endpointName, endpointAddress, eventType).ConfigureAwait(false); + ClearForMessageType(eventType); + } + + public async Task Unsubscribe(string endpointName, string eventType) + { + await inner.Unsubscribe(endpointName, eventType).ConfigureAwait(false); + ClearForMessageType(eventType); + } + + void ClearForMessageType(string eventType) + { + Cache.TryRemove(eventType, out _); + } + + TimeSpan cacheFor; + IManageTransportSubscriptions inner; + ConcurrentDictionary Cache = new ConcurrentDictionary(); + + class CacheItem + { + public DateTime Stored { get; set; } + public Task> Subscribers { get; set; } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index 9787ec0b3..824aace0b 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -7,20 +7,20 @@ namespace NServiceBus.Transport.SQLServer class SubscriptionManager : IManageSubscriptions { - TableBasedSubscriptions tableBasedSubscriptions; + IManageTransportSubscriptions subscriptions; string endpointName; string localAddress; - public SubscriptionManager(TableBasedSubscriptions tableBasedSubscriptions, ReadOnlySettings settings) + public SubscriptionManager(IManageTransportSubscriptions subscriptions, ReadOnlySettings settings) { - this.tableBasedSubscriptions = tableBasedSubscriptions; + this.subscriptions = subscriptions; endpointName = settings.EndpointName(); localAddress = settings.LocalAddress(); } public Task Subscribe(Type eventType, ContextBag context) { - return tableBasedSubscriptions.Subscribe( + return subscriptions.Subscribe( endpointName, localAddress, eventType.ToString() @@ -29,7 +29,7 @@ public Task Subscribe(Type eventType, ContextBag context) public Task Unsubscribe(Type eventType, ContextBag context) { - return tableBasedSubscriptions.Unsubscribe( + return subscriptions.Unsubscribe( endpointName, eventType.ToString() ); diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs index 4d8749ce6..97a5c841f 100644 --- a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs @@ -5,7 +5,7 @@ namespace NServiceBus.Transport.SQLServer using System.Threading.Tasks; using System.Transactions; - class TableBasedSubscriptions : IKnowWhereTheSubscriptionsAre + class TableBasedSubscriptions : IManageTransportSubscriptions { SqlConnectionFactory connectionFactory; string subscribeCommand; @@ -13,7 +13,6 @@ class TableBasedSubscriptions : IKnowWhereTheSubscriptionsAre string getSubscribersCommand; string createSubscriptionsTableCommand; - public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; @@ -82,17 +81,5 @@ public async Task> GetSubscribersForEvent(string eventType) } } } - - public async Task CreateSubscriptionTable() - { - // TODO: Call this from somewhere - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using (var command = connection.CreateCommand()) - { - command.CommandText = createSubscriptionsTableCommand; - - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs index 6a45d31a9..0be99dfbb 100644 --- a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs +++ b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs @@ -37,7 +37,7 @@ public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string ide using (var transaction = connection.BeginTransaction()) { await CreateDelayedMessageQueue(delayedMessageTable, connection, transaction, createMessageBodyColumn).ConfigureAwait(false); - + await CreateSubscriptionsTable(connection, transaction).ConfigureAwait(false); transaction.Commit(); } } @@ -94,6 +94,20 @@ static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueue } } + static async Task CreateSubscriptionsTable(SqlConnection connection, SqlTransaction transaction) + { +#pragma warning disable 618 + var sql = SqlConstants.CreateSubscriptionTableText; +#pragma warning restore 618 + using (var command = new SqlCommand(sql, connection, transaction) + { + CommandType = CommandType.Text + }) + { + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + } + SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; CanonicalQueueAddress delayedMessageTable; diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index 9c00f1ff1..f3dce8f4b 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -9,7 +9,7 @@ class MessageDispatcher : IDispatchMessages { - public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IKnowWhereTheSubscriptionsAre subscriptions) + public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IManageTransportSubscriptions subscriptions) { this.dispatcher = dispatcher; this.addressTranslator = addressTranslator; @@ -61,7 +61,7 @@ async Task> ConvertToUnicastOperations(Multicast IQueueDispatcher dispatcher; QueueAddressTranslator addressTranslator; - IKnowWhereTheSubscriptionsAre subscriptions; + IManageTransportSubscriptions subscriptions; class DeduplicationKey { diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index d74f5c110..69a268ea4 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -36,7 +36,14 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); - tableBasedSubscriptions = new TableBasedSubscriptions(CreateConnectionFactory()); + var pubSubSettings = settings.GetOrCreate(); + + subscriptions = new TableBasedSubscriptions(CreateConnectionFactory()); + + if (pubSubSettings.TimeToCacheSubscription.HasValue) + { + subscriptions = new SubscriptionCache(subscriptions, pubSubSettings.TimeToCacheSubscription.Value); + } } public override IEnumerable DeliveryConstraints @@ -194,7 +201,7 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() () => { ITableBasedQueueOperationsReader queueOperationsReader = new TableBasedQueueOperationsReader(addressTranslator, CreateDelayedMessageTable()); - var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, tableBasedSubscriptions); + var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, subscriptions); return dispatcher; }, () => Task.FromResult(StartupCheckResult.Success)); @@ -234,11 +241,8 @@ CanonicalQueueAddress GetDelayedQueueTableName() return addressTranslator.GetCanonicalForm(delayedQueueAddress); } - public override async Task Start() + public override Task Start() { - // TODO: Move this to an installer? - await tableBasedSubscriptions.CreateSubscriptionTable().ConfigureAwait(false); - foreach (var diagnosticSection in diagnostics) { settings.AddStartupDiagnosticsSection(diagnosticSection.Key, diagnosticSection.Value); @@ -256,6 +260,8 @@ public override async Task Start() var delayedMessageTable = CreateDelayedMessageTable(); dueDelayedMessageProcessor = new DueDelayedMessageProcessor(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); dueDelayedMessageProcessor.Start(); + + return Task.FromResult(0); } public override Task Stop() @@ -265,7 +271,7 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { - return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(tableBasedSubscriptions, settings)); + return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptions, settings)); } public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) @@ -301,6 +307,6 @@ public override string MakeCanonicalForm(string transportAddress) DueDelayedMessageProcessor dueDelayedMessageProcessor; DelayedDeliverySettings delayedDeliverySettings; Dictionary diagnostics = new Dictionary(); - TableBasedSubscriptions tableBasedSubscriptions; + IManageTransportSubscriptions subscriptions; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index a16dfb38e..13b1858f3 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -218,6 +218,16 @@ public static TransportExtensions CreateMessageBodyComputedC return transportExtensions; } + /// + /// Instructs the transport to cache subscriptions + /// + /// The to extend. + /// The length of time to cache subscriptions. + public static void CacheSubscriptionsFor(this TransportExtensions transportExtensions, TimeSpan timeToCacheSubscriptions) + { + transportExtensions.GetSettings().GetOrCreate().TimeToCacheSubscription = timeToCacheSubscriptions; + } + /// /// Enables multi-instance mode. /// From 7d30746b8935d29e0139c93f7ad83766a00eec8e Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 30 Oct 2019 14:27:43 +0800 Subject: [PATCH 06/31] Polymorphic dispatch --- .../When_dispatching_messages.cs | 6 +-- .../When_using_ttbr.cs | 6 +-- .../PubSub/IManageTransportSubscriptions.cs | 7 +-- ...hicTransportSubscriptionsManagerWrapper.cs | 44 +++++++++++++++++++ .../PubSub/SubscriptionCache.cs | 10 ++--- .../PubSub/SubscriptionManager.cs | 11 +---- .../PubSub/TableBasedSubscriptions.cs | 15 +++---- .../PubSub/TransportPubSub.cs | 34 ++++++++++++++ .../Sending/MessageDispatcher.cs | 2 +- .../SqlServerTransportInfrastructure.cs | 14 ++---- 10 files changed, 107 insertions(+), 42 deletions(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index ee86b5999..1f35de957 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -139,17 +139,17 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato // TODO: Figure out if this is appropriate in this test class NoOpSubscriptionStore : IManageTransportSubscriptions { - public Task> GetSubscribersForEvent(string eventType) + public Task> GetSubscribersForEvent(Type eventType) { return Task.FromResult(new List()); } - public Task Subscribe(string endpointName, string endpointAddress, string eventType) + public Task Subscribe(string endpointName, string endpointAddress, Type eventType) { return Task.FromResult(0); } - public Task Unsubscribe(string endpointName, string eventType) + public Task Unsubscribe(string endpointName, Type eventType) { return Task.FromResult(0); } diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index a6924dad3..dcb839e34 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -165,17 +165,17 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S // TODO: Figure out if this is appropriate in this test class NoOpSubscriptionStore : IManageTransportSubscriptions { - public Task> GetSubscribersForEvent(string eventType) + public Task> GetSubscribersForEvent(Type eventType) { return Task.FromResult(new List()); } - public Task Subscribe(string endpointName, string endpointAddress, string eventType) + public Task Subscribe(string endpointName, string endpointAddress, Type eventType) { return Task.FromResult(0); } - public Task Unsubscribe(string endpointName, string eventType) + public Task Unsubscribe(string endpointName, Type eventType) { return Task.FromResult(0); } diff --git a/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs index 66676165f..2fe7e3651 100644 --- a/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs @@ -1,12 +1,13 @@ namespace NServiceBus.Transport.SQLServer { + using System; using System.Collections.Generic; using System.Threading.Tasks; interface IManageTransportSubscriptions { - Task> GetSubscribersForEvent(string eventType); - Task Subscribe(string endpointName, string endpointAddress, string eventType); - Task Unsubscribe(string endpointName, string eventType); + Task> GetSubscribersForEvent(Type eventType); + Task Subscribe(string endpointName, string endpointAddress, Type eventType); + Task Unsubscribe(string endpointName, Type eventType); } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs b/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs new file mode 100644 index 000000000..1a4468166 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs @@ -0,0 +1,44 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Unicast.Messages; + + class PolymorphicTransportSubscriptionsManagerWrapper : IManageTransportSubscriptions + { + public PolymorphicTransportSubscriptionsManagerWrapper(IManageTransportSubscriptions inner, MessageMetadataRegistry messageMetadataRegistry) + { + this.inner = inner; + this.messageMetadataRegistry = messageMetadataRegistry; + } + + public async Task> GetSubscribersForEvent(Type eventType) + { + var messageMetadata = messageMetadataRegistry.GetMessageMetadata(eventType); + + var tasks = messageMetadata.MessageHierarchy.Select(t => inner.GetSubscribersForEvent(t)).ToArray(); + + await Task.WhenAll(tasks).ConfigureAwait(false); + + var results = tasks.SelectMany(t => t.Result).Distinct().ToList(); + + return results; + } + + public Task Subscribe(string endpointName, string endpointAddress, Type eventType) + { + return inner.Subscribe(endpointName, endpointAddress, eventType); + } + + public Task Unsubscribe(string endpointName, Type eventType) + { + return inner.Unsubscribe(endpointName, eventType); + } + + MessageMetadataRegistry messageMetadataRegistry; + IManageTransportSubscriptions inner; + + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs index 0b93ad83b..15ce75cb5 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs @@ -13,7 +13,7 @@ public SubscriptionCache(IManageTransportSubscriptions inner, TimeSpan cacheFor) this.cacheFor = cacheFor; } - public Task> GetSubscribersForEvent(string eventType) + public Task> GetSubscribersForEvent(Type eventType) { var cacheItem = Cache.GetOrAdd(eventType, _ => new CacheItem @@ -32,26 +32,26 @@ public Task> GetSubscribersForEvent(string eventType) return cacheItem.Subscribers; } - public async Task Subscribe(string endpointName, string endpointAddress, string eventType) + public async Task Subscribe(string endpointName, string endpointAddress, Type eventType) { await inner.Subscribe(endpointName, endpointAddress, eventType).ConfigureAwait(false); ClearForMessageType(eventType); } - public async Task Unsubscribe(string endpointName, string eventType) + public async Task Unsubscribe(string endpointName, Type eventType) { await inner.Unsubscribe(endpointName, eventType).ConfigureAwait(false); ClearForMessageType(eventType); } - void ClearForMessageType(string eventType) + void ClearForMessageType(Type eventType) { Cache.TryRemove(eventType, out _); } TimeSpan cacheFor; IManageTransportSubscriptions inner; - ConcurrentDictionary Cache = new ConcurrentDictionary(); + ConcurrentDictionary Cache = new ConcurrentDictionary(); class CacheItem { diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index 824aace0b..84d2b2635 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -20,19 +20,12 @@ public SubscriptionManager(IManageTransportSubscriptions subscriptions, ReadOnly public Task Subscribe(Type eventType, ContextBag context) { - return subscriptions.Subscribe( - endpointName, - localAddress, - eventType.ToString() - ); + return subscriptions.Subscribe(endpointName, localAddress, eventType); } public Task Unsubscribe(Type eventType, ContextBag context) { - return subscriptions.Unsubscribe( - endpointName, - eventType.ToString() - ); + return subscriptions.Unsubscribe(endpointName, eventType); } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs index 97a5c841f..c1f9063fc 100644 --- a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs @@ -1,5 +1,6 @@ namespace NServiceBus.Transport.SQLServer { + using System; using System.Collections.Generic; using System.Data; using System.Threading.Tasks; @@ -11,7 +12,6 @@ class TableBasedSubscriptions : IManageTransportSubscriptions string subscribeCommand; string unsubscribeCommand; string getSubscribersCommand; - string createSubscriptionsTableCommand; public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) { @@ -20,12 +20,11 @@ public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) #pragma warning disable 618 subscribeCommand = SqlConstants.SubscribeText; unsubscribeCommand = SqlConstants.UnsubscribeText; - createSubscriptionsTableCommand = SqlConstants.CreateSubscriptionTableText; getSubscribersCommand = SqlConstants.GetSubscribersText; #pragma warning restore 618 } - public async Task Subscribe(string endpointName, string endpointAddress, string eventType) + public async Task Subscribe(string endpointName, string endpointAddress, Type eventType) { using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { @@ -34,7 +33,7 @@ public async Task Subscribe(string endpointName, string endpointAddress, string { command.CommandText = subscribeCommand; command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; await command.ExecuteNonQueryAsync().ConfigureAwait(false); @@ -42,7 +41,7 @@ public async Task Subscribe(string endpointName, string endpointAddress, string } } - public async Task Unsubscribe(string endpointName, string eventType) + public async Task Unsubscribe(string endpointName, Type eventType) { using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { @@ -51,14 +50,14 @@ public async Task Unsubscribe(string endpointName, string eventType) { command.CommandText = unsubscribeCommand; command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); await command.ExecuteNonQueryAsync().ConfigureAwait(false); } } } - public async Task> GetSubscribersForEvent(string eventType) + public async Task> GetSubscribersForEvent(Type eventType) { var results = new List(); using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) @@ -67,7 +66,7 @@ public async Task> GetSubscribersForEvent(string eventType) using (var command = connection.CreateCommand()) { command.CommandText = getSubscribersCommand; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType; + command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) { diff --git a/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs b/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs new file mode 100644 index 000000000..0d7aea3ec --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs @@ -0,0 +1,34 @@ +namespace NServiceBus.Transport.SQLServer +{ + using Settings; + using Unicast.Messages; + + class TransportPubSub + { + IManageTransportSubscriptions subscriptions; + + public IManageTransportSubscriptions GetTransportSubscriptionsManager(ReadOnlySettings settings, SqlConnectionFactory connectionFactory) + { + if (subscriptions != null) + { + return subscriptions; + } + + var pubSubSettings = settings.GetOrDefault() ?? new TransportPubSubOptions(); + var messageMetadataRegistry = settings.Get(); + + subscriptions = new PolymorphicTransportSubscriptionsManagerWrapper( + new TableBasedSubscriptions(connectionFactory), + messageMetadataRegistry + ); + + if (pubSubSettings.TimeToCacheSubscription.HasValue) + { + subscriptions = new SubscriptionCache(subscriptions, pubSubSettings.TimeToCacheSubscription.Value); + } + + return subscriptions; + + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index f3dce8f4b..c9f7db79f 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -48,7 +48,7 @@ async Task> ConvertToUnicastOperations(Transport async Task> ConvertToUnicastOperations(MulticastTransportOperation transportOperation) { - var destinations = await subscriptions.GetSubscribersForEvent(transportOperation.MessageType.ToString()).ConfigureAwait(false); + var destinations = await subscriptions.GetSubscribersForEvent(transportOperation.MessageType).ConfigureAwait(false); return (from destination in destinations select new UnicastTransportOperation( diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 69a268ea4..5ec3f3860 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -35,15 +35,6 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat } settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); - - var pubSubSettings = settings.GetOrCreate(); - - subscriptions = new TableBasedSubscriptions(CreateConnectionFactory()); - - if (pubSubSettings.TimeToCacheSubscription.HasValue) - { - subscriptions = new SubscriptionCache(subscriptions, pubSubSettings.TimeToCacheSubscription.Value); - } } public override IEnumerable DeliveryConstraints @@ -197,6 +188,8 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() settings.GetOrCreate().AddOrReplaceInstances("SqlServer", schemaAndCatalogSettings.ToEndpointInstances()); + var subscriptions = pubSub.GetTransportSubscriptionsManager(settings, connectionFactory); + return new TransportSendInfrastructure( () => { @@ -271,6 +264,7 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { + var subscriptions = pubSub.GetTransportSubscriptionsManager(settings, CreateConnectionFactory()); return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptions, settings)); } @@ -307,6 +301,6 @@ public override string MakeCanonicalForm(string transportAddress) DueDelayedMessageProcessor dueDelayedMessageProcessor; DelayedDeliverySettings delayedDeliverySettings; Dictionary diagnostics = new Dictionary(); - IManageTransportSubscriptions subscriptions; + TransportPubSub pubSub = new TransportPubSub(); } } \ No newline at end of file From eaa9dadd1cce661e3da689647056eefdca8eb001 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Wed, 30 Oct 2019 15:00:53 +0800 Subject: [PATCH 07/31] Allow construction of transport without message metadata For NSB Raw and Transport Tests --- .../PubSub/TransportPubSub.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs b/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs index 0d7aea3ec..519e8d4e3 100644 --- a/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs +++ b/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs @@ -14,13 +14,17 @@ public IManageTransportSubscriptions GetTransportSubscriptionsManager(ReadOnlySe return subscriptions; } - var pubSubSettings = settings.GetOrDefault() ?? new TransportPubSubOptions(); - var messageMetadataRegistry = settings.Get(); + subscriptions = new TableBasedSubscriptions(connectionFactory); + + if (settings.TryGet(out var messageMetadataRegistry)) + { + subscriptions = new PolymorphicTransportSubscriptionsManagerWrapper( + subscriptions, + messageMetadataRegistry + ); + } - subscriptions = new PolymorphicTransportSubscriptionsManagerWrapper( - new TableBasedSubscriptions(connectionFactory), - messageMetadataRegistry - ); + var pubSubSettings = settings.GetOrDefault() ?? new TransportPubSubOptions(); if (pubSubSettings.TimeToCacheSubscription.HasValue) { @@ -28,7 +32,6 @@ public IManageTransportSubscriptions GetTransportSubscriptionsManager(ReadOnlySe } return subscriptions; - } } } \ No newline at end of file From ac988c79e841e7c23815d6551a0c006f3cecabb5 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 30 Oct 2019 09:40:17 +0100 Subject: [PATCH 08/31] Configurable pub/sub --- .../When_checking_schema.cs | 4 +- .../Configuration/TransportPubSubOptions.cs | 9 --- .../PubSub/PubSubSettings.cs | 63 +++++++++++++++++++ .../PubSub/QualifiedSubscriptionTableName.cs | 20 ++++++ .../PubSub/SubscriptionTableName.cs | 23 +++++++ .../PubSub/TableBasedSubscriptions.cs | 8 +-- .../Queuing/SqlConstants.cs | 51 +++++++++------ .../Receiving/QueueCreator.cs | 28 ++++++--- .../SqlServerTransportInfrastructure.cs | 19 ++++-- .../SqlServerTransportSettingsExtensions.cs | 20 +++--- 10 files changed, 186 insertions(+), 59 deletions(-) delete mode 100644 src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/SubscriptionTableName.cs diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs index 93db9a991..dbcd69515 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs @@ -44,7 +44,9 @@ public async Task It_returns_type_for_headers_column() static async Task ResetQueue(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, + new CanonicalQueueAddress("Delayed", "dbo", "nservicebus"), + new QualifiedSubscriptionTableName()); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(QueueTableName); diff --git a/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs b/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs deleted file mode 100644 index cf7f0758c..000000000 --- a/src/NServiceBus.SqlServer/Configuration/TransportPubSubOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - - class TransportPubSubOptions - { - public TimeSpan? TimeToCacheSubscription { get; set; } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs b/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs new file mode 100644 index 000000000..e5c092110 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs @@ -0,0 +1,63 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using Settings; + + /// + /// Configures the native pub/sub behavior + /// + public class PubSubSettings + { + internal SubscriptionTableName SubscriptionTable = new SubscriptionTableName("SubscriptionRouting", null, null); + TimeSpan? TimeToCacheSubscriptions; + + /// + /// Overrides the default name for the subscription table. All endpoints in a given system need to agree on that name in order for them to be able + /// to subscribe to and publish events. + /// + /// Name of the table. + /// Schema in which the table is defined if different from default schema configured for the transport. + /// Catalog in which the table is defined if different from default catalog configured for the transport. + public void SubscriptionTableName(string tableName, string schemaName = null, string catalogName = null) + { + SubscriptionTable = new SubscriptionTableName(tableName, schemaName, catalogName); + } + + /// + /// Cache subscriptions for a given . + /// + public void CacheSubscriptionInformationFor(TimeSpan timeSpan) + { + Guard.AgainstNegativeAndZero(nameof(timeSpan), timeSpan); + TimeToCacheSubscriptions = timeSpan; + } + + /// + /// Do not cache subscriptions. + /// + public void DisableSubscriptionCache() + { + TimeToCacheSubscriptions = TimeSpan.Zero; + } + + internal TimeSpan? GetCacheFor() + { + if (TimeToCacheSubscriptions == TimeSpan.Zero) + { + return null; + } + + if (TimeToCacheSubscriptions.HasValue) + { + return TimeToCacheSubscriptions.Value; + } + throw new Exception(@"Subscription caching is a required settings. Access this setting using the following: +var transport = endpointConfiguration.UseTransport(); +var pubsub = transport.PuSub(); +subscriptions.CacheSubscriptionInformationFor(TimeSpan.FromMinutes(1)); +// OR +subscriptions.DisableSubscriptionCache(); +"); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs b/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs new file mode 100644 index 000000000..9d25a6803 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs @@ -0,0 +1,20 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using static NameHelper; + + class QualifiedSubscriptionTableName + { + public string QuotedCatalog; + public string QuotedQualifiedName; + + public QualifiedSubscriptionTableName(string table, string schema, string catalog) + { + if (table == null) throw new ArgumentNullException(nameof(table)); + if (schema == null) throw new ArgumentNullException(nameof(schema)); + if (catalog == null) throw new ArgumentNullException(nameof(catalog)); + QuotedCatalog = Quote(catalog); + QuotedQualifiedName = $"{Quote(table)}.{Quote(schema)}.{Quote(catalog)}"; + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionTableName.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionTableName.cs new file mode 100644 index 000000000..14b1a6f21 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionTableName.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + + class SubscriptionTableName + { + string table; + string schema; + string catalog; + + public SubscriptionTableName(string table, string schema, string catalog) + { + this.table = table ?? throw new ArgumentNullException(nameof(table)); + this.schema = schema; + this.catalog = catalog; + } + + public QualifiedSubscriptionTableName Qualify(string defaultSchema, string defaultCatalog) + { + return new QualifiedSubscriptionTableName(table, schema ?? defaultSchema, catalog ?? defaultCatalog); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs index c1f9063fc..70f20571e 100644 --- a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs @@ -13,14 +13,14 @@ class TableBasedSubscriptions : IManageTransportSubscriptions string unsubscribeCommand; string getSubscribersCommand; - public TableBasedSubscriptions(SqlConnectionFactory connectionFactory) + public TableBasedSubscriptions(string qualifiedTableName, SqlConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; // TODO: Be able to change the subscriptions table name and schema #pragma warning disable 618 - subscribeCommand = SqlConstants.SubscribeText; - unsubscribeCommand = SqlConstants.UnsubscribeText; - getSubscribersCommand = SqlConstants.GetSubscribersText; + subscribeCommand = string.Format(SqlConstants.SubscribeText, qualifiedTableName); + unsubscribeCommand = string.Format(SqlConstants.UnsubscribeText, qualifiedTableName); + getSubscribersCommand = string.Format(SqlConstants.GetSubscribersText, qualifiedTableName); #pragma warning restore 618 } diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index 0a71bba43..0b841a4fa 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -256,29 +256,40 @@ FROM sys.columns c AND c.name = 'Headers'"; public static readonly string CreateSubscriptionTableText = @" -IF NOT EXISTS -( + +IF EXISTS ( SELECT * - FROM sys.objects - WHERE - object_id = object_id('[dbo].[SubscriptionRouting]') AND - type IN ('U') -) + FROM {1}.sys.objects + WHERE object_id = OBJECT_ID(N'{0}') + AND type in (N'U')) +RETURN + +EXEC sp_getapplock @Resource = '{0}_lock', @LockMode = 'Exclusive' + +IF EXISTS ( + SELECT * + FROM {1}.sys.objects + WHERE object_id = OBJECT_ID(N'{0}') + AND type in (N'U')) BEGIN - CREATE TABLE [dbo].[SubscriptionRouting] ( - Subscriber NVARCHAR(200) NOT NULL, - Endpoint NVARCHAR(200), - MessageType NVARCHAR(200) NOT NULL, - PRIMARY KEY CLUSTERED - ( - Subscriber, - MessageType - ) + EXEC sp_releaseapplock @Resource = '{0}_lock' + RETURN +END + +CREATE TABLE {0} ( + Subscriber NVARCHAR(200) NOT NULL, + Endpoint NVARCHAR(200), + MessageType NVARCHAR(200) NOT NULL, + PRIMARY KEY CLUSTERED + ( + Subscriber, + MessageType ) -END"; +) +EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static readonly string SubscribeText = @" -MERGE [dbo].[SubscriptionRouting] WITH (HOLDLOCK, TABLOCK) AS target +MERGE {0} WITH (HOLDLOCK, TABLOCK) AS target USING(SELECT @Endpoint AS Endpoint, @Subscriber AS Subscriber, @MessageType AS MessageType) AS source ON target.Subscriber = source.Subscriber AND target.MessageType = source.MessageType @@ -300,12 +311,12 @@ WHEN NOT MATCHED THEN public static readonly string GetSubscribersText = @" SELECT DISTINCT Subscriber, Endpoint -FROM [dbo].[SubscriptionRouting] +FROM {0} WHERE MessageType IN (@MessageType) "; public static readonly string UnsubscribeText = @" -DELETE FROM [dbo].[SubscriptionRouting] +DELETE FROM {0} WHERE Subscriber = @Subscriber and MessageType = @MessageType"; diff --git a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs index 0be99dfbb..17ff1a667 100644 --- a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs +++ b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs @@ -8,11 +8,14 @@ namespace NServiceBus.Transport.SQLServer class QueueCreator : ICreateQueues { - public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, CanonicalQueueAddress delayedMessageTable, bool createMessageBodyColumn = false) + public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, + LogicalAddress delayedQueueAddress, QualifiedSubscriptionTableName subscriptionTable, + bool createMessageBodyColumn = false) { this.connectionFactory = connectionFactory; this.addressTranslator = addressTranslator; - this.delayedMessageTable = delayedMessageTable; + this.delayedQueueAddress = delayedQueueAddress; + this.subscriptionTable = subscriptionTable; this.createMessageBodyColumn = createMessageBodyColumn; } @@ -36,7 +39,7 @@ public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string ide using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) using (var transaction = connection.BeginTransaction()) { - await CreateDelayedMessageQueue(delayedMessageTable, connection, transaction, createMessageBodyColumn).ConfigureAwait(false); + await CreateDelayedMessageQueue(connection, transaction, createMessageBodyColumn).ConfigureAwait(false); await CreateSubscriptionsTable(connection, transaction).ConfigureAwait(false); transaction.Commit(); } @@ -66,10 +69,11 @@ static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlCo } } - static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) + async Task CreateDelayedMessageQueue(SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) { + var delayedQueue = GetDelayedQueueTableName(); #pragma warning disable 618 - var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); + var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, delayedQueue.QualifiedTableName, delayedQueue.QuotedCatalogName); #pragma warning restore 618 using (var command = new SqlCommand(sql, connection, transaction) { @@ -81,7 +85,7 @@ static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueue if (createMessageBodyComputedColumn) { #pragma warning disable 618 - var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); + var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, delayedQueue.QualifiedTableName, delayedQueue.QuotedCatalogName); #pragma warning restore 618 using (var command = new SqlCommand(bodyStringSql, connection, transaction) { @@ -94,10 +98,10 @@ static async Task CreateDelayedMessageQueue(CanonicalQueueAddress canonicalQueue } } - static async Task CreateSubscriptionsTable(SqlConnection connection, SqlTransaction transaction) + async Task CreateSubscriptionsTable(SqlConnection connection, SqlTransaction transaction) { #pragma warning disable 618 - var sql = SqlConstants.CreateSubscriptionTableText; + var sql = string.Format(SqlConstants.CreateSubscriptionTableText, subscriptionTable.QuotedQualifiedName, subscriptionTable.QuotedCatalog); #pragma warning restore 618 using (var command = new SqlCommand(sql, connection, transaction) { @@ -108,9 +112,15 @@ static async Task CreateSubscriptionsTable(SqlConnection connection, SqlTransact } } + CanonicalQueueAddress GetDelayedQueueTableName() + { + return addressTranslator.GetCanonicalForm(addressTranslator.Generate(delayedQueueAddress)); + } + SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; - CanonicalQueueAddress delayedMessageTable; + LogicalAddress delayedQueueAddress; + QualifiedSubscriptionTableName subscriptionTable; bool createMessageBodyColumn; } } diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 5ec3f3860..30b3dd932 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -3,6 +3,7 @@ namespace NServiceBus.Transport.SQLServer using System; using System.Collections.Generic; using System.Data.SqlClient; + using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Transactions; using DelayedDelivery; @@ -22,6 +23,8 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat schemaAndCatalogSettings = settings.GetOrCreate(); delayedDeliverySettings = settings.GetOrCreate(); + pubSubSettings = settings.GetOrCreate(); + var timeoutManagerFeatureDisabled = !settings.IsFeatureEnabled(typeof(TimeoutManager)); diagnostics.Add("NServiceBus.Transport.SqlServer.TimeoutManager", new @@ -35,6 +38,15 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat } settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); + + var subscriptionTableName = pubSubSettings.SubscriptionTable.Qualify(addressTranslator.DefaultSchema, addressTranslator.DefaultCatalog); + subscriptions = new TableBasedSubscriptions(subscriptionTableName.QuotedQualifiedName, CreateConnectionFactory()); + + var timeToCacheSubscriptions = pubSubSettings.GetCacheFor(); + if (timeToCacheSubscriptions.HasValue) + { + subscriptions = new SubscriptionCache(subscriptions, timeToCacheSubscriptions.Value); + } } public override IEnumerable DeliveryConstraints @@ -96,10 +108,11 @@ public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() Func queueFactory = queueName => new TableBasedQueue(addressTranslator.Parse(queueName).QualifiedTableName, queueName); var delayedMessageStore = GetDelayedQueueTableName(); + var qualifiedSubscriptionTable = pubSubSettings.SubscriptionTable.Qualify(addressTranslator.DefaultSchema, addressTranslator.DefaultCatalog); return new TransportReceiveInfrastructure( () => new MessagePump(receiveStrategyFactory, queueFactory, queuePurger, expiredMessagesPurger, queuePeeker, schemaVerification, waitTimeCircuitBreaker), - () => new QueueCreator(connectionFactory, addressTranslator, delayedMessageStore, createMessageBodyComputedColumn), + () => new QueueCreator(connectionFactory, addressTranslator, delayedMessageStore, qualifiedSubscriptionTable, createMessageBodyComputedColumn), () => CheckForAmbientTransactionEnlistmentSupport(connectionFactory, scopeOptions.TransactionOptions)); } @@ -225,10 +238,6 @@ LogicalAddress GetLogicalAddress() CanonicalQueueAddress GetDelayedQueueTableName() { - if (string.IsNullOrEmpty(delayedDeliverySettings.Suffix)) - { - throw new Exception("Native delayed delivery feature requires configuring a table suffix."); - } var delayedQueueLogicalAddress = GetLogicalAddress().CreateQualifiedAddress(delayedDeliverySettings.Suffix); var delayedQueueAddress = addressTranslator.Generate(delayedQueueLogicalAddress); return addressTranslator.GetCanonicalForm(delayedQueueAddress); diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index 13b1858f3..93d1f7158 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -178,7 +178,7 @@ public static TransportExtensions WithPeekDelay(this Transpo } /// - /// Enables native delayed delivery. + /// Configures native delayed delivery. /// public static DelayedDeliverySettings NativeDelayedDelivery(this TransportExtensions transportExtensions) { @@ -192,6 +192,14 @@ public static DelayedDeliverySettings NativeDelayedDelivery(this TransportExtens return settings; } + /// + /// Configures publish/subscribe. + /// + public static PubSubSettings PubSub(this TransportExtensions transportExtensions) + { + return transportExtensions.GetSettings().GetOrCreate(); + } + /// /// Instructs the transport to purge all expired messages from the input queue before starting the processing. /// @@ -218,16 +226,6 @@ public static TransportExtensions CreateMessageBodyComputedC return transportExtensions; } - /// - /// Instructs the transport to cache subscriptions - /// - /// The to extend. - /// The length of time to cache subscriptions. - public static void CacheSubscriptionsFor(this TransportExtensions transportExtensions, TimeSpan timeToCacheSubscriptions) - { - transportExtensions.GetSettings().GetOrCreate().TimeToCacheSubscription = timeToCacheSubscriptions; - } - /// /// Enables multi-instance mode. /// From daf9bf3ce112f8c72d490ec01b7a5b54ea77f75d Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Thu, 31 Oct 2019 06:29:39 +0100 Subject: [PATCH 09/31] Streamline configuration of infra --- .../ConfigureEndpointSqlServerTransport.cs | 2 + .../When_checking_schema.cs | 3 +- .../When_dispatching_messages.cs | 13 +- .../When_using_ttbr.cs | 11 +- ...nfigureSqlServerTransportInfrastructure.cs | 11 +- ...ServiceBus.SqlServer.TransportTests.csproj | 2 + .../SqlServerTransportTests.cs | 16 +- .../DelayedDelivery/DelayedMessageTable.cs | 15 +- .../InternalsVisibleTo.cs | 3 +- .../PubSub/IManageTransportSubscriptions.cs | 13 -- .../PubSub/ISubscriptionStore.cs | 12 ++ ...hicTransportSubscriptionsManagerWrapper.cs | 44 ----- .../PubSub/PubSubSettings.cs | 1 - .../PubSub/QualifiedSubscriptionTableName.cs | 2 +- .../PubSub/SubscriptionCache.cs | 32 +-- .../PubSub/SubscriptionManager.cs | 36 ++-- ...dSubscriptions.cs => SubscriptionTable.cs} | 21 +- .../PubSub/TransportPubSub.cs | 37 ---- .../Queuing/SqlConstants.cs | 34 ++-- .../Queuing/TableBasedQueueCache.cs | 25 +++ .../TableBasedQueueOperationsReader.cs | 15 +- .../Receiving/ProcessWithNativeTransaction.cs | 4 +- .../Receiving/ProcessWithNoTransaction.cs | 4 +- .../Receiving/ProcessWithTransactionScope.cs | 4 +- .../Receiving/QueueCreator.cs | 34 +--- .../Receiving/ReceiveStrategy.cs | 32 +-- .../Sending/MessageDispatcher.cs | 12 +- .../SqlServerTransport.cs | 7 +- .../SqlServerTransportInfrastructure.cs | 183 +++++++++--------- .../SubscriptionTableCreator.cs | 36 ++++ .../SubscriptionTableInstaller.cs | 26 +++ 31 files changed, 358 insertions(+), 332 deletions(-) delete mode 100644 src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs delete mode 100644 src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs rename src/NServiceBus.SqlServer/PubSub/{TableBasedSubscriptions.cs => SubscriptionTable.cs} (76%) delete mode 100644 src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs create mode 100644 src/NServiceBus.SqlServer/Queuing/TableBasedQueueCache.cs create mode 100644 src/NServiceBus.SqlServer/SubscriptionTableCreator.cs create mode 100644 src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index 595685281..bbb6762fe 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -6,6 +6,7 @@ using NServiceBus.AcceptanceTesting.Support; using NServiceBus.Configuration.AdvancedExtensibility; using NServiceBus.Transport; +using NServiceBus.Transport.SQLServer; public class ConfigureEndpointSqlServerTransport : IConfigureEndpointTestExecution { @@ -21,6 +22,7 @@ public Task Configure(string endpointName, EndpointConfiguration configuration, var transportConfig = configuration.UseTransport(); transportConfig.ConnectionString(connectionString); + transportConfig.PubSub().DisableSubscriptionCache(); #if !NET452 transportConfig.Transactions(TransportTransactionMode.SendsAtomicWithReceive); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs index dbcd69515..bc3cd1ebb 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_checking_schema.cs @@ -45,8 +45,7 @@ public async Task It_returns_type_for_headers_column() static async Task ResetQueue(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, - new CanonicalQueueAddress("Delayed", "dbo", "nservicebus"), - new QualifiedSubscriptionTableName()); + new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(QueueTableName); diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index 1f35de957..ca8550260 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -102,12 +102,13 @@ public void Prepare() async Task PrepareAsync() { var addressParser = new QueueAddressTranslator("nservicebus", "dbo", null, null); + var tableCache = new TableBasedQueueCache(addressParser); await CreateOutputQueueIfNecessary(addressParser, sqlConnectionFactory); await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser, null)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) @@ -121,7 +122,7 @@ Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslator, SqlConnectionFactory sqlConnectionFactory) { - var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); + var queueCreator = new QueueCreator(sqlConnectionFactory, addressTranslator, new CanonicalQueueAddress("Delayed", "dbo", "nservicebus")); var queueBindings = new QueueBindings(); queueBindings.BindReceiving(validAddress); @@ -137,19 +138,19 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato SqlConnectionFactory sqlConnectionFactory; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : IManageTransportSubscriptions + class NoOpSubscriptionStore : ISubscriptionStore { - public Task> GetSubscribersForEvent(Type eventType) + public Task> GetSubscribersForTopic(string topic) { return Task.FromResult(new List()); } - public Task Subscribe(string endpointName, string endpointAddress, Type eventType) + public Task Subscribe(string endpointName, string endpointAddress, string topic) { return Task.FromResult(0); } - public Task Unsubscribe(string endpointName, Type eventType) + public Task Unsubscribe(string endpointName, string topic) { return Task.FromResult(0); } diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index dcb839e34..dd48ab840 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -121,6 +121,7 @@ public void Prepare() async Task PrepareAsync() { var addressParser = new QueueAddressTranslator("nservicebus", "dbo", null, new QueueSchemaAndCatalogSettings()); + var tableCache = new TableBasedQueueCache(addressParser); var connectionString = Environment.GetEnvironmentVariable("SqlServerTransportConnectionString"); if (string.IsNullOrEmpty(connectionString)) @@ -134,7 +135,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(addressParser, null)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpSubscriptionStore()); } Task PurgeOutputQueue(QueueAddressTranslator addressParser) @@ -163,19 +164,19 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S const string validAddress = "TTBRTests"; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : IManageTransportSubscriptions + class NoOpSubscriptionStore : ISubscriptionStore { - public Task> GetSubscribersForEvent(Type eventType) + public Task> GetSubscribersForTopic(string topic) { return Task.FromResult(new List()); } - public Task Subscribe(string endpointName, string endpointAddress, Type eventType) + public Task Subscribe(string endpointName, string endpointAddress, string topic) { return Task.FromResult(0); } - public Task Unsubscribe(string endpointName, Type eventType) + public Task Unsubscribe(string endpointName, string topic) { return Task.FromResult(0); } diff --git a/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs index ae800b41c..ce00400b6 100644 --- a/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs @@ -22,18 +22,25 @@ public TransportConfigurationResult Configure(SettingsHolder settings, Transport this.settings = settings; settings.Set(transportTransactionMode); settings.Set("NServiceBus.SharedQueue", settings.EndpointName()); - settings.Set(LogicalAddress.CreateLocalAddress(settings.EndpointName(), new Dictionary())); var delayedDeliverySettings = new DelayedDeliverySettings(); delayedDeliverySettings.TableSuffix("Delayed"); settings.Set(delayedDeliverySettings); + + var pubSubSettings = new PubSubSettings(); + pubSubSettings.DisableSubscriptionCache(); + settings.Set(pubSubSettings); + connectionString = Environment.GetEnvironmentVariable("SqlServerTransportConnectionString"); if (string.IsNullOrEmpty(connectionString)) { connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=nservicebus;Integrated Security=True"; } + + var logicalAddress = LogicalAddress.CreateLocalAddress(settings.ErrorQueueAddress(), new Dictionary()); + var localAddress = settings.EndpointName(); return new TransportConfigurationResult { - TransportInfrastructure = new SqlServerTransport().Initialize(settings, connectionString) + TransportInfrastructure = new SqlServerTransportInfrastructure("nservicebus", settings, connectionString, () => localAddress, () => logicalAddress) }; } diff --git a/src/NServiceBus.SqlServer.TransportTests/NServiceBus.SqlServer.TransportTests.csproj b/src/NServiceBus.SqlServer.TransportTests/NServiceBus.SqlServer.TransportTests.csproj index 99b448497..0d7083d02 100644 --- a/src/NServiceBus.SqlServer.TransportTests/NServiceBus.SqlServer.TransportTests.csproj +++ b/src/NServiceBus.SqlServer.TransportTests/NServiceBus.SqlServer.TransportTests.csproj @@ -2,6 +2,8 @@ net46;netcoreapp2.1 + true + ..\NServiceBus.snk true diff --git a/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs b/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs index a25f1bbbc..9af5e5b00 100644 --- a/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs +++ b/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs @@ -2,6 +2,7 @@ { using NUnit.Framework; using Settings; + using Transport.SQLServer; [TestFixture] public class SqlServerTransportTests @@ -10,7 +11,12 @@ public class SqlServerTransportTests public void It_rejects_connection_string_without_catalog_property() { var definition = new SqlServerTransport(); - Assert.That( () => definition.Initialize(new SettingsHolder(), @"Data Source=.\SQLEXPRESS;Integrated Security=True"), + var subscriptionSettings = new PubSubSettings(); + subscriptionSettings.DisableSubscriptionCache(); + var settings = new SettingsHolder(); + settings.Set(subscriptionSettings); + + Assert.That( () => definition.Initialize(settings, @"Data Source=.\SQLEXPRESS;Integrated Security=True"), Throws.Exception.Message.Contains("Initial Catalog property is mandatory in the connection string.")); } @@ -22,7 +28,13 @@ public void It_rejects_connection_string_without_catalog_property() public void It_accepts_connection_string_with_catalog_property(string connectionString) { var definition = new SqlServerTransport(); - definition.Initialize(new SettingsHolder(), connectionString); + + var subscriptionSettings = new PubSubSettings(); + subscriptionSettings.DisableSubscriptionCache(); + var settings = new SettingsHolder(); + settings.Set(subscriptionSettings); + + definition.Initialize(settings, connectionString); Assert.Pass(); } } diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs index 1ad41dcfe..960efc838 100644 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs +++ b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs @@ -6,7 +6,20 @@ namespace NServiceBus.Transport.SQLServer using System.Threading.Tasks; using Transport; - class DelayedMessageTable + interface IDelayedMessageStore + { + Task Store(OutgoingMessage message, TimeSpan dueAfter, string destination, SqlConnection connection, SqlTransaction transaction); + } + + class SendOnlyDelayedMessageStore : IDelayedMessageStore + { + public Task Store(OutgoingMessage message, TimeSpan dueAfter, string destination, SqlConnection connection, SqlTransaction transaction) + { + throw new Exception("Delayed delivery is only supported for non send-only endpoints."); + } + } + + class DelayedMessageTable : IDelayedMessageStore { public DelayedMessageTable(string delayedQueueTable, string inputQueueTable) { diff --git a/src/NServiceBus.SqlServer/InternalsVisibleTo.cs b/src/NServiceBus.SqlServer/InternalsVisibleTo.cs index abf73b519..ff2f11f6c 100644 --- a/src/NServiceBus.SqlServer/InternalsVisibleTo.cs +++ b/src/NServiceBus.SqlServer/InternalsVisibleTo.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("NServiceBus.SqlServer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] -[assembly: InternalsVisibleTo("NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] \ No newline at end of file +[assembly: InternalsVisibleTo("NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: InternalsVisibleTo("NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs deleted file mode 100644 index 2fe7e3651..000000000 --- a/src/NServiceBus.SqlServer/PubSub/IManageTransportSubscriptions.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - - interface IManageTransportSubscriptions - { - Task> GetSubscribersForEvent(Type eventType); - Task Subscribe(string endpointName, string endpointAddress, Type eventType); - Task Unsubscribe(string endpointName, Type eventType); - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs b/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs new file mode 100644 index 000000000..aaca47310 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs @@ -0,0 +1,12 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + interface ISubscriptionStore + { + Task> GetSubscribersForTopic(string topic); + Task Subscribe(string endpointName, string endpointAddress, string topic); + Task Unsubscribe(string endpointName, string topic); + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs b/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs deleted file mode 100644 index 1a4468166..000000000 --- a/src/NServiceBus.SqlServer/PubSub/PolymorphicTransportSubscriptionsManagerWrapper.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Unicast.Messages; - - class PolymorphicTransportSubscriptionsManagerWrapper : IManageTransportSubscriptions - { - public PolymorphicTransportSubscriptionsManagerWrapper(IManageTransportSubscriptions inner, MessageMetadataRegistry messageMetadataRegistry) - { - this.inner = inner; - this.messageMetadataRegistry = messageMetadataRegistry; - } - - public async Task> GetSubscribersForEvent(Type eventType) - { - var messageMetadata = messageMetadataRegistry.GetMessageMetadata(eventType); - - var tasks = messageMetadata.MessageHierarchy.Select(t => inner.GetSubscribersForEvent(t)).ToArray(); - - await Task.WhenAll(tasks).ConfigureAwait(false); - - var results = tasks.SelectMany(t => t.Result).Distinct().ToList(); - - return results; - } - - public Task Subscribe(string endpointName, string endpointAddress, Type eventType) - { - return inner.Subscribe(endpointName, endpointAddress, eventType); - } - - public Task Unsubscribe(string endpointName, Type eventType) - { - return inner.Unsubscribe(endpointName, eventType); - } - - MessageMetadataRegistry messageMetadataRegistry; - IManageTransportSubscriptions inner; - - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs b/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs index e5c092110..7187d5913 100644 --- a/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs +++ b/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs @@ -1,7 +1,6 @@ namespace NServiceBus.Transport.SQLServer { using System; - using Settings; /// /// Configures the native pub/sub behavior diff --git a/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs b/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs index 9d25a6803..bc9637c11 100644 --- a/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs +++ b/src/NServiceBus.SqlServer/PubSub/QualifiedSubscriptionTableName.cs @@ -14,7 +14,7 @@ public QualifiedSubscriptionTableName(string table, string schema, string catalo if (schema == null) throw new ArgumentNullException(nameof(schema)); if (catalog == null) throw new ArgumentNullException(nameof(catalog)); QuotedCatalog = Quote(catalog); - QuotedQualifiedName = $"{Quote(table)}.{Quote(schema)}.{Quote(catalog)}"; + QuotedQualifiedName = $"{Quote(catalog)}.{Quote(schema)}.{Quote(table)}"; } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs index 15ce75cb5..b1a36c2db 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs @@ -5,53 +5,53 @@ namespace NServiceBus.Transport.SQLServer using System.Collections.Generic; using System.Threading.Tasks; - class SubscriptionCache : IManageTransportSubscriptions + class SubscriptionCache : ISubscriptionStore { - public SubscriptionCache(IManageTransportSubscriptions inner, TimeSpan cacheFor) + public SubscriptionCache(ISubscriptionStore inner, TimeSpan cacheFor) { this.inner = inner; this.cacheFor = cacheFor; } - public Task> GetSubscribersForEvent(Type eventType) + public Task> GetSubscribersForTopic(string topic) { - var cacheItem = Cache.GetOrAdd(eventType, + var cacheItem = Cache.GetOrAdd(topic, _ => new CacheItem { Stored = DateTime.UtcNow, - Subscribers = inner.GetSubscribersForEvent(eventType) + Subscribers = inner.GetSubscribersForTopic(topic) }); var age = DateTime.UtcNow - cacheItem.Stored; if (age >= cacheFor) { - cacheItem.Subscribers = inner.GetSubscribersForEvent(eventType); + cacheItem.Subscribers = inner.GetSubscribersForTopic(topic); cacheItem.Stored = DateTime.UtcNow; } return cacheItem.Subscribers; } - public async Task Subscribe(string endpointName, string endpointAddress, Type eventType) + public async Task Subscribe(string endpointName, string endpointAddress, string topic) { - await inner.Subscribe(endpointName, endpointAddress, eventType).ConfigureAwait(false); - ClearForMessageType(eventType); + await inner.Subscribe(endpointName, endpointAddress, topic).ConfigureAwait(false); + ClearForMessageType(topic); } - public async Task Unsubscribe(string endpointName, Type eventType) + public async Task Unsubscribe(string endpointName, string topic) { - await inner.Unsubscribe(endpointName, eventType).ConfigureAwait(false); - ClearForMessageType(eventType); + await inner.Unsubscribe(endpointName, topic).ConfigureAwait(false); + ClearForMessageType(topic); } - void ClearForMessageType(Type eventType) + void ClearForMessageType(string topice) { - Cache.TryRemove(eventType, out _); + Cache.TryRemove(topice, out _); } TimeSpan cacheFor; - IManageTransportSubscriptions inner; - ConcurrentDictionary Cache = new ConcurrentDictionary(); + ISubscriptionStore inner; + ConcurrentDictionary Cache = new ConcurrentDictionary(); class CacheItem { diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index 84d2b2635..a3f9e2ccf 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -1,31 +1,45 @@ namespace NServiceBus.Transport.SQLServer { using System; + using System.Collections.Generic; + using System.Linq; using System.Threading.Tasks; using Extensibility; - using Settings; class SubscriptionManager : IManageSubscriptions { - IManageTransportSubscriptions subscriptions; - string endpointName; - string localAddress; - - public SubscriptionManager(IManageTransportSubscriptions subscriptions, ReadOnlySettings settings) + public SubscriptionManager(ISubscriptionStore subscriptionStore, Func> typeHierarchyResolver, string endpointName, string localAddress) { - this.subscriptions = subscriptions; - endpointName = settings.EndpointName(); - localAddress = settings.LocalAddress(); + this.subscriptionStore = subscriptionStore; + this.typeHierarchyResolver = typeHierarchyResolver; + this.endpointName = endpointName; + this.localAddress = localAddress; } public Task Subscribe(Type eventType, ContextBag context) { - return subscriptions.Subscribe(endpointName, localAddress, eventType); + return InvokeForEachTypeInHierarchy(eventType, topic => subscriptionStore.Subscribe(endpointName, localAddress, topic)); } public Task Unsubscribe(Type eventType, ContextBag context) { - return subscriptions.Unsubscribe(endpointName, eventType); + return InvokeForEachTypeInHierarchy(eventType, topic => subscriptionStore.Unsubscribe(endpointName, topic)); + } + + Task InvokeForEachTypeInHierarchy(Type type, Func action) + { + var allTasks = typeHierarchyResolver(type).Select(x => action(TopicName(x))).ToArray(); + return Task.WhenAll(allTasks); } + + static string TopicName(Type type) + { + return $"{type.Namespace}.{type.Name}"; + } + + ISubscriptionStore subscriptionStore; + Func> typeHierarchyResolver; + string endpointName; + string localAddress; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs similarity index 76% rename from src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs rename to src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs index 70f20571e..8f5ca085b 100644 --- a/src/NServiceBus.SqlServer/PubSub/TableBasedSubscriptions.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs @@ -1,19 +1,18 @@ namespace NServiceBus.Transport.SQLServer { - using System; using System.Collections.Generic; using System.Data; using System.Threading.Tasks; using System.Transactions; - class TableBasedSubscriptions : IManageTransportSubscriptions + class SubscriptionTable : ISubscriptionStore { SqlConnectionFactory connectionFactory; string subscribeCommand; string unsubscribeCommand; string getSubscribersCommand; - public TableBasedSubscriptions(string qualifiedTableName, SqlConnectionFactory connectionFactory) + public SubscriptionTable(string qualifiedTableName, SqlConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; // TODO: Be able to change the subscriptions table name and schema @@ -24,7 +23,7 @@ public TableBasedSubscriptions(string qualifiedTableName, SqlConnectionFactory c #pragma warning restore 618 } - public async Task Subscribe(string endpointName, string endpointAddress, Type eventType) + public async Task Subscribe(string endpointName, string endpointAddress, string topic) { using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { @@ -32,8 +31,8 @@ public async Task Subscribe(string endpointName, string endpointAddress, Type ev using (var command = connection.CreateCommand()) { command.CommandText = subscribeCommand; - command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); + command.Parameters.Add("QueueAddress", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("Topic", SqlDbType.VarChar).Value = topic; command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; await command.ExecuteNonQueryAsync().ConfigureAwait(false); @@ -41,7 +40,7 @@ public async Task Subscribe(string endpointName, string endpointAddress, Type ev } } - public async Task Unsubscribe(string endpointName, Type eventType) + public async Task Unsubscribe(string endpointName, string topic) { using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { @@ -49,15 +48,15 @@ public async Task Unsubscribe(string endpointName, Type eventType) using (var command = connection.CreateCommand()) { command.CommandText = unsubscribeCommand; - command.Parameters.Add("Subscriber", SqlDbType.VarChar).Value = endpointName; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); + command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("Topic", SqlDbType.VarChar).Value = topic; await command.ExecuteNonQueryAsync().ConfigureAwait(false); } } } - public async Task> GetSubscribersForEvent(Type eventType) + public async Task> GetSubscribersForTopic(string topic) { var results = new List(); using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) @@ -66,7 +65,7 @@ public async Task> GetSubscribersForEvent(Type eventType) using (var command = connection.CreateCommand()) { command.CommandText = getSubscribersCommand; - command.Parameters.Add("MessageType", SqlDbType.VarChar).Value = eventType.ToString(); + command.Parameters.Add("Topic", SqlDbType.VarChar).Value = topic; using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) { diff --git a/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs b/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs deleted file mode 100644 index 519e8d4e3..000000000 --- a/src/NServiceBus.SqlServer/PubSub/TransportPubSub.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using Settings; - using Unicast.Messages; - - class TransportPubSub - { - IManageTransportSubscriptions subscriptions; - - public IManageTransportSubscriptions GetTransportSubscriptionsManager(ReadOnlySettings settings, SqlConnectionFactory connectionFactory) - { - if (subscriptions != null) - { - return subscriptions; - } - - subscriptions = new TableBasedSubscriptions(connectionFactory); - - if (settings.TryGet(out var messageMetadataRegistry)) - { - subscriptions = new PolymorphicTransportSubscriptionsManagerWrapper( - subscriptions, - messageMetadataRegistry - ); - } - - var pubSubSettings = settings.GetOrDefault() ?? new TransportPubSubOptions(); - - if (pubSubSettings.TimeToCacheSubscription.HasValue) - { - subscriptions = new SubscriptionCache(subscriptions, pubSubSettings.TimeToCacheSubscription.Value); - } - - return subscriptions; - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index 0b841a4fa..685a6dd6d 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -277,49 +277,49 @@ AND type in (N'U')) END CREATE TABLE {0} ( - Subscriber NVARCHAR(200) NOT NULL, + QueueAddress NVARCHAR(200) NOT NULL, Endpoint NVARCHAR(200), - MessageType NVARCHAR(200) NOT NULL, + Topic NVARCHAR(200) NOT NULL, PRIMARY KEY CLUSTERED ( - Subscriber, - MessageType + Endpoint, + Topic ) ) EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static readonly string SubscribeText = @" MERGE {0} WITH (HOLDLOCK, TABLOCK) AS target -USING(SELECT @Endpoint AS Endpoint, @Subscriber AS Subscriber, @MessageType AS MessageType) AS source -ON target.Subscriber = source.Subscriber -AND target.MessageType = source.MessageType -WHEN MATCHED AND source.Endpoint IS NOT NULL AND (target.Endpoint IS NULL OR target.Endpoint <> source.Endpoint) THEN -UPDATE SET Endpoint = @Endpoint +USING(SELECT @Endpoint AS Endpoint, @QueueAddress AS QueueAddress, @Topic AS Topic) AS source +ON target.Endpoint = source.Endpoint +AND target.Topic = source.Topic +WHEN MATCHED AND target.QueueAddress <> source.QueueAddress THEN +UPDATE SET QueueAddress = @QueueAddress WHEN NOT MATCHED THEN INSERT ( - Subscriber, - MessageType, + QueueAddress, + Topic, Endpoint ) VALUES ( - @Subscriber, - @MessageType, + @QueueAddress, + @Topic, @Endpoint );"; public static readonly string GetSubscribersText = @" -SELECT DISTINCT Subscriber, Endpoint +SELECT DISTINCT QueueAddress, Endpoint FROM {0} -WHERE MessageType IN (@MessageType) +WHERE Topic = @Topic "; public static readonly string UnsubscribeText = @" DELETE FROM {0} WHERE - Subscriber = @Subscriber and - MessageType = @MessageType"; + Endpoint = @Endpoint and + Topic = @Topic"; } } diff --git a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueCache.cs b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueCache.cs new file mode 100644 index 000000000..c29978507 --- /dev/null +++ b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueCache.cs @@ -0,0 +1,25 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Concurrent; + + class TableBasedQueueCache + { + public TableBasedQueueCache(QueueAddressTranslator addressTranslator) + { + this.addressTranslator = addressTranslator; + } + + public TableBasedQueue Get(string destination) + { + var address = addressTranslator.Parse(destination); + var key = Tuple.Create(address.QualifiedTableName, address.Address); + var queue = cache.GetOrAdd(key, x => new TableBasedQueue(x.Item1, x.Item2)); + + return queue; + } + + QueueAddressTranslator addressTranslator; + ConcurrentDictionary, TableBasedQueue> cache = new ConcurrentDictionary, TableBasedQueue>(); + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs index 12120004b..19d1f4080 100644 --- a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs +++ b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs @@ -1,7 +1,6 @@ namespace NServiceBus.Transport.SQLServer { using System; - using System.Collections.Concurrent; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; @@ -11,9 +10,9 @@ namespace NServiceBus.Transport.SQLServer class TableBasedQueueOperationsReader : ITableBasedQueueOperationsReader { - public TableBasedQueueOperationsReader(QueueAddressTranslator addressTranslator, DelayedMessageTable delayedMessageTable) + public TableBasedQueueOperationsReader(TableBasedQueueCache tableBasedQueueCache, IDelayedMessageStore delayedMessageTable) { - this.addressTranslator = addressTranslator; + this.tableBasedQueueCache = tableBasedQueueCache; this.delayedMessageTable = delayedMessageTable; } @@ -32,10 +31,7 @@ public Func Get(UnicastTransportOperation o return (conn, trans) => delayedMessageTable.Store(operation.Message, behavior.DueAfter, behavior.Destination, conn, trans); } - var address = addressTranslator.Parse(operation.Destination); - var key = Tuple.Create(address.QualifiedTableName, address.Address); - var queue = cache.GetOrAdd(key, x => new TableBasedQueue(x.Item1, x.Item2)); - + var queue = tableBasedQueueCache.Get(operation.Destination); return (conn, trans) => queue.Send(operation.Message, discardIfNotReceivedBefore?.MaxTime ?? TimeSpan.MaxValue, conn, trans); } @@ -80,8 +76,7 @@ public static DispatchBehavior Deferred(TimeSpan dueAfter, string destination) } } - DelayedMessageTable delayedMessageTable; - QueueAddressTranslator addressTranslator; - ConcurrentDictionary, TableBasedQueue> cache = new ConcurrentDictionary, TableBasedQueue>(); + TableBasedQueueCache tableBasedQueueCache; + IDelayedMessageStore delayedMessageTable; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs index c1c73ec20..94d2f18b6 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithNativeTransaction.cs @@ -9,8 +9,8 @@ class ProcessWithNativeTransaction : ReceiveStrategy { - public ProcessWithNativeTransaction(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, IQueueDispatcher queueDispatcher, bool transactionForReceiveOnly = false) - : base(queueDispatcher) + public ProcessWithNativeTransaction(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, TableBasedQueueCache tableBasedQueueCache, bool transactionForReceiveOnly = false) + : base(tableBasedQueueCache) { this.connectionFactory = connectionFactory; this.failureInfoStorage = failureInfoStorage; diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs index 21cbd819d..00948a3bd 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithNoTransaction.cs @@ -7,8 +7,8 @@ namespace NServiceBus.Transport.SQLServer class ProcessWithNoTransaction : ReceiveStrategy { - public ProcessWithNoTransaction(SqlConnectionFactory connectionFactory, IQueueDispatcher dispatcher) - : base(dispatcher) + public ProcessWithNoTransaction(SqlConnectionFactory connectionFactory, TableBasedQueueCache tableBasedQueueCache) + : base(tableBasedQueueCache) { this.connectionFactory = connectionFactory; } diff --git a/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs b/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs index 9d86ff32a..d03353c30 100644 --- a/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs +++ b/src/NServiceBus.SqlServer/Receiving/ProcessWithTransactionScope.cs @@ -7,8 +7,8 @@ class ProcessWithTransactionScope : ReceiveStrategy { - public ProcessWithTransactionScope(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, IQueueDispatcher dispatcher) - : base(dispatcher) + public ProcessWithTransactionScope(TransactionOptions transactionOptions, SqlConnectionFactory connectionFactory, FailureInfoStorage failureInfoStorage, TableBasedQueueCache tableBasedQueueCache) + : base(tableBasedQueueCache) { this.transactionOptions = transactionOptions; this.connectionFactory = connectionFactory; diff --git a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs index 17ff1a667..fb4bafa31 100644 --- a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs +++ b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs @@ -8,14 +8,12 @@ namespace NServiceBus.Transport.SQLServer class QueueCreator : ICreateQueues { - public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, - LogicalAddress delayedQueueAddress, QualifiedSubscriptionTableName subscriptionTable, - bool createMessageBodyColumn = false) + public QueueCreator(SqlConnectionFactory connectionFactory, QueueAddressTranslator addressTranslator, + CanonicalQueueAddress delayedQueueAddress, bool createMessageBodyColumn = false) { this.connectionFactory = connectionFactory; this.addressTranslator = addressTranslator; this.delayedQueueAddress = delayedQueueAddress; - this.subscriptionTable = subscriptionTable; this.createMessageBodyColumn = createMessageBodyColumn; } @@ -40,7 +38,6 @@ public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string ide using (var transaction = connection.BeginTransaction()) { await CreateDelayedMessageQueue(connection, transaction, createMessageBodyColumn).ConfigureAwait(false); - await CreateSubscriptionsTable(connection, transaction).ConfigureAwait(false); transaction.Commit(); } } @@ -71,9 +68,8 @@ static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlCo async Task CreateDelayedMessageQueue(SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) { - var delayedQueue = GetDelayedQueueTableName(); #pragma warning disable 618 - var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, delayedQueue.QualifiedTableName, delayedQueue.QuotedCatalogName); + var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, delayedQueueAddress.QualifiedTableName, delayedQueueAddress.QuotedCatalogName); #pragma warning restore 618 using (var command = new SqlCommand(sql, connection, transaction) { @@ -85,7 +81,7 @@ async Task CreateDelayedMessageQueue(SqlConnection connection, SqlTransaction tr if (createMessageBodyComputedColumn) { #pragma warning disable 618 - var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, delayedQueue.QualifiedTableName, delayedQueue.QuotedCatalogName); + var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, delayedQueueAddress.QualifiedTableName, delayedQueueAddress.QuotedCatalogName); #pragma warning restore 618 using (var command = new SqlCommand(bodyStringSql, connection, transaction) { @@ -98,29 +94,9 @@ async Task CreateDelayedMessageQueue(SqlConnection connection, SqlTransaction tr } } - async Task CreateSubscriptionsTable(SqlConnection connection, SqlTransaction transaction) - { -#pragma warning disable 618 - var sql = string.Format(SqlConstants.CreateSubscriptionTableText, subscriptionTable.QuotedQualifiedName, subscriptionTable.QuotedCatalog); -#pragma warning restore 618 - using (var command = new SqlCommand(sql, connection, transaction) - { - CommandType = CommandType.Text - }) - { - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - } - - CanonicalQueueAddress GetDelayedQueueTableName() - { - return addressTranslator.GetCanonicalForm(addressTranslator.Generate(delayedQueueAddress)); - } - SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; - LogicalAddress delayedQueueAddress; - QualifiedSubscriptionTableName subscriptionTable; + CanonicalQueueAddress delayedQueueAddress; bool createMessageBodyColumn; } } diff --git a/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs b/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs index ef949d5cc..d61e61f12 100644 --- a/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs +++ b/src/NServiceBus.SqlServer/Receiving/ReceiveStrategy.cs @@ -1,7 +1,6 @@ namespace NServiceBus.Transport.SQLServer { using System; - using System.Collections.Generic; using System.Data.SqlClient; using System.Threading; using System.Threading.Tasks; @@ -15,9 +14,9 @@ abstract class ReceiveStrategy Func onMessage; Func> onError; - protected ReceiveStrategy(IQueueDispatcher queueDispatcher) + protected ReceiveStrategy(TableBasedQueueCache tableBasedQueueCache) { - this.queueDispatcher = queueDispatcher; + this.tableBasedQueueCache = tableBasedQueueCache; } public void Init(TableBasedQueue inputQueue, TableBasedQueue errorQueue, Func onMessage, Func> onError, CriticalError criticalError) @@ -44,6 +43,11 @@ protected async Task TryReceive(SqlConnection connection, SqlTransactio if (receiveResult.Successful) { + if (await TryHandleDelayedMessage(receiveResult.Message, connection, transaction).ConfigureAwait(false)) + { + return null; + } + return receiveResult.Message; } receiveCancellationTokenSource.Cancel(); @@ -59,7 +63,6 @@ protected async Task TryProcessingMessage(Message message, TransportTransa using (var pushCancellationTokenSource = new CancellationTokenSource()) { var messageContext = new MessageContext(message.TransportId, message.Headers, message.Body, transportTransaction, pushCancellationTokenSource, new ContextBag()); - await onMessage(messageContext).ConfigureAwait(false); // Cancellation is requested when message processing is aborted. @@ -87,9 +90,13 @@ protected async Task HandleError(Exception exception, Message } } - public async Task TryHandleDelayedMessage(MessageContext context) + async Task TryHandleDelayedMessage(Message message, SqlConnection connection, SqlTransaction transaction) { - context.Headers.TryGetValue(ForwardHeader, out var forwardDestination); + if (message.Headers.TryGetValue(ForwardHeader, out var forwardDestination)) + { + message.Headers.Remove(ForwardHeader); + } + if (forwardDestination == null) { //This is not a delayed message. Process in local endpoint instance. @@ -97,21 +104,18 @@ public async Task TryHandleDelayedMessage(MessageContext context) } if (forwardDestination == InputQueue.Name) { - context.Headers.Remove(ForwardHeader); //Do not forward the message. Process in local endpoint instance. return false; } - var outgoingMessage = new OutgoingMessage(context.MessageId, context.Headers, context.Body); - context.Headers.Remove(ForwardHeader); - await queueDispatcher.DispatchAsNonIsolated(new List - { - new UnicastTransportOperation(outgoingMessage, forwardDestination) - }, context.TransportTransaction).ConfigureAwait(false); + var outgoingMessage = new OutgoingMessage(message.TransportId, message.Headers, message.Body); + + var destinationQueue = tableBasedQueueCache.Get(forwardDestination); + await destinationQueue.Send(outgoingMessage, TimeSpan.MaxValue, connection, transaction).ConfigureAwait(false); return true; } const string ForwardHeader = "NServiceBus.SqlServer.ForwardDestination"; - IQueueDispatcher queueDispatcher; + TableBasedQueueCache tableBasedQueueCache; CriticalError criticalError; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index c9f7db79f..81eaa779a 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -9,7 +9,7 @@ class MessageDispatcher : IDispatchMessages { - public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IManageTransportSubscriptions subscriptions) + public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, ISubscriptionStore subscriptions) { this.dispatcher = dispatcher; this.addressTranslator = addressTranslator; @@ -48,7 +48,7 @@ async Task> ConvertToUnicastOperations(Transport async Task> ConvertToUnicastOperations(MulticastTransportOperation transportOperation) { - var destinations = await subscriptions.GetSubscribersForEvent(transportOperation.MessageType).ConfigureAwait(false); + var destinations = await subscriptions.GetSubscribersForTopic(TopicName(transportOperation.MessageType)).ConfigureAwait(false); return (from destination in destinations select new UnicastTransportOperation( @@ -61,7 +61,13 @@ async Task> ConvertToUnicastOperations(Multicast IQueueDispatcher dispatcher; QueueAddressTranslator addressTranslator; - IManageTransportSubscriptions subscriptions; + ISubscriptionStore subscriptions; + + static string TopicName(Type type) + { + return $"{type.Namespace}.{type.Name}"; + } + class DeduplicationKey { diff --git a/src/NServiceBus.SqlServer/SqlServerTransport.cs b/src/NServiceBus.SqlServer/SqlServerTransport.cs index 44346bd12..dbd6c6026 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransport.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransport.cs @@ -38,12 +38,9 @@ static bool LegacyMultiInstanceModeTurnedOn(SettingsHolder settings) /// public override TransportInfrastructure Initialize(SettingsHolder settings, string connectionString) { - settings.TryGet(SettingsKeys.DefaultSchemaSettingsKey, out string defaultSchemaOverride); - var queueSchemaSettings = settings.GetOrDefault(); - var catalog = GetDefaultCatalog(settings, connectionString); - var addressParser = new QueueAddressTranslator(catalog, "dbo", defaultSchemaOverride, queueSchemaSettings); - return new SqlServerTransportInfrastructure(addressParser, settings, connectionString); + + return new SqlServerTransportInfrastructure(catalog, settings, connectionString, settings.LocalAddress, settings.LogicalAddress); } static string GetDefaultCatalog(SettingsHolder settings, string connectionString) diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 30b3dd932..d46f4fa76 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -3,7 +3,6 @@ namespace NServiceBus.Transport.SQLServer using System; using System.Collections.Generic; using System.Data.SqlClient; - using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Transactions; using DelayedDelivery; @@ -12,21 +11,53 @@ namespace NServiceBus.Transport.SQLServer using Routing; using Settings; using Transport; + using Unicast.Messages; + + /// + /// ConfigureReceiveInfrastructure is called first, before features are started + /// + /// ConfigureSendInfrastructure is called last, when starting + /// class SqlServerTransportInfrastructure : TransportInfrastructure { - internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslator, SettingsHolder settings, string connectionString) + internal SqlServerTransportInfrastructure(string catalog, SettingsHolder settings, string connectionString, Func localAddress, Func logicalAddress) { - this.addressTranslator = addressTranslator; this.settings = settings; this.connectionString = connectionString; + this.localAddress = localAddress; + this.logicalAddress = logicalAddress; - schemaAndCatalogSettings = settings.GetOrCreate(); - delayedDeliverySettings = settings.GetOrCreate(); - pubSubSettings = settings.GetOrCreate(); + settings.TryGet(SettingsKeys.DefaultSchemaSettingsKey, out string defaultSchemaOverride); - var timeoutManagerFeatureDisabled = !settings.IsFeatureEnabled(typeof(TimeoutManager)); + var queueSchemaSettings = settings.GetOrDefault(); + addressTranslator = new QueueAddressTranslator(catalog, "dbo", defaultSchemaOverride, queueSchemaSettings); + tableBasedQueueCache = new TableBasedQueueCache(addressTranslator); + connectionFactory = CreateConnectionFactory(); + + //Configure the schema and catalog for logical endpoint-based routing + var schemaAndCatalogSettings = settings.GetOrCreate(); + settings.GetOrCreate().AddOrReplaceInstances("SqlServer", schemaAndCatalogSettings.ToEndpointInstances()); + + //Needs to be invoked here and not when configuring the receiving infrastructure because the EnableMigrationMode flag has to be set up before feature component is initialized. + HandleTimeoutManagerCompatibilityMode(); + var pubSubSettings = settings.GetOrCreate(); + var subscriptionTableName = pubSubSettings.SubscriptionTable.Qualify(defaultSchemaOverride ?? "dbo", catalog); + subscriptionStore = new SubscriptionTable(subscriptionTableName.QuotedQualifiedName, connectionFactory); + var timeToCacheSubscriptions = pubSubSettings.GetCacheFor(); + if (timeToCacheSubscriptions.HasValue) + { + subscriptionStore = new SubscriptionCache(subscriptionStore, timeToCacheSubscriptions.Value); + } + var subscriptionTableCreator = new SubscriptionTableCreator(subscriptionTableName, connectionFactory); + settings.Set(subscriptionTableCreator); + } + + void HandleTimeoutManagerCompatibilityMode() + { + var delayedDeliverySettings = settings.GetOrCreate(); + var timeoutManagerFeatureDisabled = !settings.IsFeatureEnabled(typeof(TimeoutManager)); diagnostics.Add("NServiceBus.Transport.SqlServer.TimeoutManager", new { FeatureEnabled = !timeoutManagerFeatureDisabled @@ -38,15 +69,16 @@ internal SqlServerTransportInfrastructure(QueueAddressTranslator addressTranslat } settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); + } - var subscriptionTableName = pubSubSettings.SubscriptionTable.Qualify(addressTranslator.DefaultSchema, addressTranslator.DefaultCatalog); - subscriptions = new TableBasedSubscriptions(subscriptionTableName.QuotedQualifiedName, CreateConnectionFactory()); - - var timeToCacheSubscriptions = pubSubSettings.GetCacheFor(); - if (timeToCacheSubscriptions.HasValue) + SqlConnectionFactory CreateConnectionFactory() + { + if (settings.TryGet(SettingsKeys.ConnectionFactoryOverride, out Func> factoryOverride)) { - subscriptions = new SubscriptionCache(subscriptions, timeToCacheSubscriptions.Value); + return new SqlConnectionFactory(factoryOverride); } + + return SqlConnectionFactory.Default(connectionString); } public override IEnumerable DeliveryConstraints @@ -94,61 +126,64 @@ public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() var createMessageBodyComputedColumn = settings.GetOrDefault(SettingsKeys.CreateMessageBodyComputedColumn); - var connectionFactory = CreateConnectionFactory(); - Func receiveStrategyFactory = guarantee => SelectReceiveStrategy(guarantee, scopeOptions.TransactionOptions, connectionFactory); var queuePurger = new QueuePurger(connectionFactory); var queuePeeker = new QueuePeeker(connectionFactory, queuePeekerOptions); - var expiredMessagesPurger = CreateExpiredMessagesPurger(connectionFactory); + var expiredMessagesPurger = CreateExpiredMessagesPurger(); var schemaVerification = new SchemaInspector(queue => connectionFactory.OpenNewConnection()); Func queueFactory = queueName => new TableBasedQueue(addressTranslator.Parse(queueName).QualifiedTableName, queueName); - var delayedMessageStore = GetDelayedQueueTableName(); - var qualifiedSubscriptionTable = pubSubSettings.SubscriptionTable.Qualify(addressTranslator.DefaultSchema, addressTranslator.DefaultCatalog); + //Create delayed delivery infrastructure + var delayedDeliverySettings = settings.GetOrDefault(); + settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new + { + Native = true, + delayedDeliverySettings.Suffix, + delayedDeliverySettings.Interval, + BatchSize = delayedDeliverySettings.MatureBatchSize, + TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" + }); - return new TransportReceiveInfrastructure( - () => new MessagePump(receiveStrategyFactory, queueFactory, queuePurger, expiredMessagesPurger, queuePeeker, schemaVerification, waitTimeCircuitBreaker), - () => new QueueCreator(connectionFactory, addressTranslator, delayedMessageStore, qualifiedSubscriptionTable, createMessageBodyComputedColumn), - () => CheckForAmbientTransactionEnlistmentSupport(connectionFactory, scopeOptions.TransactionOptions)); - } + var delayedQueueLogicalAddress = logicalAddress().CreateQualifiedAddress(delayedDeliverySettings.Suffix); + var delayedQueueAddress = addressTranslator.Generate(delayedQueueLogicalAddress); + var delayedQueueCanonicalAddress = addressTranslator.GetCanonicalForm(delayedQueueAddress); + var delayedMessageTable = new DelayedMessageTable(delayedQueueCanonicalAddress.QualifiedTableName, localAddress()); - SqlConnectionFactory CreateConnectionFactory() - { - if (settings.TryGet(SettingsKeys.ConnectionFactoryOverride, out Func> factoryOverride)) - { - return new SqlConnectionFactory(factoryOverride); - } + //Allows dispatcher to store messages in the delayed store + delayedMessageStore = delayedMessageTable; + dueDelayedMessageProcessor = new DueDelayedMessageProcessor(delayedMessageTable, connectionFactory, delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); - return SqlConnectionFactory.Default(connectionString); + return new TransportReceiveInfrastructure( + () => new MessagePump(receiveStrategyFactory, queueFactory, queuePurger, expiredMessagesPurger, queuePeeker, schemaVerification, waitTimeCircuitBreaker), + () => new QueueCreator(connectionFactory, addressTranslator, delayedQueueCanonicalAddress, createMessageBodyComputedColumn), + () => CheckForAmbientTransactionEnlistmentSupport(scopeOptions.TransactionOptions)); } ReceiveStrategy SelectReceiveStrategy(TransportTransactionMode minimumConsistencyGuarantee, TransactionOptions options, SqlConnectionFactory connectionFactory) { - var dispatcher = new TableBasedQueueDispatcher(connectionFactory, new TableBasedQueueOperationsReader(addressTranslator, CreateDelayedMessageTable())); - if (minimumConsistencyGuarantee == TransportTransactionMode.TransactionScope) { - return new ProcessWithTransactionScope(options, connectionFactory, new FailureInfoStorage(10000), dispatcher); + return new ProcessWithTransactionScope(options, connectionFactory, new FailureInfoStorage(10000), tableBasedQueueCache); } if (minimumConsistencyGuarantee == TransportTransactionMode.SendsAtomicWithReceive) { - return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), dispatcher); + return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), tableBasedQueueCache); } if (minimumConsistencyGuarantee == TransportTransactionMode.ReceiveOnly) { - return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), dispatcher, transactionForReceiveOnly: true); + return new ProcessWithNativeTransaction(options, connectionFactory, new FailureInfoStorage(10000), tableBasedQueueCache, transactionForReceiveOnly: true); } - return new ProcessWithNoTransaction(connectionFactory, dispatcher); + return new ProcessWithNoTransaction(connectionFactory, tableBasedQueueCache); } - ExpiredMessagesPurger CreateExpiredMessagesPurger(SqlConnectionFactory connectionFactory) + ExpiredMessagesPurger CreateExpiredMessagesPurger() { var purgeBatchSize = settings.HasSetting(SettingsKeys.PurgeBatchSizeKey) ? settings.Get(SettingsKeys.PurgeBatchSizeKey) : null; var enable = settings.GetOrDefault(SettingsKeys.PurgeEnableKey); @@ -162,7 +197,7 @@ ExpiredMessagesPurger CreateExpiredMessagesPurger(SqlConnectionFactory connectio return new ExpiredMessagesPurger(_ => connectionFactory.OpenNewConnection(), purgeBatchSize, enable); } - async Task CheckForAmbientTransactionEnlistmentSupport(SqlConnectionFactory connectionFactory, TransactionOptions transactionOptions) + async Task CheckForAmbientTransactionEnlistmentSupport(TransactionOptions transactionOptions) { if (!settings.TryGet(out TransportTransactionMode requestedTransportTransactionMode)) { @@ -197,52 +232,16 @@ async Task CheckForAmbientTransactionEnlistmentSupport(SqlCo public override TransportSendInfrastructure ConfigureSendInfrastructure() { - var connectionFactory = CreateConnectionFactory(); - - settings.GetOrCreate().AddOrReplaceInstances("SqlServer", schemaAndCatalogSettings.ToEndpointInstances()); - - var subscriptions = pubSub.GetTransportSubscriptionsManager(settings, connectionFactory); - return new TransportSendInfrastructure( () => { - ITableBasedQueueOperationsReader queueOperationsReader = new TableBasedQueueOperationsReader(addressTranslator, CreateDelayedMessageTable()); - var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, subscriptions); + var queueOperationsReader = new TableBasedQueueOperationsReader(tableBasedQueueCache, delayedMessageStore); + var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, subscriptionStore); return dispatcher; }, () => Task.FromResult(StartupCheckResult.Success)); } - - - DelayedMessageTable CreateDelayedMessageTable() - { - var deletedQueueTableName = GetDelayedQueueTableName(); - - var inputQueueTable = addressTranslator.Parse(ToTransportAddress(GetLogicalAddress())).QualifiedTableName; - return new DelayedMessageTable(deletedQueueTableName.QualifiedTableName, inputQueueTable); - } - - /// - /// This method is copied from the core because there is no other way to reliable get the address of the main input queue. - /// - /// - LogicalAddress GetLogicalAddress() - { - var queueNameBase = settings.GetOrDefault("BaseInputQueueName") ?? settings.EndpointName(); - - //note: This is an old hack, we are passing the endpoint name to bind but we only care about the properties - var mainInstanceProperties = BindToLocalEndpoint(new EndpointInstance(settings.EndpointName())).Properties; - - return LogicalAddress.CreateLocalAddress(queueNameBase, mainInstanceProperties); - } - - CanonicalQueueAddress GetDelayedQueueTableName() - { - var delayedQueueLogicalAddress = GetLogicalAddress().CreateQualifiedAddress(delayedDeliverySettings.Suffix); - var delayedQueueAddress = addressTranslator.Generate(delayedQueueLogicalAddress); - return addressTranslator.GetCanonicalForm(delayedQueueAddress); - } - + public override Task Start() { foreach (var diagnosticSection in diagnostics) @@ -250,19 +249,7 @@ public override Task Start() settings.AddStartupDiagnosticsSection(diagnosticSection.Key, diagnosticSection.Value); } - settings.AddStartupDiagnosticsSection("NServiceBus.Transport.SqlServer.DelayedDelivery", new - { - Native = true, - delayedDeliverySettings.Suffix, - delayedDeliverySettings.Interval, - BatchSize = delayedDeliverySettings.MatureBatchSize, - TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" - }); - - var delayedMessageTable = CreateDelayedMessageTable(); - dueDelayedMessageProcessor = new DueDelayedMessageProcessor(delayedMessageTable, CreateConnectionFactory(), delayedDeliverySettings.Interval, delayedDeliverySettings.MatureBatchSize); - dueDelayedMessageProcessor.Start(); - + dueDelayedMessageProcessor?.Start(); return Task.FromResult(0); } @@ -273,8 +260,11 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { - var subscriptions = pubSub.GetTransportSubscriptionsManager(settings, CreateConnectionFactory()); - return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptions, settings)); + + return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptionStore, + x => settings.Get().GetMessageMetadata(x).MessageHierarchy, + settings.EndpointName(), + localAddress())); } public override EndpointInstance BindToLocalEndpoint(EndpointInstance instance) @@ -305,11 +295,14 @@ public override string MakeCanonicalForm(string transportAddress) QueueAddressTranslator addressTranslator; string connectionString; + Func localAddress; + Func logicalAddress; SettingsHolder settings; - EndpointSchemaAndCatalogSettings schemaAndCatalogSettings; DueDelayedMessageProcessor dueDelayedMessageProcessor; - DelayedDeliverySettings delayedDeliverySettings; Dictionary diagnostics = new Dictionary(); - TransportPubSub pubSub = new TransportPubSub(); + SqlConnectionFactory connectionFactory; + ISubscriptionStore subscriptionStore; + IDelayedMessageStore delayedMessageStore = new SendOnlyDelayedMessageStore(); + TableBasedQueueCache tableBasedQueueCache; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SubscriptionTableCreator.cs b/src/NServiceBus.SqlServer/SubscriptionTableCreator.cs new file mode 100644 index 000000000..bfb4c2986 --- /dev/null +++ b/src/NServiceBus.SqlServer/SubscriptionTableCreator.cs @@ -0,0 +1,36 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Data; + using System.Data.SqlClient; + using System.Threading.Tasks; + + class SubscriptionTableCreator + { + QualifiedSubscriptionTableName tableName; + SqlConnectionFactory connectionFactory; + + public SubscriptionTableCreator(QualifiedSubscriptionTableName tableName, SqlConnectionFactory connectionFactory) + { + this.tableName = tableName; + this.connectionFactory = connectionFactory; + } + public async Task CreateIfNecessary() + { + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var transaction = connection.BeginTransaction()) + { +#pragma warning disable 618 + var sql = string.Format(SqlConstants.CreateSubscriptionTableText, tableName.QuotedQualifiedName, tableName.QuotedCatalog); +#pragma warning restore 618 + using (var command = new SqlCommand(sql, connection, transaction) + { + CommandType = CommandType.Text + }) + { + await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + transaction.Commit(); + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs b/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs new file mode 100644 index 000000000..8509aae34 --- /dev/null +++ b/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs @@ -0,0 +1,26 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Threading.Tasks; + using Installation; + using Settings; + + /// + /// Subscription table cannot be created via ICreateQueues because it needs to be created also for send-only endpoints. Installers are registered + /// in the container via a convention so the only way to pass objects to them is via settings or via container. The transport infrastructure + /// cannot access the container so we're doing it via settings (HACK). + /// + class SubscriptionTableInstaller : INeedToInstallSomething + { + SubscriptionTableCreator creator; + + public SubscriptionTableInstaller(ReadOnlySettings settings) + { + creator = settings.GetOrDefault(); + } + + public Task Install(string identity) + { + return creator.CreateIfNecessary(); + } + } +} \ No newline at end of file From fdb166d12234c4f90579c4ea94c363c6a9b20dc6 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Thu, 31 Oct 2019 10:50:37 +0100 Subject: [PATCH 10/31] Bug fixes to make acceptance tests pass --- .../ConfigureEndpointSqlServerTransport.cs | 1 + .../PubSub/SubscriptionTable.cs | 8 ++++---- src/NServiceBus.SqlServer/Queuing/SqlConstants.cs | 2 +- .../SqlServerTransportInfrastructure.cs | 14 ++++++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index bbb6762fe..6ead0e467 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -51,6 +51,7 @@ public Task Cleanup() foreach (var address in queueAddresses) { TryDeleteTable(conn, address); + TryDeleteTable(conn, new QueueAddress("SubscriptionRouting", address.Schema, address.Catalog)); TryDeleteTable(conn, new QueueAddress(address.Table + ".Delayed", address.Schema, address.Catalog)); } } diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs index 8f5ca085b..24f061b1a 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs @@ -23,7 +23,7 @@ public SubscriptionTable(string qualifiedTableName, SqlConnectionFactory connect #pragma warning restore 618 } - public async Task Subscribe(string endpointName, string endpointAddress, string topic) + public async Task Subscribe(string endpointName, string queueAddress, string topic) { using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { @@ -31,9 +31,9 @@ public async Task Subscribe(string endpointName, string endpointAddress, string using (var command = connection.CreateCommand()) { command.CommandText = subscribeCommand; - command.Parameters.Add("QueueAddress", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointName; + command.Parameters.Add("QueueAddress", SqlDbType.VarChar).Value = queueAddress; command.Parameters.Add("Topic", SqlDbType.VarChar).Value = topic; - command.Parameters.Add("Endpoint", SqlDbType.VarChar).Value = endpointAddress; await command.ExecuteNonQueryAsync().ConfigureAwait(false); } @@ -71,7 +71,7 @@ public async Task> GetSubscribersForTopic(string topic) { while (await reader.ReadAsync().ConfigureAwait(false)) { - results.Add(reader.GetString(1)); + results.Add(reader.GetString(0)); } } diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index 685a6dd6d..f37ce2377 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -310,7 +310,7 @@ WHEN NOT MATCHED THEN );"; public static readonly string GetSubscribersText = @" -SELECT DISTINCT QueueAddress, Endpoint +SELECT DISTINCT QueueAddress FROM {0} WHERE Topic = @Topic "; diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index d46f4fa76..8659eb321 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -148,10 +148,9 @@ public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() TimoutManager = delayedDeliverySettings.EnableMigrationMode ? "enabled" : "disabled" }); - var delayedQueueLogicalAddress = logicalAddress().CreateQualifiedAddress(delayedDeliverySettings.Suffix); - var delayedQueueAddress = addressTranslator.Generate(delayedQueueLogicalAddress); - var delayedQueueCanonicalAddress = addressTranslator.GetCanonicalForm(delayedQueueAddress); - var delayedMessageTable = new DelayedMessageTable(delayedQueueCanonicalAddress.QualifiedTableName, localAddress()); + var delayedQueueCanonicalAddress = GetDelayedTableAddress(delayedDeliverySettings); + var inputQueueTable = addressTranslator.Parse(ToTransportAddress(logicalAddress())).QualifiedTableName; + var delayedMessageTable = new DelayedMessageTable(delayedQueueCanonicalAddress.QualifiedTableName, inputQueueTable); //Allows dispatcher to store messages in the delayed store delayedMessageStore = delayedMessageTable; @@ -163,6 +162,13 @@ public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() () => CheckForAmbientTransactionEnlistmentSupport(scopeOptions.TransactionOptions)); } + CanonicalQueueAddress GetDelayedTableAddress(DelayedDeliverySettings delayedDeliverySettings) + { + var delayedQueueLogicalAddress = logicalAddress().CreateQualifiedAddress(delayedDeliverySettings.Suffix); + var delayedQueueAddress = addressTranslator.Generate(delayedQueueLogicalAddress); + return addressTranslator.GetCanonicalForm(delayedQueueAddress); + } + ReceiveStrategy SelectReceiveStrategy(TransportTransactionMode minimumConsistencyGuarantee, TransactionOptions options, SqlConnectionFactory connectionFactory) { if (minimumConsistencyGuarantee == TransportTransactionMode.TransactionScope) From 1372e0db4c565de8247a648da884e20dd92ba9a8 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Mon, 4 Nov 2019 14:43:27 +0800 Subject: [PATCH 11/31] Fix test --- .../When_custom_schema_configured_for_publisher.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs index f2424dcf2..51e06e61a 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs @@ -14,7 +14,7 @@ public class When_custom_schema_configured_for_publisher : NServiceBusAcceptance public Task Should_receive_event() { return Scenario.Define() - .WithEndpoint(b => b.When(c => c.Subscribed, session => session.Publish(new Event()))) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, session => session.Publish(new Event()))) .WithEndpoint() .Done(c => c.EventReceived) .Run(); @@ -22,7 +22,6 @@ public Task Should_receive_event() class Context : ScenarioContext { - public bool Subscribed { get; set; } public bool EventReceived { get; set; } } @@ -33,9 +32,8 @@ public Publisher() EndpointSetup(b => { b.UseTransport() - .DefaultSchema("sender"); - - b.OnEndpointSubscribed((args, context) => { context.Subscribed = true; }); + .DefaultSchema("sender") + .PubSub().SubscriptionTableName("SubscriptionRouting", "dbo"); }); } } @@ -50,7 +48,9 @@ public Subscriber() b.UseTransport() .DefaultSchema("receiver") - .UseSchemaForEndpoint(publisherEndpoint, "sender"); + .UseSchemaForEndpoint(publisherEndpoint, "sender") + .PubSub().SubscriptionTableName("SubscriptionRouting", "dbo"); + // TODO: Use this for compatibility mode //.Routing().RegisterPublisher( // eventType: typeof(Event), From f685a80660cc698339ea0068ce256fe34ef048c3 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Mon, 4 Nov 2019 14:43:43 +0800 Subject: [PATCH 12/31] Approve API --- .../ApprovalFiles/APIApprovals.Approve.approved.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index d9fd5dfc3..9ed1ee530 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -1,4 +1,5 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] namespace NServiceBus @@ -21,6 +22,13 @@ namespace NServiceBus.Transport.SQLServer public void ProcessingInterval(System.TimeSpan interval) { } public void TableSuffix(string suffix) { } } + public class PubSubSettings + { + public PubSubSettings() { } + public void CacheSubscriptionInformationFor(System.TimeSpan timeSpan) { } + public void DisableSubscriptionCache() { } + public void SubscriptionTableName(string tableName, string schemaName = null, string catalogName = null) { } + } public class static SendOptionsExtensions { [System.ObsoleteAttribute("The connection parameter is no longer required. Use `UseCustomSqlTransaction` ins" + @@ -76,7 +84,6 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; } public class static SqlServerTransportSettingsExtensions { - public static void CacheSubscriptionsFor(this NServiceBus.TransportExtensions transportExtensions, System.TimeSpan timeToCacheSubscriptions) { } public static NServiceBus.TransportExtensions CreateMessageBodyComputedColumn(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions DefaultSchema(this NServiceBus.TransportExtensions transportExtensions, string schemaName) { } [System.ObsoleteAttribute("Multi-instance mode has been deprecated. Use Transport Bridge and/or multi-catalo" + @@ -84,6 +91,7 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; "l be removed in version 5.0.0.", true)] public static NServiceBus.TransportExtensions EnableLegacyMultiInstanceMode(this NServiceBus.TransportExtensions transportExtensions, System.Func> sqlConnectionFactory) { } public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings NativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } + public static NServiceBus.Transport.SQLServer.PubSubSettings PubSub(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions PurgeExpiredMessagesOnStartup(this NServiceBus.TransportExtensions transportExtensions, System.Nullable purgeBatchSize) { } public static NServiceBus.TransportExtensions TimeToWaitBeforeTriggeringCircuitBreaker(this NServiceBus.TransportExtensions transportExtensions, System.TimeSpan waitTime) { } public static NServiceBus.TransportExtensions TransactionScopeOptions(this NServiceBus.TransportExtensions transportExtensions, System.Nullable timeout = null, System.Nullable isolationLevel = null) { } From a79b2c8c1535a5164b1b23fdfd64bb6b83676f4b Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Mon, 4 Nov 2019 14:49:33 +0800 Subject: [PATCH 13/31] Topic abstraction --- .../When_dispatching_messages.cs | 18 +++------- .../When_using_ttbr.cs | 18 +++------- .../PubSub/SubscriptionManager.cs | 21 ++--------- .../PubSub/Topics/BasicTopicManager.cs | 13 +++++++ .../PubSub/Topics/ITopicManager.cs | 10 ++++++ .../PubSub/Topics/PolymorphicTopicManager.cs | 23 ++++++++++++ .../TopicBasedMulticastToUnicastConverter.cs | 35 +++++++++++++++++++ .../PubSub/Topics/TopicNames.cs | 9 +++++ .../Sending/IMulticastToUnicastConverter.cs | 10 ++++++ .../Sending/MessageDispatcher.cs | 28 +++------------ .../SqlServerTransportInfrastructure.cs | 15 ++++++-- 11 files changed, 128 insertions(+), 72 deletions(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs create mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs create mode 100644 src/NServiceBus.SqlServer/Sending/IMulticastToUnicastConverter.cs diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index ca8550260..5fa29d424 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -108,7 +108,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpMulticastToUnicastConverter()); } Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) @@ -138,21 +138,11 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato SqlConnectionFactory sqlConnectionFactory; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : ISubscriptionStore + class NoOpMulticastToUnicastConverter : IMulticastToUnicastConverter { - public Task> GetSubscribersForTopic(string topic) + public Task> Convert(MulticastTransportOperation transportOperation) { - return Task.FromResult(new List()); - } - - public Task Subscribe(string endpointName, string endpointAddress, string topic) - { - return Task.FromResult(0); - } - - public Task Unsubscribe(string endpointName, string topic) - { - return Task.FromResult(0); + return Task.FromResult(new List()); } } diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index dd48ab840..d00306f48 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -135,7 +135,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpSubscriptionStore()); + dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpMulticastToUnicastConverter()); } Task PurgeOutputQueue(QueueAddressTranslator addressParser) @@ -164,21 +164,11 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S const string validAddress = "TTBRTests"; // TODO: Figure out if this is appropriate in this test - class NoOpSubscriptionStore : ISubscriptionStore + class NoOpMulticastToUnicastConverter : IMulticastToUnicastConverter { - public Task> GetSubscribersForTopic(string topic) + public Task> Convert(MulticastTransportOperation transportOperation) { - return Task.FromResult(new List()); - } - - public Task Subscribe(string endpointName, string endpointAddress, string topic) - { - return Task.FromResult(0); - } - - public Task Unsubscribe(string endpointName, string topic) - { - return Task.FromResult(0); + return Task.FromResult(new List()); } } diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index a3f9e2ccf..0f7bb3d5d 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -1,44 +1,29 @@ namespace NServiceBus.Transport.SQLServer { using System; - using System.Collections.Generic; - using System.Linq; using System.Threading.Tasks; using Extensibility; class SubscriptionManager : IManageSubscriptions { - public SubscriptionManager(ISubscriptionStore subscriptionStore, Func> typeHierarchyResolver, string endpointName, string localAddress) + public SubscriptionManager(ISubscriptionStore subscriptionStore, string endpointName, string localAddress) { this.subscriptionStore = subscriptionStore; - this.typeHierarchyResolver = typeHierarchyResolver; this.endpointName = endpointName; this.localAddress = localAddress; } public Task Subscribe(Type eventType, ContextBag context) { - return InvokeForEachTypeInHierarchy(eventType, topic => subscriptionStore.Subscribe(endpointName, localAddress, topic)); + return subscriptionStore.Subscribe(endpointName, localAddress, TopicName.From(eventType)); } public Task Unsubscribe(Type eventType, ContextBag context) { - return InvokeForEachTypeInHierarchy(eventType, topic => subscriptionStore.Unsubscribe(endpointName, topic)); - } - - Task InvokeForEachTypeInHierarchy(Type type, Func action) - { - var allTasks = typeHierarchyResolver(type).Select(x => action(TopicName(x))).ToArray(); - return Task.WhenAll(allTasks); - } - - static string TopicName(Type type) - { - return $"{type.Namespace}.{type.Name}"; + return subscriptionStore.Unsubscribe(endpointName, TopicName.From(eventType)); } ISubscriptionStore subscriptionStore; - Func> typeHierarchyResolver; string endpointName; string localAddress; } diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs new file mode 100644 index 000000000..688707936 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs @@ -0,0 +1,13 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Generic; + + class BasicTopicManager : ITopicManager + { + public IEnumerable GetTopicsFor(Type messageType) + { + yield return TopicName.From(messageType); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs new file mode 100644 index 000000000..caf792c3c --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs @@ -0,0 +1,10 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Generic; + + interface ITopicManager + { + IEnumerable GetTopicsFor(Type messageType); + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs new file mode 100644 index 000000000..a43580f66 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs @@ -0,0 +1,23 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Unicast.Messages; + + class PolymorphicTopicManager : ITopicManager + { + MessageMetadataRegistry messageMetadataRegistry; + + public PolymorphicTopicManager(MessageMetadataRegistry messageMetadataRegistry) + { + this.messageMetadataRegistry = messageMetadataRegistry; + } + + public IEnumerable GetTopicsFor(Type messageType) + { + var messageMetadata = messageMetadataRegistry.GetMessageMetadata(messageType); + return messageMetadata.MessageHierarchy.Select(TopicName.From); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs b/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs new file mode 100644 index 000000000..0c91c3e6c --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs @@ -0,0 +1,35 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + class TopicBasedMulticastToUnicastConverter : IMulticastToUnicastConverter + { + ITopicManager topicManager; + ISubscriptionStore subscriptions; + + public TopicBasedMulticastToUnicastConverter(ITopicManager topicManager, ISubscriptionStore subscriptions) + { + this.topicManager = topicManager; + this.subscriptions = subscriptions; + } + + public async Task> Convert(MulticastTransportOperation transportOperation) + { + var topics = topicManager.GetTopicsFor(transportOperation.MessageType); + + var topicDestinations = await Task.WhenAll(topics.Select(subscriptions.GetSubscribersForTopic)) + .ConfigureAwait(false); + + return (from topicDestination in topicDestinations + from destination in topicDestination + select new UnicastTransportOperation( + transportOperation.Message, + destination, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints + )).ToList(); + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs b/src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs new file mode 100644 index 000000000..0656105c1 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs @@ -0,0 +1,9 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + + static class TopicName + { + public static string From(Type type) => $"{type.Namespace}.{type.Name}"; + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/IMulticastToUnicastConverter.cs b/src/NServiceBus.SqlServer/Sending/IMulticastToUnicastConverter.cs new file mode 100644 index 000000000..1aabfda37 --- /dev/null +++ b/src/NServiceBus.SqlServer/Sending/IMulticastToUnicastConverter.cs @@ -0,0 +1,10 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + interface IMulticastToUnicastConverter + { + Task> Convert(MulticastTransportOperation transportOperation); + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index 81eaa779a..a65817d26 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -9,11 +9,11 @@ class MessageDispatcher : IDispatchMessages { - public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, ISubscriptionStore subscriptions) + public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IMulticastToUnicastConverter multicastToUnicastConverter) { this.dispatcher = dispatcher; this.addressTranslator = addressTranslator; - this.subscriptions = subscriptions; + this.multicastToUnicastConverter = multicastToUnicastConverter; } // We need to check if we can support cancellation in here as well? @@ -38,36 +38,16 @@ Task DeduplicateAndDispatch(IEnumerable transportOper return dispatchMethod(operationsToDispatch); } - async Task> ConvertToUnicastOperations(TransportOperations operations) { - var tasks = operations.MulticastTransportOperations.Select(m => ConvertToUnicastOperations(m)).ToArray(); + var tasks = operations.MulticastTransportOperations.Select(multicastToUnicastConverter.Convert).ToArray(); await Task.WhenAll(tasks).ConfigureAwait(false); return tasks.SelectMany(t => t.Result).ToList(); } - async Task> ConvertToUnicastOperations(MulticastTransportOperation transportOperation) - { - var destinations = await subscriptions.GetSubscribersForTopic(TopicName(transportOperation.MessageType)).ConfigureAwait(false); - - return (from destination in destinations - select new UnicastTransportOperation( - transportOperation.Message, - destination, - transportOperation.RequiredDispatchConsistency, - transportOperation.DeliveryConstraints - )).ToList(); - } - IQueueDispatcher dispatcher; QueueAddressTranslator addressTranslator; - ISubscriptionStore subscriptions; - - static string TopicName(Type type) - { - return $"{type.Namespace}.{type.Name}"; - } - + IMulticastToUnicastConverter multicastToUnicastConverter; class DeduplicationKey { diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 8659eb321..ef4d25a63 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -242,11 +242,23 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() () => { var queueOperationsReader = new TableBasedQueueOperationsReader(tableBasedQueueCache, delayedMessageStore); - var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, subscriptionStore); + var topicManager = CreateTopicManager(); + var multicastToUnicastConverter = new TopicBasedMulticastToUnicastConverter(topicManager, subscriptionStore); + var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, multicastToUnicastConverter); return dispatcher; }, () => Task.FromResult(StartupCheckResult.Success)); } + + ITopicManager CreateTopicManager() + { + if (settings.TryGet(out var messageMetadataRegistry)) + { + return new PolymorphicTopicManager(messageMetadataRegistry); + } + + return new BasicTopicManager(); + } public override Task Start() { @@ -268,7 +280,6 @@ public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrast { return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptionStore, - x => settings.Get().GetMessageMetadata(x).MessageHierarchy, settings.EndpointName(), localAddress())); } From 94fcf8d588aba23bdcac4bc3a5b29797d6108a4b Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Mon, 4 Nov 2019 13:44:31 +0100 Subject: [PATCH 14/31] Remove some redundant abstractions --- .../SqlServerTransportTests.cs | 3 + .../When_dispatching_messages.cs | 2 +- .../When_using_ttbr.cs | 2 +- ...atcherTests.cs => OperationSorterTests.cs} | 45 ++--- .../ITableBasedQueueOperationsReader.cs | 11 -- .../TableBasedQueueOperationsReader.cs | 82 --------- .../Sending/IQueueDispatcher.cs | 12 -- .../Sending/MessageDispatcher.cs | 171 +++++++++++++----- .../Sending/OperationSorter.cs | 93 ++++++++++ .../Sending/TableBasedQueueDispatcher.cs | 125 ------------- .../SqlServerTransportInfrastructure.cs | 4 +- 11 files changed, 248 insertions(+), 302 deletions(-) rename src/NServiceBus.SqlServer.UnitTests/Sending/{MessageDispatcherTests.cs => OperationSorterTests.cs} (54%) delete mode 100644 src/NServiceBus.SqlServer/Queuing/ITableBasedQueueOperationsReader.cs delete mode 100644 src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs delete mode 100644 src/NServiceBus.SqlServer/Sending/IQueueDispatcher.cs create mode 100644 src/NServiceBus.SqlServer/Sending/OperationSorter.cs delete mode 100644 src/NServiceBus.SqlServer/Sending/TableBasedQueueDispatcher.cs diff --git a/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs b/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs index 5157b8303..5c4b4970d 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs @@ -32,6 +32,9 @@ public void It_reads_catalog_from_open_connection() }; var settings = new SettingsHolder(); settings.Set(SettingsKeys.ConnectionFactoryOverride, factory); + var pubSubSettings = new PubSubSettings(); + pubSubSettings.DisableSubscriptionCache(); + settings.Set(pubSubSettings); definition.Initialize(settings, "Invalid-connection-string"); Assert.Pass(); } diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index 5fa29d424..f3562e345 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -108,7 +108,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpMulticastToUnicastConverter()); + dispatcher = new MessageDispatcher(addressParser, new NoOpMulticastToUnicastConverter(), tableCache, null, sqlConnectionFactory); } Task PurgeOutputQueue(QueueAddressTranslator addressTranslator) diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index d00306f48..1d4a974d3 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -135,7 +135,7 @@ async Task PrepareAsync() await PurgeOutputQueue(addressParser); - dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(sqlConnectionFactory, new TableBasedQueueOperationsReader(tableCache, null)), addressParser, new NoOpMulticastToUnicastConverter()); + dispatcher = new MessageDispatcher(addressParser, new NoOpMulticastToUnicastConverter(), tableCache, null, sqlConnectionFactory); } Task PurgeOutputQueue(QueueAddressTranslator addressParser) diff --git a/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs b/src/NServiceBus.SqlServer.UnitTests/Sending/OperationSorterTests.cs similarity index 54% rename from src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs rename to src/NServiceBus.SqlServer.UnitTests/Sending/OperationSorterTests.cs index 886df1239..2ac03a122 100644 --- a/src/NServiceBus.SqlServer.UnitTests/Sending/MessageDispatcherTests.cs +++ b/src/NServiceBus.SqlServer.UnitTests/Sending/OperationSorterTests.cs @@ -2,26 +2,23 @@ { using System.Collections.Generic; using System.Linq; - using System.Threading.Tasks; - using Extensibility; using NUnit.Framework; using Routing; using Transport; using Transport.SQLServer; [TestFixture] - public class MessageDispatcherTests + public class OperationSorterTests { [TestCaseSource(nameof(TestCases))] - public async Task It_deduplicates_based_on_message_id_and_address(TransportOperations transportOperations, int expectedDispatchedMessageCount) + public void It_deduplicates_based_on_message_id_and_address(TransportOperations transportOperations, int expectedDispatchedMessageCount) { - var queueDispatcher = new FakeTableBasedQueueDispatcher(); + var queueAddressTranslator = new QueueAddressTranslator("nservicebus", "dbo", null, null); - var dispatcher = new MessageDispatcher(queueDispatcher, new QueueAddressTranslator("nservicebus", "dbo", null, null), null /* TODO: Put something in here */); + var sortResult = transportOperations.UnicastTransportOperations.SortAndDeduplicate(queueAddressTranslator); - await dispatcher.Dispatch(transportOperations, new TransportTransaction(), new ContextBag()); - - Assert.AreEqual(expectedDispatchedMessageCount, queueDispatcher.DispatchedMessageIds.Count); + Assert.AreEqual(expectedDispatchedMessageCount, sortResult.DefaultDispatch.Count()); + Assert.IsNull(sortResult.IsolatedDispatch); } static object[] TestCases = @@ -61,26 +58,24 @@ public async Task It_deduplicates_based_on_message_id_and_address(TransportOpera } }; - static TransportOperation CreateTransportOperations(string messageId, string destination) + [Test] + public void It_sorts_isolated_and_default_dispatch() { - return new TransportOperation(new OutgoingMessage(messageId, new Dictionary(), new byte[0]), new UnicastAddressTag(destination)); - } + var queueAddressTranslator = new QueueAddressTranslator("nservicebus", "dbo", null, null); - class FakeTableBasedQueueDispatcher : IQueueDispatcher - { - public List DispatchedMessageIds = new List(); + var operations = new TransportOperations( + CreateTransportOperations("1", "dest", DispatchConsistency.Default), + CreateTransportOperations("2", "dest", DispatchConsistency.Isolated)); - public Task DispatchAsNonIsolated(List operations, TransportTransaction transportTransaction) - { - DispatchedMessageIds.AddRange(operations.Select(x => x.Message.MessageId)); - return Task.FromResult(0); - } + var sortResult = operations.UnicastTransportOperations.SortAndDeduplicate(queueAddressTranslator); - public Task DispatchAsIsolated(List operations) - { - DispatchedMessageIds.AddRange(operations.Select(x => x.Message.MessageId)); - return Task.FromResult(0); - } + Assert.AreEqual(1, sortResult.DefaultDispatch.Count()); + Assert.AreEqual(1, sortResult.IsolatedDispatch.Count()); + } + + static TransportOperation CreateTransportOperations(string messageId, string destination, DispatchConsistency dispatchConsistency = DispatchConsistency.Default) + { + return new TransportOperation(new OutgoingMessage(messageId, new Dictionary(), new byte[0]), new UnicastAddressTag(destination), dispatchConsistency); } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/ITableBasedQueueOperationsReader.cs b/src/NServiceBus.SqlServer/Queuing/ITableBasedQueueOperationsReader.cs deleted file mode 100644 index 6fb6ca729..000000000 --- a/src/NServiceBus.SqlServer/Queuing/ITableBasedQueueOperationsReader.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Data.SqlClient; - using System.Threading.Tasks; - - interface ITableBasedQueueOperationsReader - { - Func Get(UnicastTransportOperation operation); - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs b/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs deleted file mode 100644 index 19d1f4080..000000000 --- a/src/NServiceBus.SqlServer/Queuing/TableBasedQueueOperationsReader.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Data.SqlClient; - using System.Linq; - using System.Threading.Tasks; - using DelayedDelivery; - using DeliveryConstraints; - using Performance.TimeToBeReceived; - - class TableBasedQueueOperationsReader : ITableBasedQueueOperationsReader - { - public TableBasedQueueOperationsReader(TableBasedQueueCache tableBasedQueueCache, IDelayedMessageStore delayedMessageTable) - { - this.tableBasedQueueCache = tableBasedQueueCache; - this.delayedMessageTable = delayedMessageTable; - } - - public Func Get(UnicastTransportOperation operation) - { - var behavior = GetDueTime(operation); - TryGetConstraint(operation, out DiscardIfNotReceivedBefore discardIfNotReceivedBefore); - if (behavior.Defer) - { - // align with TimeoutManager behavior - if (discardIfNotReceivedBefore != null && discardIfNotReceivedBefore.MaxTime < TimeSpan.MaxValue) - { - throw new Exception("Delayed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to delay messages of this type."); - } - - return (conn, trans) => delayedMessageTable.Store(operation.Message, behavior.DueAfter, behavior.Destination, conn, trans); - } - - var queue = tableBasedQueueCache.Get(operation.Destination); - return (conn, trans) => queue.Send(operation.Message, discardIfNotReceivedBefore?.MaxTime ?? TimeSpan.MaxValue, conn, trans); - } - - static DispatchBehavior GetDueTime(UnicastTransportOperation operation) - { - if (TryGetConstraint(operation, out DoNotDeliverBefore doNotDeliverBefore)) - { - return DispatchBehavior.Deferred(doNotDeliverBefore.At - DateTime.UtcNow, operation.Destination); - } - if (TryGetConstraint(operation, out DelayDeliveryWith delayDeliveryWith)) - { - return DispatchBehavior.Deferred(delayDeliveryWith.Delay, operation.Destination); - } - return DispatchBehavior.Immediately(); - } - - static bool TryGetConstraint(IOutgoingTransportOperation operation, out T constraint) where T : DeliveryConstraint - { - constraint = operation.DeliveryConstraints.OfType().FirstOrDefault(); - return constraint != null; - } - - struct DispatchBehavior - { - public bool Defer; - public TimeSpan DueAfter; - public string Destination; - - public static DispatchBehavior Immediately() - { - return new DispatchBehavior(); - } - - public static DispatchBehavior Deferred(TimeSpan dueAfter, string destination) - { - return new DispatchBehavior - { - DueAfter = dueAfter < TimeSpan.Zero ? TimeSpan.Zero : dueAfter, - Defer = true, - Destination = destination - }; - } - } - - TableBasedQueueCache tableBasedQueueCache; - IDelayedMessageStore delayedMessageTable; - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/IQueueDispatcher.cs b/src/NServiceBus.SqlServer/Sending/IQueueDispatcher.cs deleted file mode 100644 index 9b1368862..000000000 --- a/src/NServiceBus.SqlServer/Sending/IQueueDispatcher.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System.Collections.Generic; - using System.Threading.Tasks; - - interface IQueueDispatcher - { - Task DispatchAsNonIsolated(List operations, TransportTransaction transportTransaction); - - Task DispatchAsIsolated(List operations); - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index a65817d26..2b2554aa8 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -2,40 +2,36 @@ { using System; using System.Collections.Generic; + using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; + using System.Transactions; + using DelayedDelivery; + using DeliveryConstraints; using Extensibility; + using Performance.TimeToBeReceived; using Transport; class MessageDispatcher : IDispatchMessages { - public MessageDispatcher(IQueueDispatcher dispatcher, QueueAddressTranslator addressTranslator, IMulticastToUnicastConverter multicastToUnicastConverter) + public MessageDispatcher(QueueAddressTranslator addressTranslator, IMulticastToUnicastConverter multicastToUnicastConverter, TableBasedQueueCache tableBasedQueueCache, IDelayedMessageStore delayedMessageTable, SqlConnectionFactory connectionFactory) { - this.dispatcher = dispatcher; this.addressTranslator = addressTranslator; this.multicastToUnicastConverter = multicastToUnicastConverter; + this.tableBasedQueueCache = tableBasedQueueCache; + this.delayedMessageTable = delayedMessageTable; + this.connectionFactory = connectionFactory; } // We need to check if we can support cancellation in here as well? public async Task Dispatch(TransportOperations operations, TransportTransaction transportTransaction, ContextBag context) { - await DeduplicateAndDispatch(operations.UnicastTransportOperations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); - await DeduplicateAndDispatch(operations.UnicastTransportOperations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); + var sortedOperations = operations.UnicastTransportOperations + .Concat(await ConvertToUnicastOperations(operations).ConfigureAwait(false)) + .SortAndDeduplicate(addressTranslator); - var multicastOperations = await ConvertToUnicastOperations(operations).ConfigureAwait(false); - await DeduplicateAndDispatch(multicastOperations, dispatcher.DispatchAsIsolated, DispatchConsistency.Isolated).ConfigureAwait(false); - await DeduplicateAndDispatch(multicastOperations, ops => dispatcher.DispatchAsNonIsolated(ops, transportTransaction), DispatchConsistency.Default).ConfigureAwait(false); - } - - Task DeduplicateAndDispatch(IEnumerable transportOperations, Func, Task> dispatchMethod, DispatchConsistency dispatchConsistency) - { - var operationsToDispatch = transportOperations - .Where(o => o.RequiredDispatchConsistency == dispatchConsistency) - .GroupBy(o => new DeduplicationKey(o.Message.MessageId, addressTranslator.Parse(o.Destination).Address)) - .Select(g => g.First()) - .ToList(); - - return dispatchMethod(operationsToDispatch); + await DispatchDefault(sortedOperations, transportTransaction).ConfigureAwait(false); + await DispatchIsolated(sortedOperations).ConfigureAwait(false); } async Task> ConvertToUnicastOperations(TransportOperations operations) @@ -45,50 +41,141 @@ async Task> ConvertToUnicastOperations(Transport return tasks.SelectMany(t => t.Result).ToList(); } - IQueueDispatcher dispatcher; - QueueAddressTranslator addressTranslator; - IMulticastToUnicastConverter multicastToUnicastConverter; - - class DeduplicationKey + async Task DispatchIsolated(SortingResult sortedOperations) { - string messageId; - string destination; + if (sortedOperations.IsolatedDispatch == null) + { + return; + } + +#if NET452 + using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled)) + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + { + await Dispatch(sortedOperations.IsolatedDispatch, connection, null).ConfigureAwait(false); + + scope.Complete(); + } +#else + using (var scope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) + using (var tx = connection.BeginTransaction()) + { + await Dispatch(sortedOperations.IsolatedDispatch, connection, tx).ConfigureAwait(false); + tx.Commit(); + scope.Complete(); + } +#endif - public DeduplicationKey(string messageId, string destination) + } + + async Task DispatchDefault(SortingResult sortedOperations, TransportTransaction transportTransaction) + { + if (sortedOperations.DefaultDispatch == null) { - this.messageId = messageId; - this.destination = destination; + return; } - bool Equals(DeduplicationKey other) + if (InReceiveWithNoTransactionMode(transportTransaction) || InReceiveOnlyTransportTransactionMode(transportTransaction)) { - return string.Equals(messageId, other.messageId) && string.Equals(destination, other.destination); + await DispatchUsingNewConnectionAndTransaction(sortedOperations.DefaultDispatch).ConfigureAwait(false); + return; } - public override bool Equals(object obj) + await DispatchUsingReceiveTransaction(transportTransaction, sortedOperations.DefaultDispatch).ConfigureAwait(false); + } + + + async Task DispatchUsingNewConnectionAndTransaction(IEnumerable operations) + { + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) { - if (ReferenceEquals(null, obj)) + using (var transaction = connection.BeginTransaction()) { - return false; + await Dispatch(operations, connection, transaction).ConfigureAwait(false); + transaction.Commit(); } - if (ReferenceEquals(this, obj)) + } + } + + async Task DispatchUsingReceiveTransaction(TransportTransaction transportTransaction, IEnumerable operations) + { + transportTransaction.TryGet(out SqlConnection sqlTransportConnection); + transportTransaction.TryGet(out SqlTransaction sqlTransportTransaction); + transportTransaction.TryGet(out Transaction ambientTransaction); + + if (ambientTransaction != null) + { + using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) { - return true; + await Dispatch(operations, connection, null).ConfigureAwait(false); } - if (obj.GetType() != this.GetType()) + } + else + { + await Dispatch(operations, sqlTransportConnection, sqlTransportTransaction).ConfigureAwait(false); + } + } + + async Task Dispatch(IEnumerable operations, SqlConnection connection, SqlTransaction transaction) + { + foreach (var operation in operations) + { + await Dispatch(connection, transaction, operation).ConfigureAwait(false); + } + } + + Task Dispatch(SqlConnection connection, SqlTransaction transaction, UnicastTransportOperation operation) + { + TryGetConstraint(operation, out DiscardIfNotReceivedBefore discardIfNotReceivedBefore); + if (TryGetConstraint(operation, out DoNotDeliverBefore doNotDeliverBefore)) + { + if (discardIfNotReceivedBefore != null && discardIfNotReceivedBefore.MaxTime < TimeSpan.MaxValue) { - return false; + throw new Exception("Delayed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to delay messages of this type."); } - return Equals((DeduplicationKey) obj); - } - public override int GetHashCode() + return delayedMessageTable.Store(operation.Message, doNotDeliverBefore.At - DateTime.UtcNow, operation.Destination, connection, transaction); + } + if (TryGetConstraint(operation, out DelayDeliveryWith delayDeliveryWith)) { - unchecked + if (discardIfNotReceivedBefore != null && discardIfNotReceivedBefore.MaxTime < TimeSpan.MaxValue) { - return (messageId.GetHashCode()*397) ^ destination.GetHashCode(); + throw new Exception("Delayed delivery of messages with TimeToBeReceived set is not supported. Remove the TimeToBeReceived attribute to delay messages of this type."); } + + return delayedMessageTable.Store(operation.Message, delayDeliveryWith.Delay, operation.Destination, connection, transaction); } + + var queue = tableBasedQueueCache.Get(operation.Destination); + return queue.Send(operation.Message, discardIfNotReceivedBefore?.MaxTime ?? TimeSpan.MaxValue, connection, transaction); } + + static bool InReceiveWithNoTransactionMode(TransportTransaction transportTransaction) + { + transportTransaction.TryGet(out SqlTransaction nativeTransaction); + transportTransaction.TryGet(out Transaction ambientTransaction); + + return nativeTransaction == null && ambientTransaction == null; + } + + static bool InReceiveOnlyTransportTransactionMode(TransportTransaction transportTransaction) + { + return transportTransaction.TryGet(ProcessWithNativeTransaction.ReceiveOnlyTransactionMode, out bool _); + } + + static bool TryGetConstraint(IOutgoingTransportOperation operation, out T constraint) where T : DeliveryConstraint + { + constraint = operation.DeliveryConstraints.OfType().FirstOrDefault(); + return constraint != null; + } + + TableBasedQueueCache tableBasedQueueCache; + IDelayedMessageStore delayedMessageTable; + SqlConnectionFactory connectionFactory; + QueueAddressTranslator addressTranslator; + IMulticastToUnicastConverter multicastToUnicastConverter; + + } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/OperationSorter.cs b/src/NServiceBus.SqlServer/Sending/OperationSorter.cs new file mode 100644 index 000000000..988fccdc9 --- /dev/null +++ b/src/NServiceBus.SqlServer/Sending/OperationSorter.cs @@ -0,0 +1,93 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System.Collections.Generic; + + struct SortingResult + { + public IEnumerable IsolatedDispatch; + public IEnumerable DefaultDispatch; + + public SortingResult(IEnumerable defaultDispatch, IEnumerable isolatedDispatch) + { + DefaultDispatch = defaultDispatch; + IsolatedDispatch = isolatedDispatch; + } + } + + static class OperationSorter + { + public static SortingResult SortAndDeduplicate(this IEnumerable source, QueueAddressTranslator addressTranslator) + { + Dictionary isolatedDispatch = null; + Dictionary defaultDispatch = null; + + foreach (var operation in source) + { + var destination = addressTranslator.Parse(operation.Destination).Address; + var messageId = operation.Message.MessageId; + var deduplicationKey = new DeduplicationKey(messageId, destination); + + if (operation.RequiredDispatchConsistency == DispatchConsistency.Default) + { + if (defaultDispatch == null) + { + defaultDispatch = new Dictionary(); + } + defaultDispatch[deduplicationKey] = operation; + } + else if (operation.RequiredDispatchConsistency == DispatchConsistency.Isolated) + { + if (isolatedDispatch == null) + { + isolatedDispatch = new Dictionary(); + } + isolatedDispatch[deduplicationKey] = operation; + } + } + + return new SortingResult(defaultDispatch?.Values, isolatedDispatch?.Values); + } + + class DeduplicationKey + { + string messageId; + string destination; + + public DeduplicationKey(string messageId, string destination) + { + this.messageId = messageId; + this.destination = destination; + } + + bool Equals(DeduplicationKey other) + { + return string.Equals(messageId, other.messageId) && string.Equals(destination, other.destination); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != this.GetType()) + { + return false; + } + return Equals((DeduplicationKey)obj); + } + + public override int GetHashCode() + { + unchecked + { + return (messageId.GetHashCode() * 397) ^ destination.GetHashCode(); + } + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/Sending/TableBasedQueueDispatcher.cs b/src/NServiceBus.SqlServer/Sending/TableBasedQueueDispatcher.cs deleted file mode 100644 index 7080ade43..000000000 --- a/src/NServiceBus.SqlServer/Sending/TableBasedQueueDispatcher.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System.Collections.Generic; - using System.Data.SqlClient; - using System.Threading.Tasks; - using System.Transactions; - using Transport; - - class TableBasedQueueDispatcher : IQueueDispatcher - { - public TableBasedQueueDispatcher(SqlConnectionFactory connectionFactory, ITableBasedQueueOperationsReader queueOperationsReader) - { - this.connectionFactory = connectionFactory; - this.queueOperationsReader = queueOperationsReader; - } - - public async Task DispatchAsIsolated(List operations) - { - if (operations.Count == 0) - { - return; - } -#if NET452 - using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, TransactionScopeAsyncFlowOption.Enabled)) - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - { - await Send(operations, connection, null).ConfigureAwait(false); - - scope.Complete(); - } -#else - using (var scope = new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using (var tx = connection.BeginTransaction()) - { - await Send(operations, connection, tx).ConfigureAwait(false); - tx.Commit(); - scope.Complete(); - } -#endif - - } - - public async Task DispatchAsNonIsolated(List operations, TransportTransaction transportTransaction) - { - if (operations.Count == 0) - { - return; - } - - if (InReceiveWithNoTransactionMode(transportTransaction) || InReceiveOnlyTransportTransactionMode(transportTransaction)) - { - await DispatchOperationsWithNewConnectionAndTransaction(operations).ConfigureAwait(false); - return; - } - - await DispatchUsingReceiveTransaction(transportTransaction, operations).ConfigureAwait(false); - } - - - async Task DispatchOperationsWithNewConnectionAndTransaction(List operations) - { - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - { - if (operations.Count == 1) - { - await Send(operations, connection, null).ConfigureAwait(false); - return; - } - - using (var transaction = connection.BeginTransaction()) - { - await Send(operations, connection, transaction).ConfigureAwait(false); - transaction.Commit(); - } - } - } - - async Task DispatchUsingReceiveTransaction(TransportTransaction transportTransaction, List operations) - { - - transportTransaction.TryGet(out SqlConnection sqlTransportConnection); - transportTransaction.TryGet(out SqlTransaction sqlTransportTransaction); - transportTransaction.TryGet(out Transaction ambientTransaction); - - if (ambientTransaction != null) - { - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - { - await Send(operations, connection, null).ConfigureAwait(false); - } - } - else - { - await Send(operations, sqlTransportConnection, sqlTransportTransaction).ConfigureAwait(false); - } - } - - async Task Send(List operations, SqlConnection connection, SqlTransaction transaction) - { - foreach (var operation in operations) - { - var queueOperation = queueOperationsReader.Get(operation); - await queueOperation(connection, transaction).ConfigureAwait(false); - } - } - - static bool InReceiveWithNoTransactionMode(TransportTransaction transportTransaction) - { - transportTransaction.TryGet(out SqlTransaction nativeTransaction); - - transportTransaction.TryGet(out Transaction ambientTransaction); - - return nativeTransaction == null && ambientTransaction == null; - } - - static bool InReceiveOnlyTransportTransactionMode(TransportTransaction transportTransaction) - { - return transportTransaction.TryGet(ProcessWithNativeTransaction.ReceiveOnlyTransactionMode, out bool _); - } - - SqlConnectionFactory connectionFactory; - ITableBasedQueueOperationsReader queueOperationsReader; - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index ef4d25a63..47fd42157 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -241,10 +241,9 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() return new TransportSendInfrastructure( () => { - var queueOperationsReader = new TableBasedQueueOperationsReader(tableBasedQueueCache, delayedMessageStore); var topicManager = CreateTopicManager(); var multicastToUnicastConverter = new TopicBasedMulticastToUnicastConverter(topicManager, subscriptionStore); - var dispatcher = new MessageDispatcher(new TableBasedQueueDispatcher(connectionFactory, queueOperationsReader), addressTranslator, multicastToUnicastConverter); + var dispatcher = new MessageDispatcher(addressTranslator, multicastToUnicastConverter, tableBasedQueueCache, delayedMessageStore, connectionFactory); return dispatcher; }, () => Task.FromResult(StartupCheckResult.Success)); @@ -278,7 +277,6 @@ public override Task Stop() public override TransportSubscriptionInfrastructure ConfigureSubscriptionInfrastructure() { - return new TransportSubscriptionInfrastructure(() => new SubscriptionManager(subscriptionStore, settings.EndpointName(), localAddress())); From 4ca79887fa12fcfc30d620dc86c6dd632380c2ee Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 5 Nov 2019 14:23:17 +0800 Subject: [PATCH 15/31] Allow user to set compat mode --- .../NServiceBus.SqlServer.csproj | 2 +- ...venPubSubCompatibilityModeConfiguration.cs | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/MessageDrivenPubSubCompatibilityModeConfiguration.cs diff --git a/src/NServiceBus.SqlServer/NServiceBus.SqlServer.csproj b/src/NServiceBus.SqlServer/NServiceBus.SqlServer.csproj index bf6905715..3b644a119 100644 --- a/src/NServiceBus.SqlServer/NServiceBus.SqlServer.csproj +++ b/src/NServiceBus.SqlServer/NServiceBus.SqlServer.csproj @@ -19,7 +19,7 @@ - + diff --git a/src/NServiceBus.SqlServer/PubSub/MessageDrivenPubSubCompatibilityModeConfiguration.cs b/src/NServiceBus.SqlServer/PubSub/MessageDrivenPubSubCompatibilityModeConfiguration.cs new file mode 100644 index 000000000..b6e8fcd02 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/MessageDrivenPubSubCompatibilityModeConfiguration.cs @@ -0,0 +1,21 @@ +namespace NServiceBus.Transport.SQLServer +{ + using Configuration.AdvancedExtensibility; + + /// + /// Configuration extensions for Message-Driven Pub-Sub compatibility mode + /// + public static class MessageDrivenPubSubCompatibilityModeConfiguration + { + /// + /// Enables compatibility with endpoints running on message-driven pub-sub + /// + /// The transport to enable pub-sub compatibility on + public static SubscriptionMigrationModeSettings EnableMessageDrivenPubSubCompatibilityMode(this TransportExtensions transportExtensions) + { + var settings = transportExtensions.GetSettings(); + settings.Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + return new SubscriptionMigrationModeSettings(settings); + } + } +} \ No newline at end of file From bf8f3d160e7a8a2641b6746b2a58bce5049d9686 Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 5 Nov 2019 14:28:15 +0800 Subject: [PATCH 16/31] Obsoletes --- .../APIApprovals.Approve.approved.txt | 4 ++ src/NServiceBus.SqlServer/obsoletes.cs | 69 ++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 9ed1ee530..0022fff23 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -22,6 +22,10 @@ namespace NServiceBus.Transport.SQLServer public void ProcessingInterval(System.TimeSpan interval) { } public void TableSuffix(string suffix) { } } + public class static MessageDrivenPubSubCompatibilityModeConfiguration + { + public static NServiceBus.SubscriptionMigrationModeSettings EnableMessageDrivenPubSubCompatibilityMode(this NServiceBus.TransportExtensions transportExtensions) { } + } public class PubSubSettings { public PubSubSettings() { } diff --git a/src/NServiceBus.SqlServer/obsoletes.cs b/src/NServiceBus.SqlServer/obsoletes.cs index 58d56b54b..312aa04d6 100644 --- a/src/NServiceBus.SqlServer/obsoletes.cs +++ b/src/NServiceBus.SqlServer/obsoletes.cs @@ -1,4 +1,69 @@ - -#pragma warning disable 1591 +#pragma warning disable 1591 + +namespace NServiceBus +{ + using System; + using System.Reflection; + using Pipeline; + + public static class MessageDrivenPubSubCompatibility + { + [ObsoleteEx( + Message = @"Subscription authorization has been moved to message-driven pub-sub migration mode. + +var compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode(); +compatMode.SubscriptionAuthorizer(authorizer);", + ReplacementTypeOrMember = "SubscriptionMigrationModeSettings.SubscriptionAuthorizer(transportExtensions, authorizer)", + TreatAsErrorFromVersion = "5.0", + RemoveInVersion = "6.0")] + public static void SubscriptionAuthorizer(this TransportExtensions transportExtensions, Func authorizer) + { + } + + [ObsoleteEx( + Message = "Pub sub can not be disabled in version 5.0 and above. The transport handles pub-sub natively and does not require a separate subscription persistence.", + TreatAsErrorFromVersion = "5.0", + RemoveInVersion = "6.0")] + public static void DisablePublishing(this TransportExtensions transportExtensions) + { + } + + [ObsoleteEx( + Message = @"Publisher registration has been moved to message-driven pub-sub migration mode. + +var compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode(); +compatMode.RegisterPublisher(eventType, publisherEndpoint);", + ReplacementTypeOrMember = "SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, eventType, publisherEndpoint)", + TreatAsErrorFromVersion = "5.0", + RemoveInVersion = "6.0")] + public static void RegisterPublisher(this RoutingSettings routingSettings, Type eventType, string publisherEndpoint) + { + } + + [ObsoleteEx( + Message = @"Publisher registration has been moved to message-driven pub-sub migration mode. + +var compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode(); +compatMode.RegisterPublisher(assembly, publisherEndpoint);", + ReplacementTypeOrMember = "SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, publisherEndpoint)", + TreatAsErrorFromVersion = "5.0", + RemoveInVersion = "6.0")] + public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string publisherEndpoint) + { + } + + [ObsoleteEx( + Message = @"Publisher registration has been moved to message-driven pub-sub migration mode. + +var compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode(); +compatMode.RegisterPublisher(assembly, namespace, publisherEndpoint);", + ReplacementTypeOrMember = "SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, namespace, publisherEndpoint)", + TreatAsErrorFromVersion = "5.0", + RemoveInVersion = "6.0")] + public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string @namespace, string publisherEndpoint) + { + } + } +} #pragma warning restore 1591 \ No newline at end of file From 89f3abab4303901c2fd6edab0cab0276a2f08cbb Mon Sep 17 00:00:00 2001 From: Mike Minutillo Date: Tue, 5 Nov 2019 14:28:57 +0800 Subject: [PATCH 17/31] Obsolete API additions --- .../APIApprovals.Approve.approved.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 0022fff23..9668b9a05 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -4,6 +4,21 @@ [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] namespace NServiceBus { + public class static MessageDrivenPubSubCompatibility + { + [System.ObsoleteAttribute("Pub sub can not be disabled in version 5.0 and above. The transport handles pub-s" + + "ub natively and does not require a separate subscription persistence. Will be tr" + + "eated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + public static void DisablePublishing(this NServiceBus.TransportExtensions transportExtensions) { } + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(eventType, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, eventType, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Type eventType, string publisherEndpoint) { } + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string publisherEndpoint) { } + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, namespace, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, namespace, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string @namespace, string publisherEndpoint) { } + [System.ObsoleteAttribute(@"Subscription authorization has been moved to message-driven pub-sub migration mode. \r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.SubscriptionAuthorizer(authorizer);. Use `SubscriptionMigrationModeSettings.SubscriptionAuthorizer(transportExtensions, authorizer)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + public static void SubscriptionAuthorizer(this NServiceBus.TransportExtensions transportExtensions, System.Func authorizer) { } + } public class SqlServerTransport : NServiceBus.Transport.TransportDefinition { public SqlServerTransport() { } From 8fb4651235768e50dbd74f4086cace383855e57d Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Tue, 5 Nov 2019 12:42:37 +0100 Subject: [PATCH 18/31] Testing the native pub sub migration --- .../ConfigureEndpointSqlServerTransport.cs | 8 +- .../When_migrating_publisher_first.cs | 189 +++++++++++++++++ .../When_migrating_subscriber_first.cs | 193 ++++++++++++++++++ .../TestingInMemoryPersistence.cs | 83 ++++++++ .../Configuration/SettingsKeys.cs | 7 +- .../SqlServerTransportInfrastructure.cs | 13 +- 6 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_publisher_first.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_subscriber_first.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/TestingInMemoryPersistence.cs diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index 6ead0e467..8b7634f3e 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -13,7 +13,7 @@ public class ConfigureEndpointSqlServerTransport : IConfigureEndpointTestExecuti public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings, PublisherMetadata publisherMetadata) { queueBindings = configuration.GetSettings().Get(); - + doNotCleanNativeSubscriptions = settings.TryGet("DoNotCleanNativeSubscriptions", out _); connectionString = Environment.GetEnvironmentVariable("SqlServerTransportConnectionString"); if (string.IsNullOrEmpty(connectionString)) { @@ -51,7 +51,10 @@ public Task Cleanup() foreach (var address in queueAddresses) { TryDeleteTable(conn, address); - TryDeleteTable(conn, new QueueAddress("SubscriptionRouting", address.Schema, address.Catalog)); + if (!doNotCleanNativeSubscriptions) + { + TryDeleteTable(conn, new QueueAddress("SubscriptionRouting", address.Schema, address.Catalog)); + } TryDeleteTable(conn, new QueueAddress(address.Table + ".Delayed", address.Schema, address.Catalog)); } } @@ -77,6 +80,7 @@ static void TryDeleteTable(SqlConnection conn, QueueAddress address) } } + bool doNotCleanNativeSubscriptions; string connectionString; QueueBindings queueBindings; diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_publisher_first.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_publisher_first.cs new file mode 100644 index 000000000..258638114 --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_publisher_first.cs @@ -0,0 +1,189 @@ +namespace NServiceBus.AcceptanceTests.NativePubSub +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using Configuration.AdvancedExtensibility; + using EndpointTemplates; + using Features; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using Conventions = AcceptanceTesting.Customization.Conventions; + + public class When_migrating_publisher_first : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + + [Test] + public async Task Should_not_lose_any_events() + { + var subscriptionStorage = new TestingInMemorySubscriptionStorage(); + + //Before migration begins + var beforeMigration = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + }); + b.When(c => c.SubscribedMessageDriven, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.GetSettings().GetOrCreate().AddOrReplacePublishers("LegacyConfig", new List + { + new PublisherTableEntry(typeof(MyEvent), PublisherAddress.CreateFromEndpointName(PublisherEndpoint)) + }); + }); + b.When(async (session, ctx) => + { + await session.Subscribe(); + }); + }) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(beforeMigration.GotTheEvent); + + //Publisher migrated and in compatibility mode + var publisherMigrated = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + }); + b.When(c => c.EndpointsStarted, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.GetSettings().GetOrCreate().AddOrReplacePublishers("LegacyConfig", new List + { + new PublisherTableEntry(typeof(MyEvent), PublisherAddress.CreateFromEndpointName(PublisherEndpoint)) + }); + }); + b.When(async (session, ctx) => + { + await session.Subscribe(); + }); + }) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(publisherMigrated.GotTheEvent); + + //Subscriber migrated and in compatibility mode + var subscriberMigratedRunSettings = new RunSettings + { + TestExecutionTimeout = TimeSpan.FromSeconds(30) + }; + subscriberMigratedRunSettings.Set("DoNotCleanNativeSubscriptions", true); + var subscriberMigrated = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + }); + b.When(c => c.SubscribedMessageDriven && c.SubscribedNative, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + var compatModeSettings = new SubscriptionMigrationModeSettings(c.GetSettings()); + compatModeSettings.RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }); + b.When(async (session, ctx) => + { + //Subscribes both using native feature and message-driven + await session.Subscribe(); + ctx.SubscribedNative = true; + }); + }) + .Done(c => c.GotTheEvent) + .Run(subscriberMigratedRunSettings); + + Assert.True(subscriberMigrated.GotTheEvent); + + //Compatibility mode disabled in both publisher and subscriber + var compatModeDisabled = await Scenario.Define() + .WithEndpoint(b => + { + b.When(c => c.EndpointsStarted, (session, ctx) => session.Publish(new MyEvent())); + }) + .WithEndpoint() + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(compatModeDisabled.GotTheEvent); + } + + public class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool SubscribedMessageDriven { get; set; } + public bool SubscribedNative { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(Subscriber)))) + { + context.SubscribedMessageDriven = true; + } + }); + }).IncludeType(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + }, + metadata => metadata.RegisterPublisherFor(typeof(Publisher))); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_subscriber_first.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_subscriber_first.cs new file mode 100644 index 000000000..4abc4ff4c --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_migrating_subscriber_first.cs @@ -0,0 +1,193 @@ +namespace NServiceBus.AcceptanceTests.NativePubSub +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Support; + using Configuration.AdvancedExtensibility; + using EndpointTemplates; + using Features; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using Conventions = AcceptanceTesting.Customization.Conventions; + + public class When_migrating_subscriber_first : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(Publisher)); + + [Test] + public async Task Should_not_lose_any_events() + { + var subscriptionStorage = new TestingInMemorySubscriptionStorage(); + + //Before migration begins + var beforeMigration = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + }); + b.When(c => c.SubscribedMessageDriven, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.GetSettings().GetOrCreate().AddOrReplacePublishers("LegacyConfig", new List + { + new PublisherTableEntry(typeof(MyEvent), PublisherAddress.CreateFromEndpointName(PublisherEndpoint)) + }); + }); + b.When(async (session, ctx) => + { + await session.Subscribe(); + }); + }) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(beforeMigration.GotTheEvent); + + //Subscriber migrated and in compatibility mode. + var subscriberMigratedRunSettings = new RunSettings + { + TestExecutionTimeout = TimeSpan.FromSeconds(30) + }; + subscriberMigratedRunSettings.Set("DoNotCleanNativeSubscriptions", true); + var subscriberMigrated = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + }); + b.When(c => c.SubscribedMessageDriven, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + var compatModeSettings = new SubscriptionMigrationModeSettings(c.GetSettings()); + compatModeSettings.RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }); + b.When(async (session, ctx) => + { + //Subscribes both using native feature and message-driven + await session.Subscribe(); + ctx.SubscribedNative = true; + }); + }) + .Done(c => c.GotTheEvent && c.SubscribedNative) //we ensure the subscriber did subscriber with the native mechanism + .Run(subscriberMigratedRunSettings); + + Assert.True(subscriberMigrated.GotTheEvent); + + //Publisher migrated and in compatibility mode + var publisherMigratedRunSettings = new RunSettings + { + TestExecutionTimeout = TimeSpan.FromSeconds(30) + }; + publisherMigratedRunSettings.Set("DoNotCleanNativeSubscriptions", true); + var publisherMigrated = await Scenario.Define() + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.UsePersistence().UseStorage(subscriptionStorage); + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + }); + b.When(c => c.SubscribedMessageDriven && c.SubscribedNative, (session, ctx) => session.Publish(new MyEvent())); + }) + + .WithEndpoint(b => + { + b.CustomConfig(c => + { + c.GetSettings().Set("NServiceBus.Subscriptions.EnableMigrationMode", true); + var compatModeSettings = new SubscriptionMigrationModeSettings(c.GetSettings()); + compatModeSettings.RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + }); + b.When(async (session, ctx) => + { + await session.Subscribe(); + ctx.SubscribedNative = true; + }); + }) + .Done(c => c.GotTheEvent) + .Run(publisherMigratedRunSettings); + + Assert.True(publisherMigrated.GotTheEvent); + + //Compatibility mode disabled in both publisher and subscriber + var compatModeDisabled = await Scenario.Define() + .WithEndpoint(b => + { + b.When(c => c.EndpointsStarted, (session, ctx) => session.Publish(new MyEvent())); + }) + .WithEndpoint() + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(compatModeDisabled.GotTheEvent); + } + + public class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool SubscribedMessageDriven { get; set; } + public bool SubscribedNative { get; set; } + } + + public class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(c => + { + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(Subscriber)))) + { + context.SubscribedMessageDriven = true; + } + }); + }).IncludeType(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.DisableFeature(); + }, + metadata => metadata.RegisterPublisherFor(typeof(Publisher))); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/TestingInMemoryPersistence.cs b/src/NServiceBus.SqlServer.AcceptanceTests/TestingInMemoryPersistence.cs new file mode 100644 index 000000000..67691b7ae --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/TestingInMemoryPersistence.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using NServiceBus; +using NServiceBus.Configuration.AdvancedExtensibility; +using NServiceBus.Extensibility; +using NServiceBus.Features; +using NServiceBus.Persistence; +using NServiceBus.Unicast.Subscriptions; +using NServiceBus.Unicast.Subscriptions.MessageDrivenSubscriptions; + +public class TestingInMemoryPersistence : PersistenceDefinition +{ + internal TestingInMemoryPersistence() + { + Supports(s => + { + s.EnableFeatureByDefault(); + }); + } +} + +public static class InMemoryPersistenceExtensions +{ + public static void UseStorage(this PersistenceExtensions extensions, TestingInMemorySubscriptionStorage storageInstance) + { + extensions.GetSettings().Set("InMemoryPersistence.StorageInstance", storageInstance); + } +} + +public class TestingInMemorySubscriptionPersistence : Feature +{ + internal TestingInMemorySubscriptionPersistence() + { + } + + protected override void Setup(FeatureConfigurationContext context) + { + var storageInstance = context.Settings.GetOrDefault("InMemoryPersistence.StorageInstance"); + context.Container.RegisterSingleton(storageInstance ?? new TestingInMemorySubscriptionStorage()); + } +} + +public class TestingInMemorySubscriptionStorage : ISubscriptionStorage +{ + public Task Subscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + var dict = storage.GetOrAdd(messageType, type => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase)); + + dict.AddOrUpdate(BuildKey(subscriber), _ => subscriber, (_, __) => subscriber); + return Task.FromResult(true); + } + + static string BuildKey(Subscriber subscriber) + { + return $"{subscriber.TransportAddress ?? ""}_{subscriber.Endpoint ?? ""}"; + } + + public Task Unsubscribe(Subscriber subscriber, MessageType messageType, ContextBag context) + { + if (storage.TryGetValue(messageType, out var dict)) + { + dict.TryRemove(BuildKey(subscriber), out var _); + } + return Task.FromResult(true); + } + + public Task> GetSubscriberAddressesForMessage(IEnumerable messageTypes, ContextBag context) + { + var result = new HashSet(); + foreach (var m in messageTypes) + { + if (storage.TryGetValue(m, out var list)) + { + result.UnionWith(list.Values); + } + } + return Task.FromResult((IEnumerable)result); + } + + ConcurrentDictionary> storage = new ConcurrentDictionary>(); +} diff --git a/src/NServiceBus.SqlServer/Configuration/SettingsKeys.cs b/src/NServiceBus.SqlServer/Configuration/SettingsKeys.cs index 8424c84b5..d1db73726 100644 --- a/src/NServiceBus.SqlServer/Configuration/SettingsKeys.cs +++ b/src/NServiceBus.SqlServer/Configuration/SettingsKeys.cs @@ -17,6 +17,11 @@ class SettingsKeys public const string SchemaPropertyKey = "Schema"; public const string CatalogPropertyKey = "Catalog"; - public const string EnableMigrationMode = "NServiceBus.TimeoutManager.EnableMigrationMode"; + public const string TimeoutManagerMigrationMode = "NServiceBus.TimeoutManager.EnableMigrationMode"; + + /// + /// For testing the migration process only + /// + public const string DisableNativePubSub = "SqlServer.DisableNativePubSub"; } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 47fd42157..ab4c42f2d 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -28,6 +28,15 @@ internal SqlServerTransportInfrastructure(string catalog, SettingsHolder setting this.localAddress = localAddress; this.logicalAddress = logicalAddress; + if (settings.HasSetting(SettingsKeys.DisableNativePubSub)) + { + OutboundRoutingPolicy = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Unicast, OutboundRoutingType.Unicast); + } + else + { + OutboundRoutingPolicy = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Multicast, OutboundRoutingType.Unicast); + } + settings.TryGet(SettingsKeys.DefaultSchemaSettingsKey, out string defaultSchemaOverride); var queueSchemaSettings = settings.GetOrDefault(); @@ -68,7 +77,7 @@ void HandleTimeoutManagerCompatibilityMode() delayedDeliverySettings.DisableTimeoutManagerCompatibility(); } - settings.Set(SettingsKeys.EnableMigrationMode, delayedDeliverySettings.EnableMigrationMode); + settings.Set(SettingsKeys.TimeoutManagerMigrationMode, delayedDeliverySettings.EnableMigrationMode); } SqlConnectionFactory CreateConnectionFactory() @@ -93,7 +102,7 @@ public override IEnumerable DeliveryConstraints public override TransportTransactionMode TransactionMode { get; } = TransportTransactionMode.TransactionScope; - public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } = new OutboundRoutingPolicy(OutboundRoutingType.Unicast, OutboundRoutingType.Multicast, OutboundRoutingType.Unicast); + public override OutboundRoutingPolicy OutboundRoutingPolicy { get; } public override TransportReceiveInfrastructure ConfigureReceiveInfrastructure() { From 35cc0f6a19db28ac2b1f20143694999da14df83a Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 6 Nov 2019 10:17:42 +0100 Subject: [PATCH 19/31] Use pure reflection (cached) instead of MMR for event polymorphism --- .../PubSub/MulticastToUnicastConverter.cs | 61 +++++++++++++++++++ .../PubSub/{Topics => }/TopicNames.cs | 0 .../PubSub/Topics/BasicTopicManager.cs | 13 ---- .../PubSub/Topics/ITopicManager.cs | 10 --- .../PubSub/Topics/PolymorphicTopicManager.cs | 23 ------- .../TopicBasedMulticastToUnicastConverter.cs | 35 ----------- .../SqlServerTransportInfrastructure.cs | 15 +---- 7 files changed, 62 insertions(+), 95 deletions(-) create mode 100644 src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs rename src/NServiceBus.SqlServer/PubSub/{Topics => }/TopicNames.cs (100%) delete mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs delete mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs delete mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs delete mode 100644 src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs diff --git a/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs b/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs new file mode 100644 index 000000000..0be435f41 --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs @@ -0,0 +1,61 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + class MulticastToUnicastConverter : IMulticastToUnicastConverter + { + ISubscriptionStore subscriptions; + ConcurrentDictionary messageHierarchyCache = new ConcurrentDictionary(); + + public MulticastToUnicastConverter(ISubscriptionStore subscriptions) + { + this.subscriptions = subscriptions; + } + + public async Task> Convert(MulticastTransportOperation transportOperation) + { + var topics = GetTopicsFor(transportOperation.MessageType); + + var topicDestinations = await Task.WhenAll(topics.Select(subscriptions.GetSubscribersForTopic)) + .ConfigureAwait(false); + + return (from topicDestination in topicDestinations + from destination in topicDestination + select new UnicastTransportOperation( + transportOperation.Message, + destination, + transportOperation.RequiredDispatchConsistency, + transportOperation.DeliveryConstraints + )).ToList(); + } + + + IEnumerable GetTopicsFor(Type messageType) + { + return GetMessageHierarchy(messageType).Select(TopicName.From); + } + + IEnumerable GetMessageHierarchy(Type messageType) + { + return messageHierarchyCache.GetOrAdd(messageType, t => GenerateMessageHierarchy(t).ToArray()); + } + + static IEnumerable GenerateMessageHierarchy(Type messageType) + { + var t = messageType; + while (t != null) + { + yield return t; + t = t.BaseType; + } + foreach (var iface in messageType.GetInterfaces()) + { + yield return iface; + } + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs b/src/NServiceBus.SqlServer/PubSub/TopicNames.cs similarity index 100% rename from src/NServiceBus.SqlServer/PubSub/Topics/TopicNames.cs rename to src/NServiceBus.SqlServer/PubSub/TopicNames.cs diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs deleted file mode 100644 index 688707936..000000000 --- a/src/NServiceBus.SqlServer/PubSub/Topics/BasicTopicManager.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Collections.Generic; - - class BasicTopicManager : ITopicManager - { - public IEnumerable GetTopicsFor(Type messageType) - { - yield return TopicName.From(messageType); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs deleted file mode 100644 index caf792c3c..000000000 --- a/src/NServiceBus.SqlServer/PubSub/Topics/ITopicManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Collections.Generic; - - interface ITopicManager - { - IEnumerable GetTopicsFor(Type messageType); - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs b/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs deleted file mode 100644 index a43580f66..000000000 --- a/src/NServiceBus.SqlServer/PubSub/Topics/PolymorphicTopicManager.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Unicast.Messages; - - class PolymorphicTopicManager : ITopicManager - { - MessageMetadataRegistry messageMetadataRegistry; - - public PolymorphicTopicManager(MessageMetadataRegistry messageMetadataRegistry) - { - this.messageMetadataRegistry = messageMetadataRegistry; - } - - public IEnumerable GetTopicsFor(Type messageType) - { - var messageMetadata = messageMetadataRegistry.GetMessageMetadata(messageType); - return messageMetadata.MessageHierarchy.Select(TopicName.From); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs b/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs deleted file mode 100644 index 0c91c3e6c..000000000 --- a/src/NServiceBus.SqlServer/PubSub/Topics/TopicBasedMulticastToUnicastConverter.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace NServiceBus.Transport.SQLServer -{ - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - - class TopicBasedMulticastToUnicastConverter : IMulticastToUnicastConverter - { - ITopicManager topicManager; - ISubscriptionStore subscriptions; - - public TopicBasedMulticastToUnicastConverter(ITopicManager topicManager, ISubscriptionStore subscriptions) - { - this.topicManager = topicManager; - this.subscriptions = subscriptions; - } - - public async Task> Convert(MulticastTransportOperation transportOperation) - { - var topics = topicManager.GetTopicsFor(transportOperation.MessageType); - - var topicDestinations = await Task.WhenAll(topics.Select(subscriptions.GetSubscribersForTopic)) - .ConfigureAwait(false); - - return (from topicDestination in topicDestinations - from destination in topicDestination - select new UnicastTransportOperation( - transportOperation.Message, - destination, - transportOperation.RequiredDispatchConsistency, - transportOperation.DeliveryConstraints - )).ToList(); - } - } -} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index ab4c42f2d..af84adb2d 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -11,8 +11,6 @@ namespace NServiceBus.Transport.SQLServer using Routing; using Settings; using Transport; - using Unicast.Messages; - /// /// ConfigureReceiveInfrastructure is called first, before features are started @@ -250,24 +248,13 @@ public override TransportSendInfrastructure ConfigureSendInfrastructure() return new TransportSendInfrastructure( () => { - var topicManager = CreateTopicManager(); - var multicastToUnicastConverter = new TopicBasedMulticastToUnicastConverter(topicManager, subscriptionStore); + var multicastToUnicastConverter = new MulticastToUnicastConverter(subscriptionStore); var dispatcher = new MessageDispatcher(addressTranslator, multicastToUnicastConverter, tableBasedQueueCache, delayedMessageStore, connectionFactory); return dispatcher; }, () => Task.FromResult(StartupCheckResult.Success)); } - ITopicManager CreateTopicManager() - { - if (settings.TryGet(out var messageMetadataRegistry)) - { - return new PolymorphicTopicManager(messageMetadataRegistry); - } - - return new BasicTopicManager(); - } - public override Task Start() { foreach (var diagnosticSection in diagnostics) From c9a4030d725532af51c19f5893322d2c25fe4543 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 6 Nov 2019 13:34:41 +0100 Subject: [PATCH 20/31] Update obsoletes and approve API --- GitVersion.yml | 2 +- .../APIApprovals.Approve.approved.txt | 22 +++++++---------- .../SendOptionsExtensions.cs | 23 +----------------- .../SqlServerTransportSettingsExtensions.cs | 14 ----------- src/NServiceBus.SqlServer/obsoletes.cs | 24 +++++++++++++++++++ 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index 8144ee242..2b3463947 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,5 +1,5 @@ assembly-versioning-scheme: Major -next-version: 4.2.1 +next-version: 5.0.0 branches: master: mode: ContinuousDeployment diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 9668b9a05..cf918d62d 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -1,4 +1,4 @@ -[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.InteropServices.ComVisibleAttribute(false)] @@ -7,16 +7,16 @@ namespace NServiceBus public class static MessageDrivenPubSubCompatibility { [System.ObsoleteAttribute("Pub sub can not be disabled in version 5.0 and above. The transport handles pub-s" + - "ub natively and does not require a separate subscription persistence. Will be tr" + - "eated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + "ub natively and does not require a separate subscription persistence. The member" + + " currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void DisablePublishing(this NServiceBus.TransportExtensions transportExtensions) { } - [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(eventType, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, eventType, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(eventType, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, eventType, publisherEndpoint)` instead. The member currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Type eventType, string publisherEndpoint) { } - [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, publisherEndpoint)` instead. The member currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string publisherEndpoint) { } - [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, namespace, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, namespace, publisherEndpoint)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(assembly, namespace, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, assembly, namespace, publisherEndpoint)` instead. The member currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Reflection.Assembly assembly, string @namespace, string publisherEndpoint) { } - [System.ObsoleteAttribute(@"Subscription authorization has been moved to message-driven pub-sub migration mode. \r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.SubscriptionAuthorizer(authorizer);. Use `SubscriptionMigrationModeSettings.SubscriptionAuthorizer(transportExtensions, authorizer)` instead. Will be treated as an error from version 5.0.0. Will be removed in version 6.0.0.", false)] + [System.ObsoleteAttribute(@"Subscription authorization has been moved to message-driven pub-sub migration mode. \r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.SubscriptionAuthorizer(authorizer);. Use `SubscriptionMigrationModeSettings.SubscriptionAuthorizer(transportExtensions, authorizer)` instead. The member currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void SubscriptionAuthorizer(this NServiceBus.TransportExtensions transportExtensions, System.Func authorizer) { } } public class SqlServerTransport : NServiceBus.Transport.TransportDefinition @@ -51,8 +51,8 @@ namespace NServiceBus.Transport.SQLServer public class static SendOptionsExtensions { [System.ObsoleteAttribute("The connection parameter is no longer required. Use `UseCustomSqlTransaction` ins" + - "tead. Will be treated as an error from version 5.0.0. Will be removed in version" + - " 6.0.0.", false)] + "tead. The member currently throws a NotImplementedException. Will be removed in " + + "version 6.0.0.", true)] public static void UseCustomSqlConnectionAndTransaction(this NServiceBus.SendOptions options, System.Data.SqlClient.SqlConnection connection, System.Data.SqlClient.SqlTransaction transaction) { } public static void UseCustomSqlTransaction(this NServiceBus.SendOptions options, System.Data.SqlClient.SqlTransaction transaction) { } } @@ -105,10 +105,6 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; { public static NServiceBus.TransportExtensions CreateMessageBodyComputedColumn(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions DefaultSchema(this NServiceBus.TransportExtensions transportExtensions, string schemaName) { } - [System.ObsoleteAttribute("Multi-instance mode has been deprecated. Use Transport Bridge and/or multi-catalo" + - "g addressing instead. The member currently throws a NotImplementedException. Wil" + - "l be removed in version 5.0.0.", true)] - public static NServiceBus.TransportExtensions EnableLegacyMultiInstanceMode(this NServiceBus.TransportExtensions transportExtensions, System.Func> sqlConnectionFactory) { } public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings NativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.Transport.SQLServer.PubSubSettings PubSub(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions PurgeExpiredMessagesOnStartup(this NServiceBus.TransportExtensions transportExtensions, System.Nullable purgeBatchSize) { } diff --git a/src/NServiceBus.SqlServer/SendOptionsExtensions.cs b/src/NServiceBus.SqlServer/SendOptionsExtensions.cs index 406f3d347..707d19e0c 100644 --- a/src/NServiceBus.SqlServer/SendOptionsExtensions.cs +++ b/src/NServiceBus.SqlServer/SendOptionsExtensions.cs @@ -6,29 +6,8 @@ /// /// Adds transport specific settings to SendOptions /// - public static class SendOptionsExtensions + public static partial class SendOptionsExtensions { - /// - /// Enables providing and instances that will be used by send operations. The same connection and transaction - /// can be used in more than one send operation. - /// - /// The to extend. - /// Open instance to be used by send operations. - /// instance that will be used by any operations performed by the transport. - [ObsoleteEx( - RemoveInVersion = "6.0", - TreatAsErrorFromVersion = "5.0", - ReplacementTypeOrMember = "UseCustomSqlTransaction", - Message = "The connection parameter is no longer required.")] - public static void UseCustomSqlConnectionAndTransaction(this SendOptions options, SqlConnection connection, SqlTransaction transaction) - { - var transportTransaction = new TransportTransaction(); - transportTransaction.Set(connection); - transportTransaction.Set(transaction); - - options.GetExtensions().Set(transportTransaction); - } - /// /// Enables the use of custom SqlTransaction instances for send operations. The same transaction can be used in more than one send operation. /// diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index 93d1f7158..42e375fb7 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -226,20 +226,6 @@ public static TransportExtensions CreateMessageBodyComputedC return transportExtensions; } - /// - /// Enables multi-instance mode. - /// - /// The to extend. - /// Function that returns opened sql connection based on destination queue name. - [ObsoleteEx( - RemoveInVersion = "5.0", - TreatAsErrorFromVersion = "4.0", - Message = "Multi-instance mode has been deprecated. Use Transport Bridge and/or multi-catalog addressing instead.")] - public static TransportExtensions EnableLegacyMultiInstanceMode(this TransportExtensions transportExtensions, Func> sqlConnectionFactory) - { - throw new NotImplementedException(); - } - static ILog Logger = LogManager.GetLogger(); } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/obsoletes.cs b/src/NServiceBus.SqlServer/obsoletes.cs index 312aa04d6..669bf99e4 100644 --- a/src/NServiceBus.SqlServer/obsoletes.cs +++ b/src/NServiceBus.SqlServer/obsoletes.cs @@ -18,6 +18,7 @@ public static class MessageDrivenPubSubCompatibility RemoveInVersion = "6.0")] public static void SubscriptionAuthorizer(this TransportExtensions transportExtensions, Func authorizer) { + throw new NotImplementedException(); } [ObsoleteEx( @@ -26,6 +27,7 @@ public static void SubscriptionAuthorizer(this TransportExtensions transportExtensions) { + throw new NotImplementedException(); } [ObsoleteEx( @@ -38,6 +40,7 @@ public static void DisablePublishing(this TransportExtensions routingSettings, Type eventType, string publisherEndpoint) { + throw new NotImplementedException(); } [ObsoleteEx( @@ -50,6 +53,7 @@ public static void RegisterPublisher(this RoutingSettings ro RemoveInVersion = "6.0")] public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string publisherEndpoint) { + throw new NotImplementedException(); } [ObsoleteEx( @@ -62,6 +66,26 @@ public static void RegisterPublisher(this RoutingSettings ro RemoveInVersion = "6.0")] public static void RegisterPublisher(this RoutingSettings routingSettings, Assembly assembly, string @namespace, string publisherEndpoint) { + throw new NotImplementedException(); + } + } +} + +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Data.SqlClient; + + public static partial class SendOptionsExtensions + { + [ObsoleteEx( + RemoveInVersion = "6.0", + TreatAsErrorFromVersion = "5.0", + ReplacementTypeOrMember = "UseCustomSqlTransaction", + Message = "The connection parameter is no longer required.")] + public static void UseCustomSqlConnectionAndTransaction(this SendOptions options, SqlConnection connection, SqlTransaction transaction) + { + throw new NotImplementedException(); } } } From e10242732d7cc87e7ab8a11a78fdbbd26cd7fe91 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 6 Nov 2019 14:11:53 +0100 Subject: [PATCH 21/31] Rename PubSubSettings --- .../ConfigureEndpointSqlServerTransport.cs | 2 +- ...hen_custom_schema_configured_for_publisher.cs | 4 ++-- .../SqlServerTransportTests.cs | 2 +- .../ConfigureSqlServerTransportInfrastructure.cs | 2 +- .../APIApprovals.Approve.approved.txt | 16 ++++++++-------- .../SqlServerTransportTests.cs | 4 ++-- ...PubSubSettings.cs => SubscriptionSettings.cs} | 2 +- .../SqlServerTransportInfrastructure.cs | 2 +- .../SqlServerTransportSettingsExtensions.cs | 6 +++--- 9 files changed, 20 insertions(+), 20 deletions(-) rename src/NServiceBus.SqlServer/PubSub/{PubSubSettings.cs => SubscriptionSettings.cs} (98%) diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index 8b7634f3e..534934d49 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -22,7 +22,7 @@ public Task Configure(string endpointName, EndpointConfiguration configuration, var transportConfig = configuration.UseTransport(); transportConfig.ConnectionString(connectionString); - transportConfig.PubSub().DisableSubscriptionCache(); + transportConfig.SubscriptionSettings().DisableSubscriptionCache(); #if !NET452 transportConfig.Transactions(TransportTransactionMode.SendsAtomicWithReceive); diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs index 51e06e61a..033bb2d70 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs @@ -33,7 +33,7 @@ public Publisher() { b.UseTransport() .DefaultSchema("sender") - .PubSub().SubscriptionTableName("SubscriptionRouting", "dbo"); + .SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); }); } } @@ -49,7 +49,7 @@ public Subscriber() b.UseTransport() .DefaultSchema("receiver") .UseSchemaForEndpoint(publisherEndpoint, "sender") - .PubSub().SubscriptionTableName("SubscriptionRouting", "dbo"); + .SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); // TODO: Use this for compatibility mode //.Routing().RegisterPublisher( diff --git a/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs b/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs index 5c4b4970d..f41ad0adc 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/SqlServerTransportTests.cs @@ -32,7 +32,7 @@ public void It_reads_catalog_from_open_connection() }; var settings = new SettingsHolder(); settings.Set(SettingsKeys.ConnectionFactoryOverride, factory); - var pubSubSettings = new PubSubSettings(); + var pubSubSettings = new SubscriptionSettings(); pubSubSettings.DisableSubscriptionCache(); settings.Set(pubSubSettings); definition.Initialize(settings, "Invalid-connection-string"); diff --git a/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs index ce00400b6..46acf79f4 100644 --- a/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer.TransportTests/ConfigureSqlServerTransportInfrastructure.cs @@ -26,7 +26,7 @@ public TransportConfigurationResult Configure(SettingsHolder settings, Transport delayedDeliverySettings.TableSuffix("Delayed"); settings.Set(delayedDeliverySettings); - var pubSubSettings = new PubSubSettings(); + var pubSubSettings = new SubscriptionSettings(); pubSubSettings.DisableSubscriptionCache(); settings.Set(pubSubSettings); diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index cf918d62d..674534b05 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -41,13 +41,6 @@ namespace NServiceBus.Transport.SQLServer { public static NServiceBus.SubscriptionMigrationModeSettings EnableMessageDrivenPubSubCompatibilityMode(this NServiceBus.TransportExtensions transportExtensions) { } } - public class PubSubSettings - { - public PubSubSettings() { } - public void CacheSubscriptionInformationFor(System.TimeSpan timeSpan) { } - public void DisableSubscriptionCache() { } - public void SubscriptionTableName(string tableName, string schemaName = null, string catalogName = null) { } - } public class static SendOptionsExtensions { [System.ObsoleteAttribute("The connection parameter is no longer required. Use `UseCustomSqlTransaction` ins" + @@ -106,8 +99,8 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static NServiceBus.TransportExtensions CreateMessageBodyComputedColumn(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions DefaultSchema(this NServiceBus.TransportExtensions transportExtensions, string schemaName) { } public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings NativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } - public static NServiceBus.Transport.SQLServer.PubSubSettings PubSub(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions PurgeExpiredMessagesOnStartup(this NServiceBus.TransportExtensions transportExtensions, System.Nullable purgeBatchSize) { } + public static NServiceBus.Transport.SQLServer.SubscriptionSettings SubscriptionSettings(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions TimeToWaitBeforeTriggeringCircuitBreaker(this NServiceBus.TransportExtensions transportExtensions, System.TimeSpan waitTime) { } public static NServiceBus.TransportExtensions TransactionScopeOptions(this NServiceBus.TransportExtensions transportExtensions, System.Nullable timeout = null, System.Nullable isolationLevel = null) { } public static NServiceBus.TransportExtensions UseCatalogForEndpoint(this NServiceBus.TransportExtensions transportExtensions, string endpointName, string catalog) { } @@ -117,4 +110,11 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static NServiceBus.TransportExtensions UseSchemaForQueue(this NServiceBus.TransportExtensions transportExtensions, string queueName, string schema) { } public static NServiceBus.TransportExtensions WithPeekDelay(this NServiceBus.TransportExtensions transportExtensions, System.Nullable delay = null) { } } + public class SubscriptionSettings + { + public SubscriptionSettings() { } + public void CacheSubscriptionInformationFor(System.TimeSpan timeSpan) { } + public void DisableSubscriptionCache() { } + public void SubscriptionTableName(string tableName, string schemaName = null, string catalogName = null) { } + } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs b/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs index 9af5e5b00..2f6cd19c1 100644 --- a/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs +++ b/src/NServiceBus.SqlServer.UnitTests/SqlServerTransportTests.cs @@ -11,7 +11,7 @@ public class SqlServerTransportTests public void It_rejects_connection_string_without_catalog_property() { var definition = new SqlServerTransport(); - var subscriptionSettings = new PubSubSettings(); + var subscriptionSettings = new SubscriptionSettings(); subscriptionSettings.DisableSubscriptionCache(); var settings = new SettingsHolder(); settings.Set(subscriptionSettings); @@ -29,7 +29,7 @@ public void It_accepts_connection_string_with_catalog_property(string connection { var definition = new SqlServerTransport(); - var subscriptionSettings = new PubSubSettings(); + var subscriptionSettings = new SubscriptionSettings(); subscriptionSettings.DisableSubscriptionCache(); var settings = new SettingsHolder(); settings.Set(subscriptionSettings); diff --git a/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs similarity index 98% rename from src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs rename to src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs index 7187d5913..be313edbc 100644 --- a/src/NServiceBus.SqlServer/PubSub/PubSubSettings.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs @@ -5,7 +5,7 @@ /// /// Configures the native pub/sub behavior /// - public class PubSubSettings + public class SubscriptionSettings { internal SubscriptionTableName SubscriptionTable = new SubscriptionTableName("SubscriptionRouting", null, null); TimeSpan? TimeToCacheSubscriptions; diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index af84adb2d..7db054cff 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -49,7 +49,7 @@ internal SqlServerTransportInfrastructure(string catalog, SettingsHolder setting //Needs to be invoked here and not when configuring the receiving infrastructure because the EnableMigrationMode flag has to be set up before feature component is initialized. HandleTimeoutManagerCompatibilityMode(); - var pubSubSettings = settings.GetOrCreate(); + var pubSubSettings = settings.GetOrCreate(); var subscriptionTableName = pubSubSettings.SubscriptionTable.Qualify(defaultSchemaOverride ?? "dbo", catalog); subscriptionStore = new SubscriptionTable(subscriptionTableName.QuotedQualifiedName, connectionFactory); var timeToCacheSubscriptions = pubSubSettings.GetCacheFor(); diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index 42e375fb7..5d48204a4 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -193,11 +193,11 @@ public static DelayedDeliverySettings NativeDelayedDelivery(this TransportExtens } /// - /// Configures publish/subscribe. + /// Configures publish/subscribe behavior. /// - public static PubSubSettings PubSub(this TransportExtensions transportExtensions) + public static SubscriptionSettings SubscriptionSettings(this TransportExtensions transportExtensions) { - return transportExtensions.GetSettings().GetOrCreate(); + return transportExtensions.GetSettings().GetOrCreate(); } /// From 6185635360c9a433a489b6a9bac4c6a29ef8daeb Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Tue, 12 Nov 2019 11:02:53 +0100 Subject: [PATCH 22/31] Use `WHERE IN` in the subscription query Add multi-schema and multi-catalog acceptance tests for full-native mode and native compat mode Add simple tests for compat mode Clean up TODOs --- .../AcceptanceTests.snk | Bin 0 -> 596 bytes .../ConfigureEndpointSqlServerTransport.cs | 40 ++++---- .../EndpointConfigurationExtensions.cs | 10 ++ ...catalog_configured_for_legacy_publisher.cs | 89 ++++++++++++++++++ ...configured_for_publisher_and_subscriber.cs | 81 ++++++++++++++++ ..._schema_configured_for_legacy_publisher.cs | 85 +++++++++++++++++ ...onfigured_for_publisher_and_subscriber.cs} | 37 ++++---- ...erviceBus.SqlServer.AcceptanceTests.csproj | 2 + .../When_publisher_runs_in_compat_mode.cs | 87 +++++++++++++++++ .../When_subscriber_runs_in_compat_mode.cs | 81 ++++++++++++++++ .../When_dispatching_messages.cs | 1 - .../When_using_ttbr.cs | 1 - .../InternalsVisibleTo.cs | 3 +- ...ionCache.cs => CachedSubscriptionStore.cs} | 34 ++++--- .../PubSub/ISubscriptionStore.cs | 7 +- .../PubSub/MulticastToUnicastConverter.cs | 38 +------- .../PubSub/PolymorphicSubscriptionStore.cs | 59 ++++++++++++ .../PubSub/SubscriptionManager.cs | 4 +- .../PubSub/SubscriptionSettings.cs | 29 ++---- .../PubSub/SubscriptionTable.cs | 21 +++-- .../Queuing/SqlConstants.cs | 2 +- .../SqlServerTransportInfrastructure.cs | 6 +- 22 files changed, 589 insertions(+), 128 deletions(-) create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/AcceptanceTests.snk create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/EndpointConfigurationExtensions.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_legacy_publisher.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_publisher_and_subscriber.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_legacy_publisher.cs rename src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/{When_custom_schema_configured_for_publisher.cs => When_custom_schema_configured_for_publisher_and_subscriber.cs} (54%) create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_publisher_runs_in_compat_mode.cs create mode 100644 src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_subscriber_runs_in_compat_mode.cs rename src/NServiceBus.SqlServer/PubSub/{SubscriptionCache.cs => CachedSubscriptionStore.cs} (54%) create mode 100644 src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/AcceptanceTests.snk b/src/NServiceBus.SqlServer.AcceptanceTests/AcceptanceTests.snk new file mode 100644 index 0000000000000000000000000000000000000000..9e5b4a21e4dbcf6e17a44c6a9074e3419b8d0c2d GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098iqWo!nQg|-?tOFk%_i6M?7=EIB`j%eY}HU zD7-A99pRR{({%I#OOgr=sBh$Z^+9(NQY38H?^M)=RP&!ebE*+baNK=I62sZugZiyI zt5&SVymb@ndKgY#R?%uIOEY*gc*!? zn`l*NDkE1z+b7vCX$YXpv+s^vu^@4-OZQK5F>9fCFz!}Dryuh3tUN)*d+YZPCk zNjjfhq+g|%Irmd86T27)A{N70b{Wk@wTq1PjR_Q$DY>oZ77gpy?!PVT8zMqn0V}Hd z@<}m>6hAFMy~N iB0Rhi#j)qSip6+FHi}RqS;`baZ7+|)cuiZ(MQJhHQxy#W literal 0 HcmV?d00001 diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs index 534934d49..3a508e73b 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/ConfigureEndpointSqlServerTransport.cs @@ -5,16 +5,19 @@ using NServiceBus; using NServiceBus.AcceptanceTesting.Support; using NServiceBus.Configuration.AdvancedExtensibility; +using NServiceBus.Settings; using NServiceBus.Transport; using NServiceBus.Transport.SQLServer; public class ConfigureEndpointSqlServerTransport : IConfigureEndpointTestExecution { - public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings settings, PublisherMetadata publisherMetadata) + public Task Configure(string endpointName, EndpointConfiguration configuration, RunSettings runSettings, PublisherMetadata publisherMetadata) { queueBindings = configuration.GetSettings().Get(); - doNotCleanNativeSubscriptions = settings.TryGet("DoNotCleanNativeSubscriptions", out _); + settings = configuration.GetSettings(); + doNotCleanNativeSubscriptions = runSettings.TryGet("DoNotCleanNativeSubscriptions", out _); connectionString = Environment.GetEnvironmentVariable("SqlServerTransportConnectionString"); + if (string.IsNullOrEmpty(connectionString)) { throw new Exception("The 'SqlServerTransportConnectionString' environment variable is not set."); @@ -27,22 +30,15 @@ public Task Configure(string endpointName, EndpointConfiguration configuration, #if !NET452 transportConfig.Transactions(TransportTransactionMode.SendsAtomicWithReceive); #endif - // TODO: Put this back when we support compatability mode - //var routingConfig = transportConfig.Routing(); - - //foreach (var publisher in publisherMetadata.Publishers) - //{ - // foreach (var eventType in publisher.Events) - // { - // routingConfig.RegisterPublisher(eventType, publisher.PublisherName); - // } - //} - return Task.FromResult(0); } public Task Cleanup() { + var subscriptionSettings = settings.GetOrDefault() ?? new SubscriptionSettings(); + settings.TryGet(SettingsKeys.DefaultSchemaSettingsKey, out string defaultSchemaOverride); + var subscriptionTable = subscriptionSettings.SubscriptionTable.Qualify(defaultSchemaOverride ?? "dbo", "nservicebus"); + using (var conn = new SqlConnection(connectionString)) { conn.Open(); @@ -50,24 +46,25 @@ public Task Cleanup() var queueAddresses = queueBindings.ReceivingAddresses.Select(QueueAddress.Parse).ToList(); foreach (var address in queueAddresses) { - TryDeleteTable(conn, address); - if (!doNotCleanNativeSubscriptions) - { - TryDeleteTable(conn, new QueueAddress("SubscriptionRouting", address.Schema, address.Catalog)); - } - TryDeleteTable(conn, new QueueAddress(address.Table + ".Delayed", address.Schema, address.Catalog)); + TryDeleteTable(conn, address.QualifiedTableName); + TryDeleteTable(conn, new QueueAddress(address.Table + ".Delayed", address.Schema, address.Catalog).QualifiedTableName); + } + + if (!doNotCleanNativeSubscriptions) + { + TryDeleteTable(conn, subscriptionTable.QuotedQualifiedName); } } return Task.FromResult(0); } - static void TryDeleteTable(SqlConnection conn, QueueAddress address) + static void TryDeleteTable(SqlConnection conn, string address) { try { using (var comm = conn.CreateCommand()) { - comm.CommandText = $"IF OBJECT_ID('{address.QualifiedTableName}', 'U') IS NOT NULL DROP TABLE {address.QualifiedTableName}"; + comm.CommandText = $"IF OBJECT_ID('{address}', 'U') IS NOT NULL DROP TABLE {address}"; comm.ExecuteNonQuery(); } } @@ -83,6 +80,7 @@ static void TryDeleteTable(SqlConnection conn, QueueAddress address) bool doNotCleanNativeSubscriptions; string connectionString; QueueBindings queueBindings; + SettingsHolder settings; class QueueAddress { diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/EndpointConfigurationExtensions.cs b/src/NServiceBus.SqlServer.AcceptanceTests/EndpointConfigurationExtensions.cs new file mode 100644 index 000000000..9a0f43a58 --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/EndpointConfigurationExtensions.cs @@ -0,0 +1,10 @@ +using NServiceBus; +using NServiceBus.Configuration.AdvancedExtensibility; + +public static class EndpointConfigurationExtensions +{ + public static TransportExtensions ConfigureSqlServerTransport(this EndpointConfiguration endpointConfiguration) + { + return new TransportExtensions(endpointConfiguration.GetSettings()); + } +} diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_legacy_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_legacy_publisher.cs new file mode 100644 index 000000000..38def54cd --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_legacy_publisher.cs @@ -0,0 +1,89 @@ +namespace NServiceBus.SqlServer.AcceptanceTests.MultiCatalog +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvancedExtensibility; + using NServiceBus.AcceptanceTests.EndpointTemplates; + using NUnit.Framework; + using Transport.SQLServer; + + public class When_custom_catalog_configured_for_legacy_publisher : MultiCatalogAcceptanceTest + { + static string PublisherConnectionString => WithCustomCatalog(GetDefaultConnectionString(), "nservicebus1"); + static string SubscriberConnectionString => WithCustomCatalog(GetDefaultConnectionString(), "nservicebus2"); + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(LegacyPublisher)); + + [Test] + public Task Should_receive_event() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscribed, session => session.Publish(new Event()))) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, s => s.Subscribe(typeof(Event)))) + .Done(c => c.EventReceived) + .Run(); + } + + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + public bool Subscribed { get; set; } + } + + class LegacyPublisher : EndpointConfigurationBuilder + { + public LegacyPublisher() + { + EndpointSetup(c => + { + var transport = c.UseTransport(); + transport.ConnectionString(PublisherConnectionString); + + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo", "nservicebus"); + transport.SubscriptionSettings().DisableSubscriptionCache(); + + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(Subscriber)))) + { + context.Subscribed = true; + } + }); + }); + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(b => + { + var transport = b.UseTransport(); + transport.ConnectionString(SubscriberConnectionString); + + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo", "nservicebus"); + + transport.UseCatalogForEndpoint(PublisherEndpoint, "nservicebus1"); + transport.EnableMessageDrivenPubSubCompatibilityMode().RegisterPublisher(typeof(Event), PublisherEndpoint); + }); + } + + class EventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Event message, IMessageHandlerContext context) + { + Context.EventReceived = true; + return Task.FromResult(0); + } + } + } + + public class Event : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_publisher_and_subscriber.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_publisher_and_subscriber.cs new file mode 100644 index 000000000..bc41b72f7 --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiCatalog/When_custom_catalog_configured_for_publisher_and_subscriber.cs @@ -0,0 +1,81 @@ +namespace NServiceBus.SqlServer.AcceptanceTests.MultiCatalog +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using Features; + using NServiceBus.AcceptanceTests.EndpointTemplates; + using NUnit.Framework; + using Transport.SQLServer; + + public class When_custom_catalog_configured_for_publisher_and_subscriber : MultiCatalogAcceptanceTest + { + static string PublisherConnectionString => WithCustomCatalog(GetDefaultConnectionString(), "nservicebus1"); + static string SubscriberConnectionString => WithCustomCatalog(GetDefaultConnectionString(), "nservicebus2"); + + [Test] + public Task Should_receive_event() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscribed, session => session.Publish(new Event()))) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, async (s, ctx) => + { + await s.Subscribe(typeof(Event)).ConfigureAwait(false); + ctx.Subscribed = true; + })) + .Done(c => c.EventReceived) + .Run(); + } + + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + public bool Subscribed { get; set; } + } + + class Publisher : EndpointConfigurationBuilder + { + public Publisher() + { + EndpointSetup(b => + { + var transport = b.UseTransport(); + transport.ConnectionString(PublisherConnectionString); + + transport.SubscriptionSettings().DisableSubscriptionCache(); + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo", "nservicebus"); + }); + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + var transport = c.UseTransport(); + + transport.ConnectionString(SubscriberConnectionString); + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo", "nservicebus"); + + c.DisableFeature(); + }); + } + + class EventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Event message, IMessageHandlerContext context) + { + Context.EventReceived = true; + return Task.FromResult(0); + } + } + } + + public class Event : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_legacy_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_legacy_publisher.cs new file mode 100644 index 000000000..ccb3f299b --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_legacy_publisher.cs @@ -0,0 +1,85 @@ +namespace NServiceBus.SqlServer.AcceptanceTests.MultiSchema +{ + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvancedExtensibility; + using NServiceBus.AcceptanceTests; + using NServiceBus.AcceptanceTests.EndpointTemplates; + using NUnit.Framework; + using Transport.SQLServer; + + public class When_custom_schema_configured_for_legacy_publisher : NServiceBusAcceptanceTest + { + [Test] + public Task Should_receive_event() + { + return Scenario.Define() + .WithEndpoint(b => b.When(c => c.Subscribed, session => session.Publish(new Event()))) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, s => s.Subscribe(typeof(Event)))) + .Done(c => c.EventReceived) + .Run(); + } + + class Context : ScenarioContext + { + public bool EventReceived { get; set; } + public bool Subscribed { get; set; } + } + + class LegacyPublisher : EndpointConfigurationBuilder + { + public LegacyPublisher() + { + EndpointSetup(c => + { + var transport = c.UseTransport(); + transport.DefaultSchema("sender"); + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + transport.SubscriptionSettings().DisableSubscriptionCache(); + + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(Subscriber)))) + { + context.Subscribed = true; + } + }); + }); + } + } + + class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(b => + { + var publisherEndpoint = Conventions.EndpointNamingConvention(typeof(LegacyPublisher)); + + var transport = b.UseTransport(); + transport.DefaultSchema("receiver").UseSchemaForEndpoint(publisherEndpoint, "sender"); + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + + transport.EnableMessageDrivenPubSubCompatibilityMode().RegisterPublisher(typeof(Event), Conventions.EndpointNamingConvention(typeof(LegacyPublisher))); + }); + } + + class EventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(Event message, IMessageHandlerContext context) + { + Context.EventReceived = true; + return Task.FromResult(0); + } + } + } + + public class Event : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher_and_subscriber.cs similarity index 54% rename from src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs rename to src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher_and_subscriber.cs index 033bb2d70..024d91cce 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher.cs +++ b/src/NServiceBus.SqlServer.AcceptanceTests/MultiSchema/When_custom_schema_configured_for_publisher_and_subscriber.cs @@ -2,20 +2,24 @@ { using System.Threading.Tasks; using AcceptanceTesting; - using AcceptanceTesting.Customization; + using Features; using NServiceBus.AcceptanceTests; using NServiceBus.AcceptanceTests.EndpointTemplates; using NUnit.Framework; using Transport.SQLServer; - public class When_custom_schema_configured_for_publisher : NServiceBusAcceptanceTest + public class When_custom_schema_configured_for_publisher_and_subscriber : NServiceBusAcceptanceTest { [Test] public Task Should_receive_event() { return Scenario.Define() - .WithEndpoint(b => b.When(c => c.EndpointsStarted, session => session.Publish(new Event()))) - .WithEndpoint() + .WithEndpoint(b => b.When(c => c.Subscribed, session => session.Publish(new Event()))) + .WithEndpoint(b => b.When(c => c.EndpointsStarted, async (s, ctx) => + { + await s.Subscribe(typeof(Event)).ConfigureAwait(false); + ctx.Subscribed = true; + })) .Done(c => c.EventReceived) .Run(); } @@ -23,6 +27,7 @@ public Task Should_receive_event() class Context : ScenarioContext { public bool EventReceived { get; set; } + public bool Subscribed { get; set; } } class Publisher : EndpointConfigurationBuilder @@ -31,9 +36,11 @@ public Publisher() { EndpointSetup(b => { - b.UseTransport() - .DefaultSchema("sender") - .SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + var transport = b.UseTransport(); + transport.DefaultSchema("sender"); + + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + transport.SubscriptionSettings().DisableSubscriptionCache(); }); } } @@ -42,19 +49,13 @@ class Subscriber : EndpointConfigurationBuilder { public Subscriber() { - EndpointSetup(b => + EndpointSetup(c => { - var publisherEndpoint = Conventions.EndpointNamingConvention(typeof(Publisher)); - - b.UseTransport() - .DefaultSchema("receiver") - .UseSchemaForEndpoint(publisherEndpoint, "sender") - .SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + var transport = c.UseTransport(); + transport.DefaultSchema("receiver"); - // TODO: Use this for compatibility mode - //.Routing().RegisterPublisher( - // eventType: typeof(Event), - // publisherEndpoint: publisherEndpoint); + transport.SubscriptionSettings().SubscriptionTableName("SubscriptionRouting", "dbo"); + c.DisableFeature(); }); } diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NServiceBus.SqlServer.AcceptanceTests.csproj b/src/NServiceBus.SqlServer.AcceptanceTests/NServiceBus.SqlServer.AcceptanceTests.csproj index ec43dd976..dbf0566ab 100644 --- a/src/NServiceBus.SqlServer.AcceptanceTests/NServiceBus.SqlServer.AcceptanceTests.csproj +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NServiceBus.SqlServer.AcceptanceTests.csproj @@ -3,6 +3,8 @@ net452;netcoreapp2.1 true + true + AcceptanceTests.snk diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_publisher_runs_in_compat_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_publisher_runs_in_compat_mode.cs new file mode 100644 index 000000000..c262ba83e --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_publisher_runs_in_compat_mode.cs @@ -0,0 +1,87 @@ +namespace NServiceBus.AcceptanceTests.NativePubSub +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvancedExtensibility; + using EndpointTemplates; + using Features; + using NServiceBus.Routing.MessageDrivenSubscriptions; + using NUnit.Framework; + using Transport.SQLServer; + + public class When_publisher_runs_in_compat_mode : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(MigratedPublisher)); + + [Test] + public async Task Legacy_subscriber_can_subscribe() + { + var publisherMigrated = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscribedMessageDriven, (session, ctx) => session.Publish(new MyEvent()))) + .WithEndpoint(b => b.When((session, ctx) => session.Subscribe())) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(publisherMigrated.GotTheEvent); + } + + public class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool SubscribedMessageDriven { get; set; } + } + + public class MigratedPublisher : EndpointConfigurationBuilder + { + public MigratedPublisher() + { + EndpointSetup(c => + { + c.ConfigureSqlServerTransport().EnableMessageDrivenPubSubCompatibilityMode(); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(Subscriber)))) + { + context.SubscribedMessageDriven = true; + } + }); + }).IncludeType(); + } + } + + public class Subscriber : EndpointConfigurationBuilder + { + public Subscriber() + { + EndpointSetup(c => + { + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + //SqlServerTransport no longer implements message-driven pub sub interface so we need to configure Publishers "manually" + c.GetSettings().GetOrCreate().AddOrReplacePublishers("LegacyConfig", new List + { + new PublisherTableEntry(typeof(MyEvent), PublisherAddress.CreateFromEndpointName(PublisherEndpoint)) + }); + c.DisableFeature(); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_subscriber_runs_in_compat_mode.cs b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_subscriber_runs_in_compat_mode.cs new file mode 100644 index 000000000..86954810f --- /dev/null +++ b/src/NServiceBus.SqlServer.AcceptanceTests/NativePubSub/When_subscriber_runs_in_compat_mode.cs @@ -0,0 +1,81 @@ +namespace NServiceBus.AcceptanceTests.NativePubSub +{ + using System; + using System.Threading.Tasks; + using AcceptanceTesting; + using AcceptanceTesting.Customization; + using Configuration.AdvancedExtensibility; + using EndpointTemplates; + using Features; + using NUnit.Framework; + using Transport.SQLServer; + + public class When_subscriber_runs_in_compat_mode : NServiceBusAcceptanceTest + { + static string PublisherEndpoint => Conventions.EndpointNamingConvention(typeof(LegacyPublisher)); + + [Test] + public async Task It_can_subscribe_for_event_published_by_legacy_publisher() + { + var publisherMigrated = await Scenario.Define() + .WithEndpoint(b => b.When(c => c.SubscribedMessageDriven, (session, ctx) => session.Publish(new MyEvent()))) + .WithEndpoint(b => b.When((session, ctx) => session.Subscribe())) + .Done(c => c.GotTheEvent) + .Run(TimeSpan.FromSeconds(30)); + + Assert.True(publisherMigrated.GotTheEvent); + } + + public class Context : ScenarioContext + { + public bool GotTheEvent { get; set; } + public bool SubscribedMessageDriven { get; set; } + } + + public class LegacyPublisher : EndpointConfigurationBuilder + { + public LegacyPublisher() + { + EndpointSetup(c => + { + c.GetSettings().Set("SqlServer.DisableNativePubSub", true); + c.OnEndpointSubscribed((s, context) => + { + if (s.SubscriberEndpoint.Contains(Conventions.EndpointNamingConvention(typeof(MigratedSubscriber)))) + { + context.SubscribedMessageDriven = true; + } + }); + }).IncludeType(); + } + } + + public class MigratedSubscriber : EndpointConfigurationBuilder + { + public MigratedSubscriber() + { + EndpointSetup(c => + { + var compatMode = c.ConfigureSqlServerTransport().EnableMessageDrivenPubSubCompatibilityMode(); + compatMode.RegisterPublisher(typeof(MyEvent), PublisherEndpoint); + c.DisableFeature(); + }); + } + + public class MyEventHandler : IHandleMessages + { + public Context Context { get; set; } + + public Task Handle(MyEvent @event, IMessageHandlerContext context) + { + Context.GotTheEvent = true; + return Task.FromResult(0); + } + } + } + + public class MyEvent : IEvent + { + } + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs index f3562e345..dd3584742 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_dispatching_messages.cs @@ -137,7 +137,6 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressTranslato SqlConnectionFactory sqlConnectionFactory; - // TODO: Figure out if this is appropriate in this test class NoOpMulticastToUnicastConverter : IMulticastToUnicastConverter { public Task> Convert(MulticastTransportOperation transportOperation) diff --git a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs index 1d4a974d3..f9ea4c44b 100644 --- a/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs +++ b/src/NServiceBus.SqlServer.IntegrationTests/When_using_ttbr.cs @@ -163,7 +163,6 @@ static Task CreateOutputQueueIfNecessary(QueueAddressTranslator addressParser, S const string validAddress = "TTBRTests"; - // TODO: Figure out if this is appropriate in this test class NoOpMulticastToUnicastConverter : IMulticastToUnicastConverter { public Task> Convert(MulticastTransportOperation transportOperation) diff --git a/src/NServiceBus.SqlServer/InternalsVisibleTo.cs b/src/NServiceBus.SqlServer/InternalsVisibleTo.cs index ff2f11f6c..7b8d4a6e2 100644 --- a/src/NServiceBus.SqlServer/InternalsVisibleTo.cs +++ b/src/NServiceBus.SqlServer/InternalsVisibleTo.cs @@ -2,4 +2,5 @@ [assembly: InternalsVisibleTo("NServiceBus.SqlServer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: InternalsVisibleTo("NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] -[assembly: InternalsVisibleTo("NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] \ No newline at end of file +[assembly: InternalsVisibleTo("NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] +[assembly: InternalsVisibleTo("NServiceBus.SqlServer.AcceptanceTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d5a2fc697d5277272662d3154a752010b3de6d598204c10c4b09ebb28b469640efcf04978e95a15f4e0461f02316c96b349083a2e2a4f07fe7dfb713b99189b634473c73c1387149a37dbc836028bc2ca21de196bbd374f4024b920a0da86fe47bf541771352246cd8ef54d48654f39f4073aa114b70dc7d4712c3d9dd83faad")] \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs b/src/NServiceBus.SqlServer/PubSub/CachedSubscriptionStore.cs similarity index 54% rename from src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs rename to src/NServiceBus.SqlServer/PubSub/CachedSubscriptionStore.cs index b1a36c2db..304bf6392 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionCache.cs +++ b/src/NServiceBus.SqlServer/PubSub/CachedSubscriptionStore.cs @@ -5,48 +5,54 @@ namespace NServiceBus.Transport.SQLServer using System.Collections.Generic; using System.Threading.Tasks; - class SubscriptionCache : ISubscriptionStore + + class CachedSubscriptionStore : ISubscriptionStore { - public SubscriptionCache(ISubscriptionStore inner, TimeSpan cacheFor) + public CachedSubscriptionStore(ISubscriptionStore inner, TimeSpan cacheFor) { this.inner = inner; this.cacheFor = cacheFor; } - public Task> GetSubscribersForTopic(string topic) + public Task> GetSubscribers(Type eventType) { - var cacheItem = Cache.GetOrAdd(topic, + var cacheItem = Cache.GetOrAdd(CacheKey(eventType), _ => new CacheItem { Stored = DateTime.UtcNow, - Subscribers = inner.GetSubscribersForTopic(topic) + Subscribers = inner.GetSubscribers(eventType) }); var age = DateTime.UtcNow - cacheItem.Stored; if (age >= cacheFor) { - cacheItem.Subscribers = inner.GetSubscribersForTopic(topic); + cacheItem.Subscribers = inner.GetSubscribers(eventType); cacheItem.Stored = DateTime.UtcNow; } return cacheItem.Subscribers; } - public async Task Subscribe(string endpointName, string endpointAddress, string topic) + public async Task Subscribe(string endpointName, string endpointAddress, Type eventType) + { + await inner.Subscribe(endpointName, endpointAddress, eventType).ConfigureAwait(false); + ClearForMessageType(CacheKey(eventType)); + } + + public async Task Unsubscribe(string endpointName, Type eventType) { - await inner.Subscribe(endpointName, endpointAddress, topic).ConfigureAwait(false); - ClearForMessageType(topic); + await inner.Unsubscribe(endpointName, eventType).ConfigureAwait(false); + ClearForMessageType(CacheKey(eventType)); } - public async Task Unsubscribe(string endpointName, string topic) + void ClearForMessageType(string topic) { - await inner.Unsubscribe(endpointName, topic).ConfigureAwait(false); - ClearForMessageType(topic); + Cache.TryRemove(topic, out _); } - void ClearForMessageType(string topice) + static string CacheKey(Type eventType) { - Cache.TryRemove(topice, out _); + return eventType.FullName; } TimeSpan cacheFor; diff --git a/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs b/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs index aaca47310..7106494e3 100644 --- a/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs +++ b/src/NServiceBus.SqlServer/PubSub/ISubscriptionStore.cs @@ -1,12 +1,13 @@ namespace NServiceBus.Transport.SQLServer { + using System; using System.Collections.Generic; using System.Threading.Tasks; interface ISubscriptionStore { - Task> GetSubscribersForTopic(string topic); - Task Subscribe(string endpointName, string endpointAddress, string topic); - Task Unsubscribe(string endpointName, string topic); + Task> GetSubscribers(Type eventType); + Task Subscribe(string endpointName, string endpointAddress, Type eventType); + Task Unsubscribe(string endpointName, Type eventType); } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs b/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs index 0be435f41..9d14e2851 100644 --- a/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs +++ b/src/NServiceBus.SqlServer/PubSub/MulticastToUnicastConverter.cs @@ -1,7 +1,5 @@ namespace NServiceBus.Transport.SQLServer { - using System; - using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -9,7 +7,6 @@ namespace NServiceBus.Transport.SQLServer class MulticastToUnicastConverter : IMulticastToUnicastConverter { ISubscriptionStore subscriptions; - ConcurrentDictionary messageHierarchyCache = new ConcurrentDictionary(); public MulticastToUnicastConverter(ISubscriptionStore subscriptions) { @@ -18,44 +15,15 @@ public MulticastToUnicastConverter(ISubscriptionStore subscriptions) public async Task> Convert(MulticastTransportOperation transportOperation) { - var topics = GetTopicsFor(transportOperation.MessageType); + var subscribers = await subscriptions.GetSubscribers(transportOperation.MessageType).ConfigureAwait(false); - var topicDestinations = await Task.WhenAll(topics.Select(subscriptions.GetSubscribersForTopic)) - .ConfigureAwait(false); - - return (from topicDestination in topicDestinations - from destination in topicDestination + return (from subscriber in subscribers select new UnicastTransportOperation( transportOperation.Message, - destination, + subscriber, transportOperation.RequiredDispatchConsistency, transportOperation.DeliveryConstraints )).ToList(); } - - - IEnumerable GetTopicsFor(Type messageType) - { - return GetMessageHierarchy(messageType).Select(TopicName.From); - } - - IEnumerable GetMessageHierarchy(Type messageType) - { - return messageHierarchyCache.GetOrAdd(messageType, t => GenerateMessageHierarchy(t).ToArray()); - } - - static IEnumerable GenerateMessageHierarchy(Type messageType) - { - var t = messageType; - while (t != null) - { - yield return t; - t = t.BaseType; - } - foreach (var iface in messageType.GetInterfaces()) - { - yield return iface; - } - } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs b/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs new file mode 100644 index 000000000..e9e2528ee --- /dev/null +++ b/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs @@ -0,0 +1,59 @@ +namespace NServiceBus.Transport.SQLServer +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + class PolymorphicSubscriptionStore : ISubscriptionStore + { + public PolymorphicSubscriptionStore(SubscriptionTable subscriptionTable) + { + this.subscriptionTable = subscriptionTable; + } + + public Task> GetSubscribers(Type eventType) + { + var topics = GetTopicsFor(eventType); + return subscriptionTable.GetSubscribers(topics.ToArray()); + } + + public Task Subscribe(string endpointName, string endpointAddress, Type eventType) + { + return subscriptionTable.Subscribe(endpointName, endpointAddress, TopicName.From(eventType)); + } + + public Task Unsubscribe(string endpointName, Type eventType) + { + return subscriptionTable.Unsubscribe(endpointName, TopicName.From(eventType)); + } + + IEnumerable GetTopicsFor(Type messageType) + { + return GetMessageHierarchy(messageType).Select(TopicName.From); + } + + IEnumerable GetMessageHierarchy(Type messageType) + { + return messageHierarchyCache.GetOrAdd(messageType, t => GenerateMessageHierarchy(t).ToArray()); + } + + static IEnumerable GenerateMessageHierarchy(Type messageType) + { + var t = messageType; + while (t != null) + { + yield return t; + t = t.BaseType; + } + foreach (var iface in messageType.GetInterfaces()) + { + yield return iface; + } + } + + ConcurrentDictionary messageHierarchyCache = new ConcurrentDictionary(); + SubscriptionTable subscriptionTable; + } +} \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs index 0f7bb3d5d..4a440c65b 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionManager.cs @@ -15,12 +15,12 @@ public SubscriptionManager(ISubscriptionStore subscriptionStore, string endpoint public Task Subscribe(Type eventType, ContextBag context) { - return subscriptionStore.Subscribe(endpointName, localAddress, TopicName.From(eventType)); + return subscriptionStore.Subscribe(endpointName, localAddress, eventType); } public Task Unsubscribe(Type eventType, ContextBag context) { - return subscriptionStore.Unsubscribe(endpointName, TopicName.From(eventType)); + return subscriptionStore.Unsubscribe(endpointName, eventType); } ISubscriptionStore subscriptionStore; diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs index be313edbc..d10b0a495 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionSettings.cs @@ -8,7 +8,12 @@ public class SubscriptionSettings { internal SubscriptionTableName SubscriptionTable = new SubscriptionTableName("SubscriptionRouting", null, null); - TimeSpan? TimeToCacheSubscriptions; + /// + /// Default to 5 seconds caching. If a system is under load that prevent doing an extra roundtrip for each Publish operation. If + /// a system is not under load, doing an extra roundtrip every 5 seconds is not a problem and 5 seconds is small enough value that + /// people accepts as we always say that subscription operation is not instantaneous. + /// + internal TimeSpan? TimeToCacheSubscriptions = TimeSpan.FromSeconds(5); /// /// Overrides the default name for the subscription table. All endpoints in a given system need to agree on that name in order for them to be able @@ -36,27 +41,7 @@ public void CacheSubscriptionInformationFor(TimeSpan timeSpan) /// public void DisableSubscriptionCache() { - TimeToCacheSubscriptions = TimeSpan.Zero; - } - - internal TimeSpan? GetCacheFor() - { - if (TimeToCacheSubscriptions == TimeSpan.Zero) - { - return null; - } - - if (TimeToCacheSubscriptions.HasValue) - { - return TimeToCacheSubscriptions.Value; - } - throw new Exception(@"Subscription caching is a required settings. Access this setting using the following: -var transport = endpointConfiguration.UseTransport(); -var pubsub = transport.PuSub(); -subscriptions.CacheSubscriptionInformationFor(TimeSpan.FromMinutes(1)); -// OR -subscriptions.DisableSubscriptionCache(); -"); + TimeToCacheSubscriptions = null; } } } \ No newline at end of file diff --git a/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs index 24f061b1a..e3f91f6fa 100644 --- a/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs +++ b/src/NServiceBus.SqlServer/PubSub/SubscriptionTable.cs @@ -2,24 +2,24 @@ namespace NServiceBus.Transport.SQLServer { using System.Collections.Generic; using System.Data; + using System.Linq; using System.Threading.Tasks; using System.Transactions; - class SubscriptionTable : ISubscriptionStore + class SubscriptionTable { + string qualifiedTableName; SqlConnectionFactory connectionFactory; string subscribeCommand; string unsubscribeCommand; - string getSubscribersCommand; public SubscriptionTable(string qualifiedTableName, SqlConnectionFactory connectionFactory) { + this.qualifiedTableName = qualifiedTableName; this.connectionFactory = connectionFactory; - // TODO: Be able to change the subscriptions table name and schema #pragma warning disable 618 subscribeCommand = string.Format(SqlConstants.SubscribeText, qualifiedTableName); unsubscribeCommand = string.Format(SqlConstants.UnsubscribeText, qualifiedTableName); - getSubscribersCommand = string.Format(SqlConstants.GetSubscribersText, qualifiedTableName); #pragma warning restore 618 } @@ -56,16 +56,25 @@ public async Task Unsubscribe(string endpointName, string topic) } } - public async Task> GetSubscribersForTopic(string topic) + public async Task> GetSubscribers(string[] topics) { var results = new List(); + + var argumentsList = string.Join(", ", Enumerable.Range(0, topics.Length).Select(i => $"@Topic_{i}")); +#pragma warning disable 618 + var getSubscribersCommand = string.Format(SqlConstants.GetSubscribersText, qualifiedTableName, argumentsList); +#pragma warning restore 618 + using (new TransactionScope(TransactionScopeOption.Suppress, TransactionScopeAsyncFlowOption.Enabled)) { using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) using (var command = connection.CreateCommand()) { command.CommandText = getSubscribersCommand; - command.Parameters.Add("Topic", SqlDbType.VarChar).Value = topic; + for (var i = 0; i < topics.Length; i++) + { + command.Parameters.Add($"Topic_{i}", SqlDbType.VarChar).Value = topics[i]; + } using (var reader = await command.ExecuteReaderAsync().ConfigureAwait(false)) { diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index f37ce2377..443b72913 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -312,7 +312,7 @@ WHEN NOT MATCHED THEN public static readonly string GetSubscribersText = @" SELECT DISTINCT QueueAddress FROM {0} -WHERE Topic = @Topic +WHERE Topic IN ({1}) "; public static readonly string UnsubscribeText = @" diff --git a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs index 7db054cff..bde762d9c 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportInfrastructure.cs @@ -51,11 +51,11 @@ internal SqlServerTransportInfrastructure(string catalog, SettingsHolder setting var pubSubSettings = settings.GetOrCreate(); var subscriptionTableName = pubSubSettings.SubscriptionTable.Qualify(defaultSchemaOverride ?? "dbo", catalog); - subscriptionStore = new SubscriptionTable(subscriptionTableName.QuotedQualifiedName, connectionFactory); - var timeToCacheSubscriptions = pubSubSettings.GetCacheFor(); + subscriptionStore = new PolymorphicSubscriptionStore(new SubscriptionTable(subscriptionTableName.QuotedQualifiedName, connectionFactory)); + var timeToCacheSubscriptions = pubSubSettings.TimeToCacheSubscriptions; if (timeToCacheSubscriptions.HasValue) { - subscriptionStore = new SubscriptionCache(subscriptionStore, timeToCacheSubscriptions.Value); + subscriptionStore = new CachedSubscriptionStore(subscriptionStore, timeToCacheSubscriptions.Value); } var subscriptionTableCreator = new SubscriptionTableCreator(subscriptionTableName, connectionFactory); settings.Set(subscriptionTableCreator); From f0cd631d4f002be6a2702e0365d095426d11fa86 Mon Sep 17 00:00:00 2001 From: Szymon Pobiega Date: Wed, 13 Nov 2019 12:15:26 +0100 Subject: [PATCH 23/31] Apply suggestions from code review Co-Authored-By: Mike Minutillo --- .../DelayedDelivery/DelayedMessageTable.cs | 4 ++-- src/NServiceBus.SqlServer/obsoletes.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs index 960efc838..5aeaa608e 100644 --- a/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs +++ b/src/NServiceBus.SqlServer/DelayedDelivery/DelayedMessageTable.cs @@ -15,7 +15,7 @@ class SendOnlyDelayedMessageStore : IDelayedMessageStore { public Task Store(OutgoingMessage message, TimeSpan dueAfter, string destination, SqlConnection connection, SqlTransaction transaction) { - throw new Exception("Delayed delivery is only supported for non send-only endpoints."); + throw new Exception("Delayed delivery is not supported for send-only endpoints."); } } @@ -51,4 +51,4 @@ public async Task MoveDueMessages(int batchSize, SqlConnection connection, SqlTr string storeCommand; string moveDueCommand; } -} \ No newline at end of file +} diff --git a/src/NServiceBus.SqlServer/obsoletes.cs b/src/NServiceBus.SqlServer/obsoletes.cs index 669bf99e4..aa7757eef 100644 --- a/src/NServiceBus.SqlServer/obsoletes.cs +++ b/src/NServiceBus.SqlServer/obsoletes.cs @@ -22,7 +22,7 @@ public static void SubscriptionAuthorizer(this TransportExtensions transportExtensions) @@ -90,4 +90,4 @@ public static void UseCustomSqlConnectionAndTransaction(this SendOptions options } } -#pragma warning restore 1591 \ No newline at end of file +#pragma warning restore 1591 From 9c399e4b4954a9884ad8d5480d76502c5ff07f27 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 11:09:23 +0100 Subject: [PATCH 24/31] Obsolete UseNativeDelayedDelivery --- .../APIApprovals.Approve.approved.txt | 5 +++++ .../SqlServerTransportSettingsExtensions.cs | 2 +- src/NServiceBus.SqlServer/obsoletes.cs | 22 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 674534b05..64445abae 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -1,3 +1,4 @@ +[assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.AcceptanceTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d5a2fc697d5277272662d3154a752010b3de6d598204c10c4b09ebb28b469640efcf04978e95a15f4e0461f02316c96b349083a2e2a4f07fe7dfb713b99189b634473c73c1387149a37dbc836028bc2ca21de196bbd374f4024b920a0da86fe47bf541771352246cd8ef54d48654f39f4073aa114b70dc7d4712c3d9dd83faad")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.TransportTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] [assembly: System.Runtime.CompilerServices.InternalsVisibleToAttribute(@"NServiceBus.SqlServer.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100dde965e6172e019ac82c2639ffe494dd2e7dd16347c34762a05732b492e110f2e4e2e1b5ef2d85c848ccfb671ee20a47c8d1376276708dc30a90ff1121b647ba3b7259a6bc383b2034938ef0e275b58b920375ac605076178123693c6c4f1331661a62eba28c249386855637780e3ff5f23a6d854700eaa6803ef48907513b92")] @@ -106,6 +107,10 @@ EXEC sp_releaseapplock @Resource = '{0}_lock'"; public static NServiceBus.TransportExtensions UseCatalogForEndpoint(this NServiceBus.TransportExtensions transportExtensions, string endpointName, string catalog) { } public static NServiceBus.TransportExtensions UseCatalogForQueue(this NServiceBus.TransportExtensions transportExtensions, string queueName, string catalog) { } public static NServiceBus.TransportExtensions UseCustomSqlConnectionFactory(this NServiceBus.TransportExtensions transportExtensions, System.Func> sqlConnectionFactory) { } + [System.ObsoleteAttribute("Starting from version 5 native delayed delivery is always enabled. It can be conf" + + "igured via NativeDelayedDelivery. Use `NativeDelayedDelivery` instead. The membe" + + "r currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] + public static NServiceBus.Transport.SQLServer.DelayedDeliverySettings UseNativeDelayedDelivery(this NServiceBus.TransportExtensions transportExtensions) { } public static NServiceBus.TransportExtensions UseSchemaForEndpoint(this NServiceBus.TransportExtensions transportExtensions, string endpointName, string schema) { } public static NServiceBus.TransportExtensions UseSchemaForQueue(this NServiceBus.TransportExtensions transportExtensions, string queueName, string schema) { } public static NServiceBus.TransportExtensions WithPeekDelay(this NServiceBus.TransportExtensions transportExtensions, System.Nullable delay = null) { } diff --git a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs index 5d48204a4..e3bc90bb8 100644 --- a/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs +++ b/src/NServiceBus.SqlServer/SqlServerTransportSettingsExtensions.cs @@ -10,7 +10,7 @@ /// /// Adds extra configuration for the Sql Server transport. /// - public static class SqlServerTransportSettingsExtensions + public static partial class SqlServerTransportSettingsExtensions { /// /// Sets a default schema for both input and output queues diff --git a/src/NServiceBus.SqlServer/obsoletes.cs b/src/NServiceBus.SqlServer/obsoletes.cs index aa7757eef..f82f3c046 100644 --- a/src/NServiceBus.SqlServer/obsoletes.cs +++ b/src/NServiceBus.SqlServer/obsoletes.cs @@ -90,4 +90,24 @@ public static void UseCustomSqlConnectionAndTransaction(this SendOptions options } } -#pragma warning restore 1591 +namespace NServiceBus.Transport.SQLServer +{ + using System; + + public static partial class SqlServerTransportSettingsExtensions + { + [ObsoleteEx( + RemoveInVersion = "6.0", + TreatAsErrorFromVersion = "5.0", + ReplacementTypeOrMember = "NativeDelayedDelivery", + Message = "Starting from version 5 native delayed delivery is always enabled. It can be configured via NativeDelayedDelivery")] + public static DelayedDeliverySettings UseNativeDelayedDelivery(this TransportExtensions transportExtensions) + { + throw new NotImplementedException(); + } + } +} + + + +#pragma warning restore 1591 \ No newline at end of file From 483cc2cb2ed28556d419263471031f3827a08446 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 11:35:19 +0100 Subject: [PATCH 25/31] Align SqlConstants --- src/NServiceBus.SqlServer/Queuing/SqlConstants.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs index 443b72913..339f132cf 100644 --- a/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs +++ b/src/NServiceBus.SqlServer/Queuing/SqlConstants.cs @@ -38,7 +38,7 @@ THEN DATEADD(ms, @TimeToBeReceivedMs, GETUTCDATE()) END, IF (@NOCOUNT = 'ON') SET NOCOUNT ON; IF (@NOCOUNT = 'OFF') SET NOCOUNT OFF;"; - internal const string StoreDelayedMessageText = + public static readonly string StoreDelayedMessageText = @" DECLARE @NOCOUNT VARCHAR(3) = 'OFF'; IF ( (512 & @@OPTIONS) = 512 ) SET @NOCOUNT = 'ON' @@ -90,7 +90,7 @@ ELSE 1 IF (@NOCOUNT = 'ON') SET NOCOUNT ON; IF (@NOCOUNT = 'OFF') SET NOCOUNT OFF;"; - internal const string MoveDueDelayedMessageText = @" + public static readonly string MoveDueDelayedMessageText = @" DECLARE @NOCOUNT VARCHAR(3) = 'OFF'; IF ( (512 & @@OPTIONS) = 512 ) SET @NOCOUNT = 'ON'; SET NOCOUNT ON; @@ -201,7 +201,7 @@ Expires IS NOT NULL EXEC sp_releaseapplock @Resource = '{0}_lock'"; - public const string CreateDelayedMessageStoreText = @" + public static readonly string CreateDelayedMessageStoreText = @" IF EXISTS ( SELECT * FROM {1}.sys.objects From 799da1ab05aed2ff5b745e7933cf1c797d7528b7 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 12:15:59 +0100 Subject: [PATCH 26/31] Prevent throwing exception when SQLT is referenced but not enabled in the endpoint --- src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs b/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs index 8509aae34..b20afec2b 100644 --- a/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs +++ b/src/NServiceBus.SqlServer/SubscriptionTableInstaller.cs @@ -20,7 +20,7 @@ public SubscriptionTableInstaller(ReadOnlySettings settings) public Task Install(string identity) { - return creator.CreateIfNecessary(); + return creator?.CreateIfNecessary(); } } } \ No newline at end of file From 6b2d133aa27e4662288a973b097ed65396a257c6 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 12:16:31 +0100 Subject: [PATCH 27/31] Optimize multicast to unicast op conversion for scenario with no multicast ops --- .../Sending/MessageDispatcher.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs index 2b2554aa8..db43f9456 100644 --- a/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs +++ b/src/NServiceBus.SqlServer/Sending/MessageDispatcher.cs @@ -34,11 +34,16 @@ public async Task Dispatch(TransportOperations operations, TransportTransaction await DispatchIsolated(sortedOperations).ConfigureAwait(false); } - async Task> ConvertToUnicastOperations(TransportOperations operations) + async Task> ConvertToUnicastOperations(TransportOperations operations) { - var tasks = operations.MulticastTransportOperations.Select(multicastToUnicastConverter.Convert).ToArray(); - await Task.WhenAll(tasks).ConfigureAwait(false); - return tasks.SelectMany(t => t.Result).ToList(); + if (operations.MulticastTransportOperations.Count == 0) + { + return emptyUnicastTransportOperationsList; + } + + var tasks = operations.MulticastTransportOperations.Select(multicastToUnicastConverter.Convert); + var result = await Task.WhenAll(tasks).ConfigureAwait(false); + return result.SelectMany(x => x); } async Task DispatchIsolated(SortingResult sortedOperations) @@ -175,7 +180,6 @@ static bool TryGetConstraint(IOutgoingTransportOperation operation, out T con SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; IMulticastToUnicastConverter multicastToUnicastConverter; - - + static UnicastTransportOperation[] emptyUnicastTransportOperationsList = new UnicastTransportOperation[0]; } } \ No newline at end of file From 6d01d7ada00a5e5e82554596b4ce5098e73e8a5d Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 12:16:48 +0100 Subject: [PATCH 28/31] Remove duplication from QueueCreator --- .../Receiving/QueueCreator.cs | 45 +++---------------- 1 file changed, 6 insertions(+), 39 deletions(-) diff --git a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs index fb4bafa31..372fa62df 100644 --- a/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs +++ b/src/NServiceBus.SqlServer/Receiving/QueueCreator.cs @@ -24,27 +24,22 @@ public async Task CreateQueueIfNecessary(QueueBindings queueBindings, string ide { foreach (var receivingAddress in queueBindings.ReceivingAddresses) { - await CreateQueue(addressTranslator.Parse(receivingAddress), connection, transaction, createMessageBodyColumn).ConfigureAwait(false); + await CreateQueue(SqlConstants.CreateQueueText, addressTranslator.Parse(receivingAddress), connection, transaction, createMessageBodyColumn).ConfigureAwait(false); } foreach (var sendingAddress in queueBindings.SendingAddresses) { - await CreateQueue(addressTranslator.Parse(sendingAddress), connection, transaction, createMessageBodyColumn).ConfigureAwait(false); + await CreateQueue(SqlConstants.CreateQueueText, addressTranslator.Parse(sendingAddress), connection, transaction, createMessageBodyColumn).ConfigureAwait(false); } - transaction.Commit(); - } - - using (var connection = await connectionFactory.OpenNewConnection().ConfigureAwait(false)) - using (var transaction = connection.BeginTransaction()) - { - await CreateDelayedMessageQueue(connection, transaction, createMessageBodyColumn).ConfigureAwait(false); + + await CreateQueue(SqlConstants.CreateDelayedMessageStoreText, delayedQueueAddress, connection, transaction, createMessageBodyColumn).ConfigureAwait(false); transaction.Commit(); } } - static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyColumn) + static async Task CreateQueue(string creationScript, CanonicalQueueAddress canonicalQueueAddress, SqlConnection connection, SqlTransaction transaction, bool createMessageBodyColumn) { - var sql = string.Format(SqlConstants.CreateQueueText, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); + var sql = string.Format(creationScript, canonicalQueueAddress.QualifiedTableName, canonicalQueueAddress.QuotedCatalogName); using (var command = new SqlCommand(sql, connection, transaction) { CommandType = CommandType.Text @@ -66,34 +61,6 @@ static async Task CreateQueue(CanonicalQueueAddress canonicalQueueAddress, SqlCo } } - async Task CreateDelayedMessageQueue(SqlConnection connection, SqlTransaction transaction, bool createMessageBodyComputedColumn) - { -#pragma warning disable 618 - var sql = string.Format(SqlConstants.CreateDelayedMessageStoreText, delayedQueueAddress.QualifiedTableName, delayedQueueAddress.QuotedCatalogName); -#pragma warning restore 618 - using (var command = new SqlCommand(sql, connection, transaction) - { - CommandType = CommandType.Text - }) - { - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - if (createMessageBodyComputedColumn) - { -#pragma warning disable 618 - var bodyStringSql = string.Format(SqlConstants.AddMessageBodyStringColumn, delayedQueueAddress.QualifiedTableName, delayedQueueAddress.QuotedCatalogName); -#pragma warning restore 618 - using (var command = new SqlCommand(bodyStringSql, connection, transaction) - { - CommandType = CommandType.Text - }) - { - await command.ExecuteNonQueryAsync().ConfigureAwait(false); - } - - } - } - SqlConnectionFactory connectionFactory; QueueAddressTranslator addressTranslator; CanonicalQueueAddress delayedQueueAddress; From de3d7dac787c5b603363709eb2423fdcf9a876e3 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 12:17:07 +0100 Subject: [PATCH 29/31] Cache topics instead of types in PolymorphicSubscriptionStore --- .../PubSub/PolymorphicSubscriptionStore.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs b/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs index e9e2528ee..d487f8ae6 100644 --- a/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs +++ b/src/NServiceBus.SqlServer/PubSub/PolymorphicSubscriptionStore.cs @@ -15,7 +15,7 @@ public PolymorphicSubscriptionStore(SubscriptionTable subscriptionTable) public Task> GetSubscribers(Type eventType) { - var topics = GetTopicsFor(eventType); + var topics = GetTopics(eventType); return subscriptionTable.GetSubscribers(topics.ToArray()); } @@ -29,14 +29,16 @@ public Task Unsubscribe(string endpointName, Type eventType) return subscriptionTable.Unsubscribe(endpointName, TopicName.From(eventType)); } - IEnumerable GetTopicsFor(Type messageType) + IEnumerable GetTopics(Type messageType) { - return GetMessageHierarchy(messageType).Select(TopicName.From); + return eventTypeToTopicListMap.GetOrAdd(messageType, GenerateTopics); } - IEnumerable GetMessageHierarchy(Type messageType) + static string[] GenerateTopics(Type messageType) { - return messageHierarchyCache.GetOrAdd(messageType, t => GenerateMessageHierarchy(t).ToArray()); + return GenerateMessageHierarchy(messageType) + .Select(TopicName.From) + .ToArray(); } static IEnumerable GenerateMessageHierarchy(Type messageType) @@ -53,7 +55,7 @@ static IEnumerable GenerateMessageHierarchy(Type messageType) } } - ConcurrentDictionary messageHierarchyCache = new ConcurrentDictionary(); + ConcurrentDictionary eventTypeToTopicListMap = new ConcurrentDictionary(); SubscriptionTable subscriptionTable; } } \ No newline at end of file From bdbddc0475d028e3d70b7cd29fd1a2fdb345ccb1 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Wed, 13 Nov 2019 12:21:16 +0100 Subject: [PATCH 30/31] Approvde API changes. --- .../APIApprovals.Approve.approved.txt | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt index 64445abae..92cc11d77 100644 --- a/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt +++ b/src/NServiceBus.SqlServer.UnitTests/ApprovalFiles/APIApprovals.Approve.approved.txt @@ -7,9 +7,10 @@ namespace NServiceBus { public class static MessageDrivenPubSubCompatibility { - [System.ObsoleteAttribute("Pub sub can not be disabled in version 5.0 and above. The transport handles pub-s" + - "ub natively and does not require a separate subscription persistence. The member" + - " currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] + [System.ObsoleteAttribute("Publishing can not be disabled in version 5.0 and above. The transport handles pu" + + "blish-subscribe natively and does not require a separate subscription persistenc" + + "e. The member currently throws a NotImplementedException. Will be removed in ver" + + "sion 6.0.0.", true)] public static void DisablePublishing(this NServiceBus.TransportExtensions transportExtensions) { } [System.ObsoleteAttribute(@"Publisher registration has been moved to message-driven pub-sub migration mode.\r\n\r\nvar compatMode = transport.EnableMessageDrivenPubSubCompatibilityMode();\r\ncompatMode.RegisterPublisher(eventType, publisherEndpoint);. Use `SubscriptionMigrationModeSettings.RegisterPublisher(routingSettings, eventType, publisherEndpoint)` instead. The member currently throws a NotImplementedException. Will be removed in version 6.0.0.", true)] public static void RegisterPublisher(this NServiceBus.RoutingSettings routingSettings, System.Type eventType, string publisherEndpoint) { } @@ -56,42 +57,17 @@ namespace NServiceBus.Transport.SQLServer public static readonly string AddMessageBodyStringColumn; public static readonly string CheckHeadersColumnType; public static readonly string CheckIfExpiresIndexIsPresent; - public const string CreateDelayedMessageStoreText = @" -IF EXISTS ( - SELECT * - FROM {1}.sys.objects - WHERE object_id = OBJECT_ID(N'{0}') - AND type in (N'U')) -RETURN -EXEC sp_getapplock @Resource = '{0}_lock', @LockMode = 'Exclusive' -IF EXISTS ( - SELECT * - FROM {1}.sys.objects - WHERE object_id = OBJECT_ID(N'{0}') - AND type in (N'U')) -BEGIN - EXEC sp_releaseapplock @Resource = '{0}_lock' - RETURN -END -CREATE TABLE {0} ( - Headers nvarchar(max) NOT NULL, - Body varbinary(max), - Due datetime NOT NULL, - RowVersion bigint IDENTITY(1,1) NOT NULL -); -CREATE NONCLUSTERED INDEX [Index_Due] ON {0} -( - [Due] -) -EXEC sp_releaseapplock @Resource = '{0}_lock'"; + public static readonly string CreateDelayedMessageStoreText; public static readonly string CreateQueueText; public static readonly string CreateSubscriptionTableText; public static readonly string GetSubscribersText; + public static readonly string MoveDueDelayedMessageText; public static readonly string PeekText; public static readonly string PurgeBatchOfExpiredMessagesText; public static readonly string PurgeText; public static readonly string ReceiveText; public static readonly string SendText; + public static readonly string StoreDelayedMessageText; public static readonly string SubscribeText; public static readonly string UnsubscribeText; } From e34af9c8e3aa479e8aa79ba004d06b7e04b413b5 Mon Sep 17 00:00:00 2001 From: SzymonPobiega Date: Thu, 14 Nov 2019 07:48:41 +0100 Subject: [PATCH 31/31] Use type's full name as a topic name --- src/NServiceBus.SqlServer/PubSub/TopicNames.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NServiceBus.SqlServer/PubSub/TopicNames.cs b/src/NServiceBus.SqlServer/PubSub/TopicNames.cs index 0656105c1..f2de4529d 100644 --- a/src/NServiceBus.SqlServer/PubSub/TopicNames.cs +++ b/src/NServiceBus.SqlServer/PubSub/TopicNames.cs @@ -4,6 +4,6 @@ namespace NServiceBus.Transport.SQLServer static class TopicName { - public static string From(Type type) => $"{type.Namespace}.{type.Name}"; + public static string From(Type type) => type.FullName; } } \ No newline at end of file