diff --git a/src/Silverback.Core.EFCore30/EntityFrameworkCore/DbContextEventsPublisher.cs b/src/Silverback.Core.EFCore30/EntityFrameworkCore/DbContextEventsPublisher.cs index cc510dc31..f4ae05c75 100644 --- a/src/Silverback.Core.EFCore30/EntityFrameworkCore/DbContextEventsPublisher.cs +++ b/src/Silverback.Core.EFCore30/EntityFrameworkCore/DbContextEventsPublisher.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Silverback.Messaging.Messages; @@ -96,45 +97,48 @@ public int ExecuteSaveTransaction(Func saveChanges) /// /// The delegate to the original SaveChangesAsync method. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// number of entities saved to the database. /// - public Task ExecuteSaveTransactionAsync(Func> saveChangesAsync) + public Task ExecuteSaveTransactionAsync(Func> saveChangesAsync, CancellationToken cancellationToken = default) { Check.NotNull(saveChangesAsync, nameof(saveChangesAsync)); - return ExecuteSaveTransactionAsync(saveChangesAsync, true); + return ExecuteSaveTransactionAsync(saveChangesAsync, true, cancellationToken); } - private async Task ExecuteSaveTransactionAsync(Func> saveChanges, bool executeAsync) + private async Task ExecuteSaveTransactionAsync(Func> saveChanges, bool executeAsync, CancellationToken cancellationToken = default) { - await PublishEventAsync(executeAsync).ConfigureAwait(false); + await PublishEventAsync(executeAsync, cancellationToken).ConfigureAwait(false); var saved = false; try { - await PublishDomainEventsAsync(executeAsync).ConfigureAwait(false); + await PublishDomainEventsAsync(executeAsync, cancellationToken).ConfigureAwait(false); int result = await saveChanges().ConfigureAwait(false); saved = true; - await PublishEventAsync(executeAsync).ConfigureAwait(false); + await PublishEventAsync(executeAsync, cancellationToken).ConfigureAwait(false); return result; } catch { if (!saved) - await PublishEventAsync(executeAsync).ConfigureAwait(false); + await PublishEventAsync(executeAsync, cancellationToken).ConfigureAwait(false); throw; } } [SuppressMessage("", "VSTHRD103", Justification = Justifications.ExecutesSyncOrAsync)] - private async Task PublishDomainEventsAsync(bool executeAsync) + private async Task PublishDomainEventsAsync(bool executeAsync, CancellationToken cancellationToken) { var events = GetDomainEvents(); @@ -143,7 +147,7 @@ private async Task PublishDomainEventsAsync(bool executeAsync) { if (executeAsync) { - await events.ForEachAsync(message => _publisher.PublishAsync(message)) + await events.ForEachAsync(message => _publisher.PublishAsync(message, cancellationToken)) .ConfigureAwait(false); } else @@ -168,11 +172,11 @@ private IReadOnlyCollection GetDomainEvents() => }).ToList(); [SuppressMessage("", "VSTHRD103", Justification = Justifications.ExecutesSyncOrAsync)] - private async Task PublishEventAsync(bool executeAsync) + private async Task PublishEventAsync(bool executeAsync, CancellationToken cancellationToken) where TEvent : new() { if (executeAsync) - await _publisher.PublishAsync(new TEvent()).ConfigureAwait(false); + await _publisher.PublishAsync(new TEvent(), cancellationToken).ConfigureAwait(false); else _publisher.Publish(new TEvent()); } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/CommandPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/CommandPublisher.cs index d652a42e1..ec266165a 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/CommandPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/CommandPublisher.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -38,24 +39,31 @@ public TResult Execute(ICommand commandMessage) => public TResult Execute(ICommand commandMessage, bool throwIfUnhandled) => _publisher.Publish(commandMessage, throwIfUnhandled).SingleOrDefault(); - /// - public Task ExecuteAsync(ICommand commandMessage) => _publisher.PublishAsync(commandMessage); + /// + public Task ExecuteAsync(ICommand commandMessage, CancellationToken cancellationToken = default) => + _publisher.PublishAsync(commandMessage, cancellationToken); - /// - public Task ExecuteAsync(ICommand commandMessage, bool throwIfUnhandled) => - _publisher.PublishAsync(commandMessage, throwIfUnhandled); + /// + public Task ExecuteAsync( + ICommand commandMessage, + bool throwIfUnhandled, + CancellationToken cancellationToken = default) => + _publisher.PublishAsync(commandMessage, throwIfUnhandled, cancellationToken); - /// - public async Task ExecuteAsync(ICommand commandMessage) => - (await _publisher.PublishAsync(commandMessage) + /// + public async Task ExecuteAsync( + ICommand commandMessage, + CancellationToken cancellationToken = default) => + (await _publisher.PublishAsync(commandMessage, cancellationToken) .ConfigureAwait(false)) .SingleOrDefault(); - /// + /// public async Task ExecuteAsync( ICommand commandMessage, - bool throwIfUnhandled) => - (await _publisher.PublishAsync(commandMessage, throwIfUnhandled) + bool throwIfUnhandled, + CancellationToken cancellationToken = default) => + (await _publisher.PublishAsync(commandMessage, throwIfUnhandled, cancellationToken) .ConfigureAwait(false)) .SingleOrDefault(); } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/EventPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/EventPublisher.cs index f30f78209..e4a178d19 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/EventPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/EventPublisher.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -29,11 +30,15 @@ public EventPublisher(IPublisher publisher) public void Publish(IEvent eventMessage, bool throwIfUnhandled) => _publisher.Publish(eventMessage, throwIfUnhandled); - /// - public Task PublishAsync(IEvent eventMessage) => _publisher.PublishAsync(eventMessage); + /// + public Task PublishAsync(IEvent eventMessage, CancellationToken cancellationToken = default) => + _publisher.PublishAsync(eventMessage, cancellationToken); - /// - public Task PublishAsync(IEvent eventMessage, bool throwIfUnhandled) => - _publisher.PublishAsync(eventMessage, throwIfUnhandled); + /// + public Task PublishAsync( + IEvent eventMessage, + bool throwIfUnhandled, + CancellationToken cancellationToken = default) => + _publisher.PublishAsync(eventMessage, throwIfUnhandled, cancellationToken); } } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/ICommandPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/ICommandPublisher.cs index bed192108..4e35b8218 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/ICommandPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/ICommandPublisher.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -80,10 +81,13 @@ public interface ICommandPublisher /// /// The command to be executed. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task ExecuteAsync(ICommand commandMessage); + Task ExecuteAsync(ICommand commandMessage, CancellationToken cancellationToken = default); /// /// Executes the specified command publishing it to the internal bus. The message will be forwarded to @@ -98,10 +102,13 @@ public interface ICommandPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task ExecuteAsync(ICommand commandMessage, bool throwIfUnhandled); + Task ExecuteAsync(ICommand commandMessage, bool throwIfUnhandled, CancellationToken cancellationToken = default); /// /// Executes the specified command publishing it to the internal bus. The message will be forwarded to @@ -115,11 +122,14 @@ public interface ICommandPublisher /// /// The command to be executed. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// command result. /// - Task ExecuteAsync(ICommand commandMessage); + Task ExecuteAsync(ICommand commandMessage, CancellationToken cancellationToken = default); /// /// Executes the specified command publishing it to the internal bus. The message will be forwarded to @@ -137,10 +147,13 @@ public interface ICommandPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// command result. /// - Task ExecuteAsync(ICommand commandMessage, bool throwIfUnhandled); + Task ExecuteAsync(ICommand commandMessage, bool throwIfUnhandled, CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/IEventPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/IEventPublisher.cs index d67aefa65..fcc2c908c 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/IEventPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/IEventPublisher.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -43,10 +44,13 @@ public interface IEventPublisher /// /// The event to be executed. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task PublishAsync(IEvent eventMessage); + Task PublishAsync(IEvent eventMessage, CancellationToken cancellationToken = default); /// /// Publishes the specified event to the internal bus. The message will be forwarded to its subscribers @@ -60,9 +64,15 @@ public interface IEventPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task PublishAsync(IEvent eventMessage, bool throwIfUnhandled); + Task PublishAsync( + IEvent eventMessage, + bool throwIfUnhandled, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/IQueryPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/IQueryPublisher.cs index 9192825fd..703ec72ed 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/IQueryPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/IQueryPublisher.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -58,11 +59,16 @@ public interface IQueryPublisher /// /// The query to be executed. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// query result. /// - Task ExecuteAsync(IQuery queryMessage); + Task ExecuteAsync( + IQuery queryMessage, + CancellationToken cancellationToken = default); /// /// Executes the specified query publishing it to the internal bus. The message will be forwarded to its @@ -79,10 +85,16 @@ public interface IQueryPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// query result. /// - Task ExecuteAsync(IQuery queryMessage, bool throwIfUnhandled); + Task ExecuteAsync( + IQuery queryMessage, + bool throwIfUnhandled, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core.Model/Messaging/Publishing/QueryPublisher.cs b/src/Silverback.Core.Model/Messaging/Publishing/QueryPublisher.cs index 9e823949a..dfba36026 100644 --- a/src/Silverback.Core.Model/Messaging/Publishing/QueryPublisher.cs +++ b/src/Silverback.Core.Model/Messaging/Publishing/QueryPublisher.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -31,17 +32,20 @@ public TResult Execute(IQuery queryMessage) => public TResult Execute(IQuery queryMessage, bool throwIfUnhandled) => _publisher.Publish(queryMessage, throwIfUnhandled).SingleOrDefault(); - /// - public async Task ExecuteAsync(IQuery queryMessage) => - (await _publisher.PublishAsync(queryMessage) + /// + public async Task ExecuteAsync( + IQuery queryMessage, + CancellationToken cancellationToken = default) => + (await _publisher.PublishAsync(queryMessage, cancellationToken) .ConfigureAwait(false)) .SingleOrDefault(); - /// + /// public async Task ExecuteAsync( IQuery queryMessage, - bool throwIfUnhandled) => - (await _publisher.PublishAsync(queryMessage, throwIfUnhandled) + bool throwIfUnhandled, + CancellationToken cancellationToken = default) => + (await _publisher.PublishAsync(queryMessage, throwIfUnhandled, cancellationToken) .ConfigureAwait(false)) .SingleOrDefault(); } diff --git a/src/Silverback.Core.Rx/Messaging/Subscribers/ReturnValueHandlers/ObservableMessagesReturnValueHandler.cs b/src/Silverback.Core.Rx/Messaging/Subscribers/ReturnValueHandlers/ObservableMessagesReturnValueHandler.cs index 61f280765..30f539bda 100644 --- a/src/Silverback.Core.Rx/Messaging/Subscribers/ReturnValueHandlers/ObservableMessagesReturnValueHandler.cs +++ b/src/Silverback.Core.Rx/Messaging/Subscribers/ReturnValueHandlers/ObservableMessagesReturnValueHandler.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Publishing; @@ -35,7 +36,7 @@ public ObservableMessagesReturnValueHandler(IPublisher publisher, IBusOptions bu } /// - public bool CanHandle(object returnValue) => + public bool CanHandle(object? returnValue) => returnValue != null && returnValue.GetType().GetInterfaces().Any( i => i.IsGenericType && @@ -45,11 +46,11 @@ public bool CanHandle(object returnValue) => messageType.IsAssignableFrom(i.GenericTypeArguments[0]))); /// - public void Handle(object returnValue) => - _publisher.Publish(((IObservable)returnValue).ToEnumerable()); + public void Handle(object? returnValue) => + _publisher.Publish(((IObservable)returnValue!).ToEnumerable()); /// - public Task HandleAsync(object returnValue) => - _publisher.PublishAsync(((IObservable)returnValue).ToEnumerable()); + public Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default) => + _publisher.PublishAsync(((IObservable)returnValue!).ToEnumerable(), cancellationToken); } } diff --git a/src/Silverback.Core/Messaging/Configuration/SilverbackBuilderAddDelegateSubscriberExtensions.cs b/src/Silverback.Core/Messaging/Configuration/SilverbackBuilderAddDelegateSubscriberExtensions.cs index 45d3b61df..f20e19e2a 100644 --- a/src/Silverback.Core/Messaging/Configuration/SilverbackBuilderAddDelegateSubscriberExtensions.cs +++ b/src/Silverback.Core/Messaging/Configuration/SilverbackBuilderAddDelegateSubscriberExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Subscribers.Subscriptions; @@ -92,6 +93,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// @@ -116,6 +141,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// @@ -140,6 +189,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func> handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// @@ -188,6 +261,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func, CancellationToken, Task> handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// @@ -212,6 +309,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func, CancellationToken, object> handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// @@ -236,6 +357,30 @@ public static ISilverbackBuilder AddDelegateSubscriber( SubscriptionOptions? options = null) => AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// + /// Subscribes the specified delegate to the messages being published into the bus. + /// + /// + /// The that references the to be configured. + /// + /// + /// The type of the messages to be handled. + /// + /// + /// The message handler delegate. + /// + /// + /// The . + /// + /// + /// The so that additional calls can be chained. + /// + public static ISilverbackBuilder AddDelegateSubscriber( + this ISilverbackBuilder silverbackBuilder, + Func, CancellationToken, Task> handler, + SubscriptionOptions? options = null) => + AddDelegateSubscriber(silverbackBuilder, (Delegate)handler, options); + /// /// Subscribes the specified delegate to the messages being published into the bus. /// diff --git a/src/Silverback.Core/Messaging/Messages/MessageStreamProvider`1.cs b/src/Silverback.Core/Messaging/Messages/MessageStreamProvider`1.cs index 8fb14f9e6..2e9ad5afa 100644 --- a/src/Silverback.Core/Messaging/Messages/MessageStreamProvider`1.cs +++ b/src/Silverback.Core/Messaging/Messages/MessageStreamProvider`1.cs @@ -251,6 +251,9 @@ private IEnumerable PushToCompatibleStreams( { foreach (var lazyStream in _lazyStreams) { + if (cancellationToken.IsCancellationRequested) + break; + if (PushIfCompatibleType( lazyStream, messageId, diff --git a/src/Silverback.Core/Messaging/Publishing/IBehavior.cs b/src/Silverback.Core/Messaging/Publishing/IBehavior.cs index 4c3b6031f..0e33a16ac 100644 --- a/src/Silverback.Core/Messaging/Publishing/IBehavior.cs +++ b/src/Silverback.Core/Messaging/Publishing/IBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Publishing @@ -21,10 +22,13 @@ public interface IBehavior /// /// The next behavior in the pipeline. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// result values (if any). /// - Task> HandleAsync(object message, MessageHandler next); + Task> HandleAsync(object message, MessageHandler next, CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core/Messaging/Publishing/IPublisher.cs b/src/Silverback.Core/Messaging/Publishing/IPublisher.cs index c67b3979e..b7fef1319 100644 --- a/src/Silverback.Core/Messaging/Publishing/IPublisher.cs +++ b/src/Silverback.Core/Messaging/Publishing/IPublisher.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Publishing @@ -86,10 +87,13 @@ public interface IPublisher /// /// The message to be published. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task PublishAsync(object message); + Task PublishAsync(object message, CancellationToken cancellationToken = default); /// /// Publishes the specified message to the internal bus. The message will be forwarded to its @@ -103,10 +107,13 @@ public interface IPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task PublishAsync(object message, bool throwIfUnhandled); + Task PublishAsync(object message, bool throwIfUnhandled, CancellationToken cancellationToken = default); /// /// Publishes the specified message to the internal bus. The message will be forwarded to its @@ -119,13 +126,16 @@ public interface IPublisher /// /// The message to be published. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains a /// collection of , since multiple subscribers could handle the message and /// return a /// value. /// - Task> PublishAsync(object message); + Task> PublishAsync(object message, CancellationToken cancellationToken = default); /// /// Publishes the specified message to the internal bus. The message will be forwarded to its @@ -142,12 +152,15 @@ public interface IPublisher /// A boolean value indicating whether an exception must be thrown if no subscriber is handling the /// message. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains a /// collection of , since multiple subscribers could handle the message /// and return a /// value. /// - Task> PublishAsync(object message, bool throwIfUnhandled); + Task> PublishAsync(object message, bool throwIfUnhandled, CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core/Messaging/Publishing/IStreamPublisher.cs b/src/Silverback.Core/Messaging/Publishing/IStreamPublisher.cs index f19dec3ef..23ecc8770 100644 --- a/src/Silverback.Core/Messaging/Publishing/IStreamPublisher.cs +++ b/src/Silverback.Core/Messaging/Publishing/IStreamPublisher.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -36,10 +37,15 @@ internal interface IStreamPublisher /// /// The to be used to generate the streams to be published. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains a /// collection of that will complete when each subscriber completes. /// - Task> PublishAsync(IMessageStreamProvider streamProvider); + Task> PublishAsync( + IMessageStreamProvider streamProvider, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core/Messaging/Publishing/MessageHandler.cs b/src/Silverback.Core/Messaging/Publishing/MessageHandler.cs index dc7f8af5c..c14b27eb3 100644 --- a/src/Silverback.Core/Messaging/Publishing/MessageHandler.cs +++ b/src/Silverback.Core/Messaging/Publishing/MessageHandler.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Publishing @@ -12,9 +13,14 @@ namespace Silverback.Messaging.Publishing /// /// The message being published. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// actual messages to be published. /// - public delegate Task> MessageHandler(object message); + public delegate Task> MessageHandler( + object message, + CancellationToken cancellationToken = default); } diff --git a/src/Silverback.Core/Messaging/Publishing/Publisher.cs b/src/Silverback.Core/Messaging/Publishing/Publisher.cs index d4368b5b7..1b6e9e2c2 100644 --- a/src/Silverback.Core/Messaging/Publishing/Publisher.cs +++ b/src/Silverback.Core/Messaging/Publishing/Publisher.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -63,60 +64,63 @@ public IReadOnlyCollection Publish(object message) => public IReadOnlyCollection Publish(object message, bool throwIfUnhandled) => CastResults(PublishAsync(message, throwIfUnhandled, false).Result).ToList(); - /// - public Task PublishAsync(object message) => - PublishAsync(message, false); + /// + public Task PublishAsync(object message, CancellationToken cancellationToken = default) => + PublishAsync(message, false, cancellationToken); - /// - public Task PublishAsync(object message, bool throwIfUnhandled) => - PublishAsync(message, throwIfUnhandled, true); + /// + public Task PublishAsync(object message, bool throwIfUnhandled, CancellationToken cancellationToken = default) => + PublishAsync(message, throwIfUnhandled, true, cancellationToken); - /// - public Task> PublishAsync(object message) => - PublishAsync(message, false); + /// + public Task> PublishAsync(object message, CancellationToken cancellationToken = default) => + PublishAsync(message, false, cancellationToken); - /// - public async Task> PublishAsync( - object message, - bool throwIfUnhandled) => + /// + public async Task> PublishAsync(object message, bool throwIfUnhandled, CancellationToken cancellationToken = default) => CastResults( - await PublishAsync(message, throwIfUnhandled, true) + await PublishAsync(message, throwIfUnhandled, true, cancellationToken) .ConfigureAwait(false)) .ToList(); private static Task> ExecuteBehaviorsPipelineAsync( Stack behaviors, object message, - Func>> finalAction) + Func>> finalAction, + CancellationToken cancellationToken) { if (!behaviors.TryPop(out var nextBehavior)) return finalAction(message); return nextBehavior.HandleAsync( message, - nextMessage => - ExecuteBehaviorsPipelineAsync(behaviors, nextMessage, finalAction)); + (nextMessage, ctx) => + ExecuteBehaviorsPipelineAsync(behaviors, nextMessage, finalAction, ctx), + cancellationToken); } private Task> PublishAsync( object message, bool throwIfUnhandled, - bool executeAsync) + bool executeAsync, + CancellationToken cancellationToken = default) { Check.NotNull(message, nameof(message)); return ExecuteBehaviorsPipelineAsync( _behaviorsProvider.CreateStack(), message, - finalMessage => PublishCoreAsync(finalMessage, throwIfUnhandled, executeAsync)); + finalMessage => PublishCoreAsync(finalMessage, throwIfUnhandled, executeAsync, cancellationToken), + cancellationToken); } private async Task> PublishCoreAsync( object message, bool throwIfUnhandled, - bool executeAsync) + bool executeAsync, + CancellationToken cancellationToken) { - var resultsCollection = await InvokeSubscribedMethodsAsync(message, executeAsync) + var resultsCollection = await InvokeSubscribedMethodsAsync(message, executeAsync, cancellationToken) .ConfigureAwait(false); bool handled = resultsCollection.Any(invocationResult => invocationResult.WasInvoked); @@ -146,14 +150,16 @@ private IEnumerable CastResults(IReadOnlyCollection r private async Task> InvokeSubscribedMethodsAsync( object message, - bool executeAsync) => - (await InvokeExclusiveMethodsAsync(message, executeAsync).ConfigureAwait(false)) - .Union(await InvokeNonExclusiveMethodsAsync(message, executeAsync).ConfigureAwait(false)) + bool executeAsync, + CancellationToken cancellationToken) => + (await InvokeExclusiveMethodsAsync(message, executeAsync, cancellationToken).ConfigureAwait(false)) + .Union(await InvokeNonExclusiveMethodsAsync(message, executeAsync, cancellationToken).ConfigureAwait(false)) .ToList(); private async Task> InvokeExclusiveMethodsAsync( object message, - bool executeAsync) => + bool executeAsync, + CancellationToken cancellationToken) => (await _subscribedMethodsCache.GetExclusiveMethods(message) .SelectAsync( method => @@ -161,13 +167,15 @@ private async Task> InvokeExclusiveM method, message, _serviceProvider, - executeAsync)) + executeAsync, + cancellationToken)) .ConfigureAwait(false)) .ToList(); private async Task> InvokeNonExclusiveMethodsAsync( object message, - bool executeAsync) => + bool executeAsync, + CancellationToken cancellationToken) => (await _subscribedMethodsCache.GetNonExclusiveMethods(message) .ParallelSelectAsync( method => @@ -175,7 +183,8 @@ private async Task> InvokeNonExclusi method, message, _serviceProvider, - executeAsync)) + executeAsync, + cancellationToken)) .ConfigureAwait(false)) .ToList(); } diff --git a/src/Silverback.Core/Messaging/Publishing/StreamPublisher.cs b/src/Silverback.Core/Messaging/Publishing/StreamPublisher.cs index bf33342b6..22568a457 100644 --- a/src/Silverback.Core/Messaging/Publishing/StreamPublisher.cs +++ b/src/Silverback.Core/Messaging/Publishing/StreamPublisher.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -19,7 +20,9 @@ public StreamPublisher(IPublisher publisher) public IReadOnlyCollection Publish(IMessageStreamProvider streamProvider) => _publisher.Publish(streamProvider); - public async Task> PublishAsync(IMessageStreamProvider streamProvider) => - await _publisher.PublishAsync(streamProvider).ConfigureAwait(false); + public async Task> PublishAsync( + IMessageStreamProvider streamProvider, + CancellationToken cancellationToken = default) => + await _publisher.PublishAsync(streamProvider, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Silverback.Core/Messaging/Subscribers/ArgumentResolvers/IStreamEnumerableMessageArgumentResolver.cs b/src/Silverback.Core/Messaging/Subscribers/ArgumentResolvers/IStreamEnumerableMessageArgumentResolver.cs index 524e6af9b..ccfff7b4e 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ArgumentResolvers/IStreamEnumerableMessageArgumentResolver.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ArgumentResolvers/IStreamEnumerableMessageArgumentResolver.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using Silverback.Messaging.Messages; using Silverback.Messaging.Publishing; @@ -12,8 +13,8 @@ namespace Silverback.Messaging.Subscribers.ArgumentResolvers /// These resolvers are used to handle the message streams such as /// . The streams are basically handled as a single /// message by the publisher. The difference is that it is guaranteed that the subscribers are invoked - /// from another thread, when published via / - /// . This is done to avoid blocking the original + /// from another thread, when published via / + /// . This is done to avoid blocking the original /// thread waiting for the stream to complete. /// public interface IStreamEnumerableMessageArgumentResolver : IMessageArgumentResolver diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/AsyncEnumerableMessagesReturnValueHandler.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/AsyncEnumerableMessagesReturnValueHandler.cs index 793aaf328..f813d2e2a 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/AsyncEnumerableMessagesReturnValueHandler.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/AsyncEnumerableMessagesReturnValueHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Publishing; @@ -35,7 +36,7 @@ public AsyncEnumerableMessagesReturnValueHandler(IPublisher publisher, IBusOptio } /// - public bool CanHandle(object returnValue) => + public bool CanHandle(object? returnValue) => returnValue != null && returnValue.GetType().GetInterfaces().Any( i => i.IsGenericType && @@ -45,20 +46,20 @@ public bool CanHandle(object returnValue) => messageType.IsAssignableFrom(i.GenericTypeArguments[0]))); /// - public void Handle(object returnValue) + public void Handle(object? returnValue) { Check.NotNull(returnValue, nameof(returnValue)); AsyncHelper.RunSynchronously( - () => ((IAsyncEnumerable)returnValue).ForEachAsync(_publisher.Publish)); + () => ((IAsyncEnumerable)returnValue!).ForEachAsync(_publisher.Publish)); } /// - public Task HandleAsync(object returnValue) + public Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default) { Check.NotNull(returnValue, nameof(returnValue)); - return ((IAsyncEnumerable)returnValue).ForEachAsync(_publisher.PublishAsync); + return ((IAsyncEnumerable)returnValue!).ForEachAsync(value => _publisher.PublishAsync(value, cancellationToken)); } } } diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/EnumerableMessagesReturnValueHandler.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/EnumerableMessagesReturnValueHandler.cs index 6ed7f1bc3..11106fa39 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/EnumerableMessagesReturnValueHandler.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/EnumerableMessagesReturnValueHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Publishing; @@ -35,7 +36,7 @@ public EnumerableMessagesReturnValueHandler(IPublisher publisher, IBusOptions bu } /// - public bool CanHandle(object returnValue) => + public bool CanHandle(object? returnValue) => returnValue != null && returnValue.GetType().GetInterfaces().Any( i => i.IsGenericType && @@ -45,19 +46,19 @@ public bool CanHandle(object returnValue) => messageType.IsAssignableFrom(i.GenericTypeArguments[0]))); /// - public void Handle(object returnValue) + public void Handle(object? returnValue) { Check.NotNull(returnValue, nameof(returnValue)); - ((IEnumerable)returnValue).ForEach(_publisher.Publish); + ((IEnumerable)returnValue!).ForEach(_publisher.Publish); } /// - public Task HandleAsync(object returnValue) + public Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default) { Check.NotNull(returnValue, nameof(returnValue)); - return ((IEnumerable)returnValue).ForEachAsync(_publisher.PublishAsync); + return ((IEnumerable)returnValue!).ForEachAsync(value => _publisher.PublishAsync(value, cancellationToken)); } } } diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/IReturnValueHandler.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/IReturnValueHandler.cs index 7f35fec23..ac19bd44d 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/IReturnValueHandler.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/IReturnValueHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Subscribers.ReturnValueHandlers @@ -20,7 +21,7 @@ public interface IReturnValueHandler /// /// A boolean value indicating whether the value can be handled. /// - bool CanHandle(object returnValue); + bool CanHandle(object? returnValue); /// /// Handles the specified return value. @@ -28,7 +29,7 @@ public interface IReturnValueHandler /// /// The value to be handled. /// - void Handle(object returnValue); + void Handle(object? returnValue); /// /// Handles the specified return value. @@ -36,9 +37,12 @@ public interface IReturnValueHandler /// /// The value to be handled. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task HandleAsync(object returnValue); + Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReadOnlyCollectionMessagesReturnValueHandler.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReadOnlyCollectionMessagesReturnValueHandler.cs index 53328c7e5..40ff2a9d9 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReadOnlyCollectionMessagesReturnValueHandler.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReadOnlyCollectionMessagesReturnValueHandler.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Publishing; @@ -35,7 +36,7 @@ public ReadOnlyCollectionMessagesReturnValueHandler(IPublisher publisher, IBusOp } /// - public bool CanHandle(object returnValue) => + public bool CanHandle(object? returnValue) => returnValue != null && returnValue.GetType().GetInterfaces().Any( i => i.IsGenericType && @@ -45,19 +46,19 @@ public bool CanHandle(object returnValue) => messageType.IsAssignableFrom(i.GenericTypeArguments[0]))); /// - public void Handle(object returnValue) + public void Handle(object? returnValue) { Check.NotNull(returnValue, nameof(returnValue)); - ((IReadOnlyCollection)returnValue).ForEach(_publisher.Publish); + ((IReadOnlyCollection)returnValue!).ForEach(_publisher.Publish); } /// - public Task HandleAsync(object returnValue) + public Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default) { Check.NotNull(returnValue, nameof(returnValue)); - return ((IReadOnlyCollection)returnValue).ForEachAsync(_publisher.PublishAsync); + return ((IReadOnlyCollection)returnValue!).ForEachAsync(value => _publisher.PublishAsync(value, cancellationToken)); } } } diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReturnValueHandlerService.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReturnValueHandlerService.cs index 5ae165cae..689ad7fe3 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReturnValueHandlerService.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/ReturnValueHandlerService.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Subscribers.ReturnValueHandlers @@ -23,7 +24,10 @@ public ReturnValueHandlerService(IEnumerable returnValueHan } [SuppressMessage("", "VSTHRD103", Justification = Justifications.ExecutesSyncOrAsync)] - public async Task HandleReturnValuesAsync(object? returnValue, bool executeAsync) + public async Task HandleReturnValuesAsync( + object? returnValue, + bool executeAsync, + CancellationToken cancellationToken = default) { if (returnValue == null || returnValue.GetType().Name == "VoidTaskResult") return false; @@ -33,7 +37,7 @@ public async Task HandleReturnValuesAsync(object? returnValue, bool execut if (handler != null) { if (executeAsync) - await handler.HandleAsync(returnValue).ConfigureAwait(false); + await handler.HandleAsync(returnValue, cancellationToken).ConfigureAwait(false); else handler.Handle(returnValue); diff --git a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/SingleMessageReturnValueHandler.cs b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/SingleMessageReturnValueHandler.cs index 85c3e30be..c03806b8c 100644 --- a/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/SingleMessageReturnValueHandler.cs +++ b/src/Silverback.Core/Messaging/Subscribers/ReturnValueHandlers/SingleMessageReturnValueHandler.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Configuration; using Silverback.Messaging.Publishing; @@ -33,16 +34,16 @@ public SingleMessageReturnValueHandler(IPublisher publisher, IBusOptions busOpti } /// - public bool CanHandle(object returnValue) => + public bool CanHandle(object? returnValue) => returnValue != null && _busOptions.MessageTypes.Any(type => type.IsInstanceOfType(returnValue)); /// - public void Handle(object returnValue) => - _publisher.Publish(returnValue); + public void Handle(object? returnValue) => + _publisher.Publish(returnValue!); /// - public Task HandleAsync(object returnValue) => - _publisher.PublishAsync(returnValue); + public Task HandleAsync(object? returnValue, CancellationToken cancellationToken = default) => + _publisher.PublishAsync(returnValue!, cancellationToken); } } diff --git a/src/Silverback.Core/Messaging/Subscribers/SubscribedMethodInvoker.cs b/src/Silverback.Core/Messaging/Subscribers/SubscribedMethodInvoker.cs index 2ff6932f8..ebb2d6def 100644 --- a/src/Silverback.Core/Messaging/Subscribers/SubscribedMethodInvoker.cs +++ b/src/Silverback.Core/Messaging/Subscribers/SubscribedMethodInvoker.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Messaging.Messages; @@ -18,12 +19,13 @@ public static async Task InvokeAsync( SubscribedMethod subscribedMethod, object message, IServiceProvider serviceProvider, - bool executeAsync) + bool executeAsync, + CancellationToken cancellationToken = default) { if (IsFiltered(subscribedMethod.Options.Filters, message)) return MethodInvocationResult.NotInvoked; - var arguments = GetArgumentValuesArray(subscribedMethod, serviceProvider); + var parameters = GetParameterTypeValuesArray(subscribedMethod, serviceProvider); object? returnValue; @@ -33,18 +35,20 @@ public static async Task InvokeAsync( returnValue = await InvokeWithSingleMessageAsync( message, subscribedMethod, - arguments, + parameters, singleResolver, serviceProvider, - executeAsync).ConfigureAwait(false); + executeAsync, + cancellationToken).ConfigureAwait(false); break; case IStreamEnumerableMessageArgumentResolver streamEnumerableResolver: returnValue = InvokeWithStreamEnumerable( (IMessageStreamProvider)message, subscribedMethod, - arguments, + parameters, streamEnumerableResolver, - serviceProvider); + serviceProvider, + cancellationToken); break; default: @@ -60,7 +64,7 @@ public static async Task InvokeAsync( bool returnValueWasHandled = await serviceProvider .GetRequiredService() - .HandleReturnValuesAsync(returnValue, executeAsync) + .HandleReturnValuesAsync(returnValue, executeAsync, cancellationToken) .ConfigureAwait(false); if (returnValueWasHandled) @@ -72,18 +76,22 @@ await serviceProvider private static bool IsFiltered(IReadOnlyCollection filters, object message) => filters.Count != 0 && !filters.All(filter => filter.MustProcess(message)); - private static object?[] GetArgumentValuesArray( + private static ParameterTypeValue[] GetParameterTypeValuesArray( SubscribedMethod method, IServiceProvider serviceProvider) { - var values = new object?[method.Parameters.Count]; + var values = new ParameterTypeValue[method.Parameters.Count]; for (int i = 1; i < method.Parameters.Count; i++) { var parameterType = method.Parameters[i].ParameterType; - values[i] = method.AdditionalArgumentsResolvers[i - 1] - .GetValue(parameterType, serviceProvider); + values[i] = new ParameterTypeValue + { + Type = parameterType, + Value = method.AdditionalArgumentsResolvers[i - 1] + .GetValue(parameterType, serviceProvider) + }; } return values; @@ -92,24 +100,48 @@ private static bool IsFiltered(IReadOnlyCollection filters, obje private static Task InvokeWithSingleMessageAsync( object message, SubscribedMethod subscribedMethod, - object?[] arguments, + ParameterTypeValue[] parameters, ISingleMessageArgumentResolver singleResolver, IServiceProvider serviceProvider, - bool executeAsync) + bool executeAsync, + CancellationToken cancellationToken) { message = UnwrapEnvelopeIfNeeded(message, subscribedMethod); var target = subscribedMethod.ResolveTargetType(serviceProvider); - arguments[0] = singleResolver.GetValue(message); - return subscribedMethod.MethodInfo.InvokeWithActivityAsync(target, arguments, executeAsync); + + return subscribedMethod.MethodInfo.InvokeWithActivityAsync(target, GetArgumentValuesArray(singleResolver.GetValue(message), parameters, cancellationToken), executeAsync); + } + + private static object?[] GetArgumentValuesArray( + object? messageValue, + ParameterTypeValue[] parameters, + CancellationToken cancellationToken) + { + var arguments = new object?[parameters.Length]; + arguments[0] = messageValue; + for (int i = 1; i < parameters.Length; i++) + { + var parameter = parameters[i]; + if (parameter.Type == typeof(CancellationToken)) + { + arguments[i] = cancellationToken; + continue; + } + + arguments[i] = parameter.Value; + } + + return arguments; } private static object InvokeWithStreamEnumerable( IMessageStreamProvider messageStreamProvider, SubscribedMethod subscribedMethod, - object?[] arguments, + ParameterTypeValue[] parameters, IStreamEnumerableMessageArgumentResolver streamEnumerableResolver, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + CancellationToken cancellationToken) { var target = subscribedMethod.ResolveTargetType(serviceProvider); @@ -121,11 +153,12 @@ private static object InvokeWithStreamEnumerable( return Task.Run( async () => { + object?[] arguments; try { await lazyStream.WaitUntilCreatedAsync().ConfigureAwait(false); - arguments[0] = lazyStream.Value; + arguments = GetArgumentValuesArray(lazyStream.Value, parameters, cancellationToken); } catch (OperationCanceledException) { @@ -136,7 +169,8 @@ await subscribedMethod.MethodInfo.InvokeWithActivityWithoutBlockingAsync( target, arguments) .ConfigureAwait(false); - }); + }, + cancellationToken); } private static object UnwrapEnvelopeIfNeeded(object message, SubscribedMethod subscribedMethod) => @@ -145,5 +179,12 @@ message is IEnvelope envelope && subscribedMethod.MessageType.IsInstanceOfType(envelope.Message) ? envelope.Message ?? throw new InvalidOperationException("The envelope message is null.") : message; + + private sealed class ParameterTypeValue + { + public Type Type { get; init; } = null!; + + public object? Value { get; init; } + } } } diff --git a/src/Silverback.Core/Util/StreamExtensions.cs b/src/Silverback.Core/Util/StreamExtensions.cs index 3764b685c..3091c9225 100644 --- a/src/Silverback.Core/Util/StreamExtensions.cs +++ b/src/Silverback.Core/Util/StreamExtensions.cs @@ -3,13 +3,14 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace Silverback.Util { internal static class StreamExtensions { - public static async ValueTask ReadAllAsync(this Stream? stream) + public static async ValueTask ReadAllAsync(this Stream? stream, CancellationToken cancellationToken = default) { if (stream == null) return null; @@ -17,11 +18,9 @@ internal static class StreamExtensions if (stream is MemoryStream memoryStream) return memoryStream.ToArray(); - await using (var tempMemoryStream = new MemoryStream()) - { - await stream.CopyToAsync(tempMemoryStream).ConfigureAwait(false); - return tempMemoryStream.ToArray(); - } + await using var tempMemoryStream = new MemoryStream(); + await stream.CopyToAsync(tempMemoryStream, cancellationToken).ConfigureAwait(false); + return tempMemoryStream.ToArray(); } public static byte[]? ReadAll(this Stream? stream) @@ -32,21 +31,19 @@ internal static class StreamExtensions if (stream is MemoryStream memoryStream) return memoryStream.ToArray(); - using (var tempMemoryStream = new MemoryStream()) - { - stream.CopyTo(tempMemoryStream); - return tempMemoryStream.ToArray(); - } + using var tempMemoryStream = new MemoryStream(); + stream.CopyTo(tempMemoryStream); + return tempMemoryStream.ToArray(); } - public static async ValueTask ReadAsync(this Stream? stream, int count) + public static async ValueTask ReadAsync(this Stream? stream, int count, CancellationToken cancellationToken = default) { if (stream == null) return null; var buffer = new byte[count]; - await stream.ReadAsync(buffer.AsMemory()).ConfigureAwait(false); + await stream.ReadAsync(buffer.AsMemory(), cancellationToken).ConfigureAwait(false); return buffer; } diff --git a/src/Silverback.EventSourcing/EventStore/EventStoreEntity`1.cs b/src/Silverback.EventSourcing/EventStore/EventStoreEntity`1.cs index 0cc6f9c16..d2668202c 100644 --- a/src/Silverback.EventSourcing/EventStore/EventStoreEntity`1.cs +++ b/src/Silverback.EventSourcing/EventStore/EventStoreEntity`1.cs @@ -18,6 +18,6 @@ public class EventStoreEntity : MessagesSource, IEventStor public int EntityVersion { get; set; } /// - public void AddDomainEvents(IEnumerable events) => events?.ForEach(AddEvent); + public void AddDomainEvents(IEnumerable events) => events.ForEach(AddEvent); } } diff --git a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializerBase.cs b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializerBase.cs index a0fc053b9..30afca226 100644 --- a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializerBase.cs +++ b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializerBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Confluent.SchemaRegistry; using Confluent.SchemaRegistry.Serdes; @@ -33,13 +34,15 @@ public abstract class AvroMessageDeserializerBase : IKafkaMessageSerializer public abstract ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract byte[] SerializeKey( diff --git a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializer`1.cs b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializer`1.cs index 995d763e6..09335a541 100644 --- a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializer`1.cs +++ b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageDeserializer`1.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; using Confluent.SchemaRegistry.Serdes; @@ -25,16 +26,18 @@ public class AvroMessageDeserializer : AvroMessageDeserializerBase public override ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) => + MessageSerializationContext context, + CancellationToken cancellationToken = default) => throw new NotSupportedException(); /// public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { - var buffer = await messageStream.ReadAllAsync().ConfigureAwait(false); + var buffer = await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false); var deserialized = await DeserializeAsync(buffer, MessageComponentType.Value, context) .ConfigureAwait(false); diff --git a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializerBase.cs b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializerBase.cs index 8a31d0e32..f58f2d459 100644 --- a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializerBase.cs +++ b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializerBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Confluent.SchemaRegistry; using Confluent.SchemaRegistry.Serdes; @@ -33,13 +34,15 @@ public abstract class AvroMessageSerializerBase : IKafkaMessageSerializer public abstract ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract byte[] SerializeKey( diff --git a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializer`1.cs b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializer`1.cs index c15c7989c..e1c7b5e1a 100644 --- a/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializer`1.cs +++ b/src/Silverback.Integration.Kafka.SchemaRegistry/Messaging/Serialization/AvroMessageSerializer`1.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; using Confluent.SchemaRegistry.Serdes; @@ -25,9 +26,10 @@ public class AvroMessageSerializer : AvroMessageSerializerBase public override async ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { - var buffer = await SerializeAsync(message, MessageComponentType.Value, context) + var buffer = await SerializeAsync(message, MessageComponentType.Value, context, cancellationToken) .ConfigureAwait(false); return buffer == null ? null : new MemoryStream(buffer); @@ -37,9 +39,10 @@ public class AvroMessageSerializer : AvroMessageSerializerBase public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { - var buffer = await messageStream.ReadAllAsync().ConfigureAwait(false); + var buffer = await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false); var deserialized = await DeserializeAsync(buffer, MessageComponentType.Value, context) .ConfigureAwait(false); @@ -80,13 +83,14 @@ private static SerializationContext GetConfluentSerializationContext( private async ValueTask SerializeAsync( object? message, MessageComponentType componentType, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (message == null) return null; if (message is Stream inputStream) - return await inputStream.ReadAllAsync().ConfigureAwait(false); + return await inputStream.ReadAllAsync(cancellationToken).ConfigureAwait(false); if (message is byte[] inputBytes) return inputBytes; diff --git a/src/Silverback.Integration.Kafka/Messaging/Broker/KafkaProducer.cs b/src/Silverback.Integration.Kafka/Messaging/Broker/KafkaProducer.cs index e7d09dae1..6f23b4268 100644 --- a/src/Silverback.Integration.Kafka/Messaging/Broker/KafkaProducer.cs +++ b/src/Silverback.Integration.Kafka/Messaging/Broker/KafkaProducer.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; using Silverback.Diagnostics; @@ -168,25 +169,28 @@ protected override void ProduceCore( }); } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { try { @@ -203,7 +207,7 @@ await messageStream.ReadAllAsync().ConfigureAwait(false), actualEndpointName, GetPartition(headers)); - var deliveryResult = await GetConfluentProducer().ProduceAsync(topicPartition, kafkaMessage) + var deliveryResult = await GetConfluentProducer().ProduceAsync(topicPartition, kafkaMessage, cancellationToken) .ConfigureAwait(false); if (Endpoint.Configuration.ArePersistenceStatusReportsEnabled) @@ -223,24 +227,26 @@ await messageStream.ReadAllAsync().ConfigureAwait(false), } } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError) + onError, + cancellationToken) .ConfigureAwait(false); - /// + /// [SuppressMessage("", "CA1031", Justification = "Exception logged/forwarded")] protected override Task ProduceCoreAsync( object? message, @@ -248,7 +254,8 @@ protected override Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { ProduceCore(message, messageBytes, headers, actualEndpointName, onSuccess, onError); return Task.CompletedTask; diff --git a/src/Silverback.Integration.Kafka/Messaging/KafkaConsumerEndpoint.cs b/src/Silverback.Integration.Kafka/Messaging/KafkaConsumerEndpoint.cs index 88bf0f39c..95b4ac1d6 100644 --- a/src/Silverback.Integration.Kafka/Messaging/KafkaConsumerEndpoint.cs +++ b/src/Silverback.Integration.Kafka/Messaging/KafkaConsumerEndpoint.cs @@ -87,7 +87,7 @@ public KafkaConsumerEndpoint( IEnumerable topicPartitions, KafkaClientConfig? clientConfig = null) : this( - topicPartitions?.Select(topicPartition => new TopicPartitionOffset(topicPartition, Offset.Unset))!, + topicPartitions.Select(topicPartition => new TopicPartitionOffset(topicPartition, Offset.Unset))!, clientConfig) { } diff --git a/src/Silverback.Integration.Kafka/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehavior.cs b/src/Silverback.Integration.Kafka/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehavior.cs index 85a4e7ab1..477eacaf5 100644 --- a/src/Silverback.Integration.Kafka/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehavior.cs +++ b/src/Silverback.Integration.Kafka/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -20,7 +21,10 @@ public class KafkaMessageKeyInitializerProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.BrokerKeyHeaderInitializer; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -31,7 +35,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH context.Envelope.Headers.Add(KafkaMessageHeaders.KafkaMessageKey, key); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } private static string GetKafkaKey(ProducerPipelineContext context) diff --git a/src/Silverback.Integration.Kafka/Messaging/Outbound/Routing/KafkaPartitionResolverProducerBehavior.cs b/src/Silverback.Integration.Kafka/Messaging/Outbound/Routing/KafkaPartitionResolverProducerBehavior.cs index 414085640..a8d63993a 100644 --- a/src/Silverback.Integration.Kafka/Messaging/Outbound/Routing/KafkaPartitionResolverProducerBehavior.cs +++ b/src/Silverback.Integration.Kafka/Messaging/Outbound/Routing/KafkaPartitionResolverProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Confluent.Kafka; using Silverback.Messaging.Broker.Behaviors; @@ -19,7 +20,10 @@ public class KafkaPartitionResolverProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.EndpointNameResolver + 1; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -36,7 +40,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH } } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration.Kafka/Messaging/Serialization/DefaultKafkaMessageSerializer.cs b/src/Silverback.Integration.Kafka/Messaging/Serialization/DefaultKafkaMessageSerializer.cs index cf9b8e31d..bb22adfea 100644 --- a/src/Silverback.Integration.Kafka/Messaging/Serialization/DefaultKafkaMessageSerializer.cs +++ b/src/Silverback.Integration.Kafka/Messaging/Serialization/DefaultKafkaMessageSerializer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -36,15 +37,17 @@ public DefaultKafkaMessageSerializer(IMessageSerializer serializer) public ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) => - _serializer.SerializeAsync(message, messageHeaders, context); + MessageSerializationContext context, + CancellationToken cancellationToken = default) => + _serializer.SerializeAsync(message, messageHeaders, context, cancellationToken); /// public ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) => - _serializer.DeserializeAsync(messageStream, messageHeaders, context); + MessageSerializationContext context, + CancellationToken cancellationToken = default) => + _serializer.DeserializeAsync(messageStream, messageHeaders, context, cancellationToken); /// public byte[] SerializeKey( diff --git a/src/Silverback.Integration.MQTT/Messaging/Broker/MqttProducer.cs b/src/Silverback.Integration.MQTT/Messaging/Broker/MqttProducer.cs index 6729960dd..6170a19a8 100644 --- a/src/Silverback.Integration.MQTT/Messaging/Broker/MqttProducer.cs +++ b/src/Silverback.Integration.MQTT/Messaging/Broker/MqttProducer.cs @@ -146,63 +146,69 @@ protected override void ProduceCore( TaskScheduler.Default); } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { var queuedMessage = new QueuedMessage(messageBytes, headers, actualEndpointName); - await _queueChannel.Writer.WriteAsync(queuedMessage).ConfigureAwait(false); + await _queueChannel.Writer.WriteAsync(queuedMessage, cancellationToken).ConfigureAwait(false); await queuedMessage.TaskCompletionSource.Task.ConfigureAwait(false); return null; } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError) + onError, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { var queuedMessage = new QueuedMessage(messageBytes, headers, actualEndpointName); - await _queueChannel.Writer.WriteAsync(queuedMessage).ConfigureAwait(false); + await _queueChannel.Writer.WriteAsync(queuedMessage, cancellationToken).ConfigureAwait(false); queuedMessage.TaskCompletionSource.Task.ContinueWith( task => diff --git a/src/Silverback.Integration.MQTT/Messaging/Configuration/Mqtt/MqttConsumerEndpointBuilder.cs b/src/Silverback.Integration.MQTT/Messaging/Configuration/Mqtt/MqttConsumerEndpointBuilder.cs index 9a66a0650..4b5c02dc0 100644 --- a/src/Silverback.Integration.MQTT/Messaging/Configuration/Mqtt/MqttConsumerEndpointBuilder.cs +++ b/src/Silverback.Integration.MQTT/Messaging/Configuration/Mqtt/MqttConsumerEndpointBuilder.cs @@ -130,7 +130,7 @@ protected override MqttConsumerEndpoint CreateEndpoint() { var endpoint = new MqttConsumerEndpoint(_topicNames ?? Array.Empty()) { - Configuration = _clientConfig, + Configuration = _clientConfig }; if (_qualityOfServiceLevel != null) diff --git a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer.cs b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer.cs index 16e0653e6..e5c72c48a 100644 --- a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer.cs +++ b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Silverback.Messaging.Messages; @@ -32,7 +33,8 @@ public sealed class NewtonsoftJsonMessageSerializer public override ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -58,7 +60,8 @@ public sealed class NewtonsoftJsonMessageSerializer public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -73,7 +76,7 @@ public sealed class NewtonsoftJsonMessageSerializer if (type == null) throw new MessageSerializerException("Missing type header."); - var buffer = await messageStream.ReadAllAsync().ConfigureAwait(false); + var buffer = await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false); var jsonString = SystemEncoding.GetString(buffer!); var deserializedObject = JsonConvert.DeserializeObject(jsonString, type, Settings); diff --git a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializerBase.cs b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializerBase.cs index 5be125d6f..2767bc7d8 100644 --- a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializerBase.cs +++ b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializerBase.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Silverback.Messaging.Messages; @@ -62,12 +63,14 @@ public abstract class NewtonsoftJsonMessageSerializerBase : IMessageSerializer public abstract ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer`1.cs b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer`1.cs index 5695fa3dc..17a43b30f 100644 --- a/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer`1.cs +++ b/src/Silverback.Integration.Newtonsoft/Messaging/Serialization/NewtonsoftJsonMessageSerializer`1.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using Silverback.Messaging.Messages; @@ -30,7 +31,8 @@ public sealed class NewtonsoftJsonMessageSerializer public override ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (message == null) return ValueTaskFactory.FromResult(null); @@ -50,12 +52,13 @@ public sealed class NewtonsoftJsonMessageSerializer public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (messageStream == null) return (null, _type); - var buffer = await messageStream.ReadAllAsync().ConfigureAwait(false); + var buffer = await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false); var jsonString = SystemEncoding.GetString(buffer!); var deserializedObject = JsonConvert.DeserializeObject(jsonString, _type, Settings); diff --git a/src/Silverback.Integration.RabbitMQ/Messaging/Broker/RabbitProducer.cs b/src/Silverback.Integration.RabbitMQ/Messaging/Broker/RabbitProducer.cs index 8f23ef603..2dc0ee01c 100644 --- a/src/Silverback.Integration.RabbitMQ/Messaging/Broker/RabbitProducer.cs +++ b/src/Silverback.Integration.RabbitMQ/Messaging/Broker/RabbitProducer.cs @@ -142,63 +142,69 @@ protected override void ProduceCore( TaskScheduler.Default); } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { var queuedMessage = new QueuedMessage(messageBytes, headers, actualEndpointName); - await _queueChannel.Writer.WriteAsync(queuedMessage).ConfigureAwait(false); + await _queueChannel.Writer.WriteAsync(queuedMessage, cancellationToken).ConfigureAwait(false); await queuedMessage.TaskCompletionSource.Task.ConfigureAwait(false); return null; } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError) + onError, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { var queuedMessage = new QueuedMessage(messageBytes, headers, actualEndpointName); - await _queueChannel.Writer.WriteAsync(queuedMessage).ConfigureAwait(false); + await _queueChannel.Writer.WriteAsync(queuedMessage, cancellationToken).ConfigureAwait(false); queuedMessage.TaskCompletionSource.Task.ContinueWith( task => diff --git a/src/Silverback.Integration.RabbitMQ/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehavior.cs b/src/Silverback.Integration.RabbitMQ/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehavior.cs index c5e01a2a9..736e1a9ed 100644 --- a/src/Silverback.Integration.RabbitMQ/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehavior.cs +++ b/src/Silverback.Integration.RabbitMQ/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -19,7 +20,10 @@ public class RabbitRoutingKeyInitializerProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.BrokerKeyHeaderInitializer; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -32,7 +36,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH context.Envelope.Headers.AddOrReplace(RabbitMessageHeaders.RoutingKey, key); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration.Testing/Testing/InboundSpyBrokerBehavior.cs b/src/Silverback.Integration.Testing/Testing/InboundSpyBrokerBehavior.cs index 5a75d0f87..09cf1013c 100644 --- a/src/Silverback.Integration.Testing/Testing/InboundSpyBrokerBehavior.cs +++ b/src/Silverback.Integration.Testing/Testing/InboundSpyBrokerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -31,7 +32,10 @@ public InboundSpyBrokerBehavior(IntegrationSpy integrationSpy) public int SortIndex => BrokerBehaviorsSortIndexes.Consumer.Publisher - 1; /// - public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -39,7 +43,7 @@ public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler if (context.Envelope is IInboundEnvelope inboundEnvelope) _integrationSpy.AddInboundEnvelope(inboundEnvelope); - return next(context); + return next(context, cancellationToken); } } } diff --git a/src/Silverback.Integration.Testing/Testing/OutboundSpyBrokerBehavior.cs b/src/Silverback.Integration.Testing/Testing/OutboundSpyBrokerBehavior.cs index 933dd4ac3..6df21d033 100644 --- a/src/Silverback.Integration.Testing/Testing/OutboundSpyBrokerBehavior.cs +++ b/src/Silverback.Integration.Testing/Testing/OutboundSpyBrokerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -31,14 +32,17 @@ public OutboundSpyBrokerBehavior(IntegrationSpy integrationSpy) public int SortIndex => int.MinValue; /// - public Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); _integrationSpy.AddOutboundEnvelope(context.Envelope); - return next(context); + return next(context, cancellationToken); } } } diff --git a/src/Silverback.Integration.Testing/Testing/RawInboundSpyBrokerBehavior.cs b/src/Silverback.Integration.Testing/Testing/RawInboundSpyBrokerBehavior.cs index 533629040..1da356835 100644 --- a/src/Silverback.Integration.Testing/Testing/RawInboundSpyBrokerBehavior.cs +++ b/src/Silverback.Integration.Testing/Testing/RawInboundSpyBrokerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -31,14 +32,17 @@ public RawInboundSpyBrokerBehavior(IntegrationSpy integrationSpy) public int SortIndex => int.MinValue; /// - public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); _integrationSpy.AddRawInboundEnvelope(context.Envelope); - return next(context); + return next(context, cancellationToken); } } } diff --git a/src/Silverback.Integration.Testing/Testing/RawOutboundSpyBrokerBehavior.cs b/src/Silverback.Integration.Testing/Testing/RawOutboundSpyBrokerBehavior.cs index b8fb061c3..2cba434c4 100644 --- a/src/Silverback.Integration.Testing/Testing/RawOutboundSpyBrokerBehavior.cs +++ b/src/Silverback.Integration.Testing/Testing/RawOutboundSpyBrokerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -31,14 +32,17 @@ public RawOutboundSpyBrokerBehavior(IntegrationSpy integrationSpy) public int SortIndex => int.MaxValue; /// - public Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); _integrationSpy.AddRawOutboundEnvelope(context.Envelope); - return next(context); + return next(context, cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehavior.cs index 9262b7745..f2703740f 100644 --- a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -21,17 +22,20 @@ public class BinaryFileHandlerConsumerBehavior : IConsumerBehavior /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); - context.Envelope = await HandleAsync(context.Envelope).ConfigureAwait(false); + context.Envelope = await HandleAsync(context.Envelope, cancellationToken).ConfigureAwait(false); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } - private static async Task HandleAsync(IRawInboundEnvelope envelope) + private static async Task HandleAsync( + IRawInboundEnvelope envelope, + CancellationToken cancellationToken = default) { if (envelope.Endpoint.Serializer is BinaryFileMessageSerializer || envelope.Endpoint.Serializer.GetType().IsGenericType && @@ -48,7 +52,8 @@ private static async Task HandleAsync(IRawInboundEnvelope e var (deserializedObject, deserializedType) = await BinaryFileMessageSerializer.Default.DeserializeAsync( envelope.RawMessage, envelope.Headers, - MessageSerializationContext.Empty) + MessageSerializationContext.Empty, + cancellationToken) .ConfigureAwait(false); // Create typed message for easier specific subscription diff --git a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerProducerBehavior.cs b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerProducerBehavior.cs index c5758815b..2c9663d69 100644 --- a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileHandlerProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -21,7 +22,10 @@ public class BinaryFileHandlerProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.BinaryFileHandler; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -32,11 +36,12 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH context.Envelope.RawMessage = await _binaryFileMessageSerializer.SerializeAsync( context.Envelope.Message, context.Envelope.Headers, - MessageSerializationContext.Empty) + MessageSerializationContext.Empty, + cancellationToken) .ConfigureAwait(false); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer.cs b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer.cs index 2649c2964..be24af3cd 100644 --- a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer.cs +++ b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Messaging.Serialization; @@ -30,7 +31,8 @@ public class BinaryFileMessageSerializer : IMessageSerializer public ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -62,7 +64,8 @@ public class BinaryFileMessageSerializer : IMessageSerializer public ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); diff --git a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer`1.cs b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer`1.cs index 2d50eaa8a..464f67b02 100644 --- a/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer`1.cs +++ b/src/Silverback.Integration/Messaging/BinaryFiles/BinaryFileMessageSerializer`1.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Messaging.Serialization; @@ -31,7 +32,8 @@ public class BinaryFileMessageSerializer : IMessageSerializer public ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -59,7 +61,8 @@ public class BinaryFileMessageSerializer : IMessageSerializer public ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { var binaryFileMessage = new TModel { Content = messageStream }; diff --git a/src/Silverback.Integration/Messaging/Broker/Behaviors/ConsumerBehaviorHandler.cs b/src/Silverback.Integration/Messaging/Broker/Behaviors/ConsumerBehaviorHandler.cs index ab598e875..ddb6be307 100644 --- a/src/Silverback.Integration/Messaging/Broker/Behaviors/ConsumerBehaviorHandler.cs +++ b/src/Silverback.Integration/Messaging/Broker/Behaviors/ConsumerBehaviorHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Broker.Behaviors @@ -11,5 +12,10 @@ namespace Silverback.Messaging.Broker.Behaviors /// /// The context that is passed along the consumer behaviors pipeline. /// - public delegate Task ConsumerBehaviorHandler(ConsumerPipelineContext context); + /// + /// A used to cancel the operation. + /// + public delegate Task ConsumerBehaviorHandler( + ConsumerPipelineContext context, + CancellationToken cancellationToken = default); } diff --git a/src/Silverback.Integration/Messaging/Broker/Behaviors/IConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Broker/Behaviors/IConsumerBehavior.cs index d60fefde0..19d2c7311 100644 --- a/src/Silverback.Integration/Messaging/Broker/Behaviors/IConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Broker/Behaviors/IConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Broker.Behaviors @@ -20,9 +21,15 @@ public interface IConsumerBehavior : IBrokerBehavior, ISorted /// /// The next behavior in the pipeline. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next); + Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Broker/Behaviors/IProducerBehavior.cs b/src/Silverback.Integration/Messaging/Broker/Behaviors/IProducerBehavior.cs index f73dd0200..958535972 100644 --- a/src/Silverback.Integration/Messaging/Broker/Behaviors/IProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Broker/Behaviors/IProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Broker.Behaviors @@ -20,9 +21,15 @@ public interface IProducerBehavior : IBrokerBehavior, ISorted /// /// The next behavior in the pipeline. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next); + Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Broker/Behaviors/MessageIdInitializerProducerBehavior.cs b/src/Silverback.Integration/Messaging/Broker/Behaviors/MessageIdInitializerProducerBehavior.cs index ab688b85c..963ce61a9 100644 --- a/src/Silverback.Integration/Messaging/Broker/Behaviors/MessageIdInitializerProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Broker/Behaviors/MessageIdInitializerProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Util; @@ -16,14 +17,17 @@ public class MessageIdInitializerProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.MessageIdInitializer; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); MessageIdProvider.EnsureMessageIdIsInitialized(context.Envelope.Headers); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Broker/Behaviors/ProducerBehaviorHandler.cs b/src/Silverback.Integration/Messaging/Broker/Behaviors/ProducerBehaviorHandler.cs index 535ee93e5..8a728fb29 100644 --- a/src/Silverback.Integration/Messaging/Broker/Behaviors/ProducerBehaviorHandler.cs +++ b/src/Silverback.Integration/Messaging/Broker/Behaviors/ProducerBehaviorHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; namespace Silverback.Messaging.Broker.Behaviors @@ -11,5 +12,10 @@ namespace Silverback.Messaging.Broker.Behaviors /// /// The context that is passed along the producer behaviors pipeline. /// - public delegate Task ProducerBehaviorHandler(ProducerPipelineContext context); + /// + /// A used to cancel the operation. + /// + public delegate Task ProducerBehaviorHandler( + ProducerPipelineContext context, + CancellationToken cancellationToken = default); } diff --git a/src/Silverback.Integration/Messaging/Broker/Consumer.cs b/src/Silverback.Integration/Messaging/Broker/Consumer.cs index 35d075975..915d6c0e1 100644 --- a/src/Silverback.Integration/Messaging/Broker/Consumer.cs +++ b/src/Silverback.Integration/Messaging/Broker/Consumer.cs @@ -581,14 +581,18 @@ private Task WaitUntilConsumingStoppedAsync() => "Waiting until consumer stops...", "Consumer stopped."); - private Task ExecutePipelineAsync(ConsumerPipelineContext context, int stepIndex = 0) + private Task ExecutePipelineAsync( + ConsumerPipelineContext context, + int stepIndex = 0, + CancellationToken cancellationToken = default) { if (_behaviors.Count == 0 || stepIndex >= _behaviors.Count) return Task.CompletedTask; return _behaviors[stepIndex].HandleAsync( context, - nextContext => ExecutePipelineAsync(nextContext, stepIndex + 1)); + (nextContext, ctx) => ExecutePipelineAsync(nextContext, stepIndex + 1, ctx), + cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/Broker/IProducer.cs b/src/Silverback.Integration/Messaging/Broker/IProducer.cs index 23fec6f4a..a791f8b18 100644 --- a/src/Silverback.Integration/Messaging/Broker/IProducer.cs +++ b/src/Silverback.Integration/Messaging/Broker/IProducer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -318,13 +319,17 @@ void RawProduce( /// /// The optional message headers. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. /// Task ProduceAsync( object? message, - IReadOnlyCollection? headers = null); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default); /// /// Publishes the specified message. @@ -332,11 +337,16 @@ void RawProduce( /// /// The envelope containing the message to be delivered. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. /// - Task ProduceAsync(IOutboundEnvelope envelope); + Task ProduceAsync( + IOutboundEnvelope envelope, + CancellationToken cancellationToken = default); /// /// Publishes the specified message. @@ -357,6 +367,9 @@ void RawProduce( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -365,7 +378,8 @@ Task ProduceAsync( object? message, IReadOnlyCollection? headers, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message. @@ -383,6 +397,9 @@ Task ProduceAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -390,7 +407,8 @@ Task ProduceAsync( Task ProduceAsync( IOutboundEnvelope envelope, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -401,13 +419,17 @@ Task ProduceAsync( /// /// The optional message headers. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. /// Task RawProduceAsync( byte[]? messageContent, - IReadOnlyCollection? headers = null); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -418,13 +440,17 @@ Task ProduceAsync( /// /// The optional message headers. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. /// Task RawProduceAsync( Stream? messageStream, - IReadOnlyCollection? headers = null); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -438,6 +464,9 @@ Task ProduceAsync( /// /// The optional message headers. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. @@ -445,7 +474,8 @@ Task ProduceAsync( Task RawProduceAsync( string actualEndpointName, byte[]? messageContent, - IReadOnlyCollection? headers = null); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -459,6 +489,9 @@ Task ProduceAsync( /// /// The optional message headers. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// of the produced record. @@ -466,7 +499,8 @@ Task ProduceAsync( Task RawProduceAsync( string actualEndpointName, Stream? messageStream, - IReadOnlyCollection? headers = null); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -487,6 +521,9 @@ Task ProduceAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -495,7 +532,8 @@ Task RawProduceAsync( byte[]? messageContent, IReadOnlyCollection? headers, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -516,6 +554,9 @@ Task RawProduceAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -524,7 +565,8 @@ Task RawProduceAsync( Stream? messageStream, IReadOnlyCollection? headers, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -548,6 +590,9 @@ Task RawProduceAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -557,7 +602,8 @@ Task RawProduceAsync( byte[]? messageContent, IReadOnlyCollection? headers, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message as-is, without sending it through the behaviors pipeline. @@ -581,6 +627,9 @@ Task RawProduceAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -590,6 +639,7 @@ Task RawProduceAsync( Stream? messageStream, IReadOnlyCollection? headers, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Broker/Producer.cs b/src/Silverback.Integration/Messaging/Broker/Producer.cs index d5113a0af..1ab23b78a 100644 --- a/src/Silverback.Integration/Messaging/Broker/Producer.cs +++ b/src/Silverback.Integration/Messaging/Broker/Producer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -137,7 +138,7 @@ public async Task DisconnectAsync() async () => await ExecutePipelineAsync( new ProducerPipelineContext(envelope, this, _serviceProvider), - finalContext => + (finalContext, _) => { brokerMessageIdentifier = ProduceCore( finalContext.Envelope.Message, @@ -184,7 +185,7 @@ public void Produce( { await ExecutePipelineAsync( new ProducerPipelineContext(envelope, this, _serviceProvider), - finalContext => + (finalContext, _) => { ProduceCore( finalContext.Envelope.Message, @@ -342,14 +343,17 @@ public void RawProduce( onError.Invoke(exception); }); - /// + /// public Task ProduceAsync( object? message, - IReadOnlyCollection? headers = null) => - ProduceAsync(_envelopeFactory.CreateOutboundEnvelope(message, headers, Endpoint)); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default) => + ProduceAsync(_envelopeFactory.CreateOutboundEnvelope(message, headers, Endpoint), cancellationToken); - /// - public async Task ProduceAsync(IOutboundEnvelope envelope) + /// + public async Task ProduceAsync( + IOutboundEnvelope envelope, + CancellationToken cancellationToken = default) { try { @@ -357,20 +361,22 @@ public void RawProduce( await ExecutePipelineAsync( new ProducerPipelineContext(envelope, this, _serviceProvider), - async finalContext => + async (finalContext, _) => { brokerMessageIdentifier = await ProduceCoreAsync( finalContext.Envelope.Message, finalContext.Envelope.RawMessage, finalContext.Envelope.Headers, - finalContext.Envelope.ActualEndpointName) + finalContext.Envelope.ActualEndpointName, + cancellationToken) .ConfigureAwait(false); ((RawOutboundEnvelope)finalContext.Envelope).BrokerMessageIdentifier = brokerMessageIdentifier; _logger.LogProduced(envelope); - }).ConfigureAwait(false); + }, + cancellationToken: cancellationToken).ConfigureAwait(false); return brokerMessageIdentifier; } @@ -381,22 +387,24 @@ await ExecutePipelineAsync( } } - /// + /// public Task ProduceAsync( object? message, IReadOnlyCollection? headers, Action onSuccess, - Action onError) => - ProduceAsync(_envelopeFactory.CreateOutboundEnvelope(message, headers, Endpoint), onSuccess, onError); + Action onError, + CancellationToken cancellationToken = default) => + ProduceAsync(_envelopeFactory.CreateOutboundEnvelope(message, headers, Endpoint), onSuccess, onError, cancellationToken); - /// + /// public async Task ProduceAsync( IOutboundEnvelope envelope, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ExecutePipelineAsync( new ProducerPipelineContext(envelope, this, _serviceProvider), - finalContext => ProduceCoreAsync( + (finalContext, _) => ProduceCoreAsync( finalContext.Envelope.Message, finalContext.Envelope.RawMessage, finalContext.Envelope.Headers, @@ -411,25 +419,30 @@ await ExecutePipelineAsync( { _logger.LogProduceError(envelope, exception); onError.Invoke(exception); - })).ConfigureAwait(false); + }, + cancellationToken), + cancellationToken: cancellationToken).ConfigureAwait(false); - /// + /// public Task RawProduceAsync( byte[]? messageContent, - IReadOnlyCollection? headers = null) => - RawProduceAsync(Endpoint.Name, messageContent, headers); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default) => + RawProduceAsync(Endpoint.Name, messageContent, headers, cancellationToken); - /// + /// public Task RawProduceAsync( Stream? messageStream, - IReadOnlyCollection? headers = null) => - RawProduceAsync(Endpoint.Name, messageStream, headers); + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default) => + RawProduceAsync(Endpoint.Name, messageStream, headers, cancellationToken); - /// + /// public async Task RawProduceAsync( string actualEndpointName, byte[]? messageContent, - IReadOnlyCollection? headers = null) + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default) { try { @@ -437,7 +450,8 @@ await ExecutePipelineAsync( null, messageContent, headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); _logger.LogProduced(Endpoint, actualEndpointName, headers, brokerMessageIdentifier); @@ -451,11 +465,12 @@ await ExecutePipelineAsync( } } - /// + /// public async Task RawProduceAsync( string actualEndpointName, Stream? messageStream, - IReadOnlyCollection? headers = null) + IReadOnlyCollection? headers = null, + CancellationToken cancellationToken = default) { try { @@ -463,7 +478,8 @@ await ExecutePipelineAsync( null, messageStream, headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); _logger.LogProduced(Endpoint, actualEndpointName, headers, brokerMessageIdentifier); @@ -477,39 +493,44 @@ await ExecutePipelineAsync( } } - /// + /// public Task RawProduceAsync( byte[]? messageContent, IReadOnlyCollection? headers, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => RawProduceAsync( Endpoint.Name, messageContent, headers, onSuccess, - onError); + onError, + cancellationToken); - /// + /// public Task RawProduceAsync( Stream? messageStream, IReadOnlyCollection? headers, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => RawProduceAsync( Endpoint.Name, messageStream, headers, onSuccess, - onError); + onError, + cancellationToken); - /// + /// public Task RawProduceAsync( string actualEndpointName, byte[]? messageContent, IReadOnlyCollection? headers, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => ProduceCoreAsync( null, messageContent, @@ -524,15 +545,17 @@ public Task RawProduceAsync( { _logger.LogProduceError(Endpoint, actualEndpointName, headers, exception); onError.Invoke(exception); - }); + }, + cancellationToken); - /// + /// public Task RawProduceAsync( string actualEndpointName, Stream? messageStream, IReadOnlyCollection? headers, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => ProduceCoreAsync( null, messageStream, @@ -547,7 +570,8 @@ public Task RawProduceAsync( { _logger.LogProduceError(Endpoint, actualEndpointName, headers, exception); onError.Invoke(exception); - }); + }, + cancellationToken); /// /// Connects to the message broker. @@ -572,8 +596,8 @@ public Task RawProduceAsync( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -601,8 +625,8 @@ public Task RawProduceAsync( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -634,8 +658,8 @@ public Task RawProduceAsync( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -672,8 +696,8 @@ protected abstract void ProduceCore( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -706,8 +730,8 @@ protected abstract void ProduceCore( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -719,6 +743,9 @@ protected abstract void ProduceCore( /// /// The actual endpoint to produce to. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// message identifier assigned by the broker (the Kafka offset or similar). @@ -727,7 +754,8 @@ protected abstract void ProduceCore( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName); + string actualEndpointName, + CancellationToken cancellationToken = default); /// /// Publishes the specified message and returns its identifier. @@ -736,8 +764,8 @@ protected abstract void ProduceCore( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -749,6 +777,9 @@ protected abstract void ProduceCore( /// /// The actual endpoint to produce to. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// message identifier assigned by the broker (the Kafka offset or similar). @@ -757,7 +788,8 @@ protected abstract void ProduceCore( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName); + string actualEndpointName, + CancellationToken cancellationToken = default); /// /// Publishes the specified message and returns its identifier. @@ -770,8 +802,8 @@ protected abstract void ProduceCore( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -789,6 +821,9 @@ protected abstract void ProduceCore( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -799,7 +834,8 @@ protected abstract Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); /// /// Publishes the specified message and returns its identifier. @@ -812,8 +848,8 @@ protected abstract Task ProduceCoreAsync( /// The message to be delivered before serialization. This might be null if /// , /// , - /// or - /// have been used to + /// or + /// have been used to /// produce. /// /// @@ -831,6 +867,9 @@ protected abstract Task ProduceCoreAsync( /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The will complete as /// soon as the message is enqueued. @@ -841,19 +880,22 @@ protected abstract Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError); + Action onError, + CancellationToken cancellationToken = default); private Task ExecutePipelineAsync( ProducerPipelineContext context, ProducerBehaviorHandler finalAction, - int stepIndex = 0) + int stepIndex = 0, + CancellationToken cancellationToken = default) { if (_behaviors.Count <= 0 || stepIndex >= _behaviors.Count) - return finalAction(context); + return finalAction(context, cancellationToken); return _behaviors[stepIndex].HandleAsync( context, - nextContext => ExecutePipelineAsync(nextContext, finalAction, stepIndex + 1)); + (nextContext, ctx) => ExecutePipelineAsync(nextContext, finalAction, stepIndex + 1, ctx), + cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/Diagnostics/ActivityConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Diagnostics/ActivityConsumerBehavior.cs index 9d85070fb..6a5cf1b93 100644 --- a/src/Silverback.Integration/Messaging/Diagnostics/ActivityConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Diagnostics/ActivityConsumerBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -33,7 +34,8 @@ public ActivityConsumerBehavior(IActivityEnricherFactory activityEnricherFactory /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -43,7 +45,7 @@ public async Task HandleAsync( _activityEnricherFactory.GetActivityEnricher(context.Envelope.Endpoint) .EnrichInboundActivity(activity, context); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Diagnostics/ActivityProducerBehavior.cs b/src/Silverback.Integration/Messaging/Diagnostics/ActivityProducerBehavior.cs index 1319022b9..90ba4b398 100644 --- a/src/Silverback.Integration/Messaging/Diagnostics/ActivityProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Diagnostics/ActivityProducerBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -30,7 +31,10 @@ public ActivityProducerBehavior(IActivityEnricherFactory activityEnricherFactory public int SortIndex => BrokerBehaviorsSortIndexes.Producer.Activity; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -39,7 +43,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH _activityEnricherFactory .GetActivityEnricher(context.Envelope.Endpoint) .EnrichOutboundActivity(activity, context); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehavior.cs index 0a590ce7e..a4e877abe 100644 --- a/src/Silverback.Integration/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -35,14 +36,15 @@ public FatalExceptionLoggerConsumerBehavior( /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); try { - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/Silverback.Integration/Messaging/Encryption/DecryptorConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Encryption/DecryptorConsumerBehavior.cs index d7b258974..591ea12e2 100644 --- a/src/Silverback.Integration/Messaging/Encryption/DecryptorConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Encryption/DecryptorConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -32,7 +33,8 @@ public DecryptorConsumerBehavior(ISilverbackCryptoStreamFactory streamFactory) /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -54,7 +56,7 @@ public async Task HandleAsync( keyIdentifier); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Encryption/EncryptorProducerBehavior.cs b/src/Silverback.Integration/Messaging/Encryption/EncryptorProducerBehavior.cs index 9fe6bea59..3bb877a94 100644 --- a/src/Silverback.Integration/Messaging/Encryption/EncryptorProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Encryption/EncryptorProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -30,7 +31,10 @@ public EncryptorProducerBehavior(ISilverbackCryptoStreamFactory streamFactory) public int SortIndex => BrokerBehaviorsSortIndexes.Producer.Encryptor; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -50,7 +54,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH } } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperConsumerBehavior.cs index 1acaa15d0..8d321d94b 100644 --- a/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -31,7 +32,8 @@ public CustomHeadersMapperConsumerBehavior(ICustomHeadersMappings? mappings) /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -41,7 +43,7 @@ public async Task HandleAsync( _mappings.Revert(context.Envelope.Headers); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperProducerBehavior.cs b/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperProducerBehavior.cs index 4f6fd3b84..61cb726f5 100644 --- a/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Headers/CustomHeadersMapperProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -29,14 +30,17 @@ public CustomHeadersMapperProducerBehavior(ICustomHeadersMappings? mappings) public int SortIndex => BrokerBehaviorsSortIndexes.Producer.CustomHeadersMapper; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); _mappings?.Apply(context.Envelope.Headers); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Headers/HeadersReaderConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Headers/HeadersReaderConsumerBehavior.cs index c9a56ddc5..7b5c78566 100644 --- a/src/Silverback.Integration/Messaging/Headers/HeadersReaderConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Headers/HeadersReaderConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -19,7 +20,8 @@ public class HeadersReaderConsumerBehavior : IConsumerBehavior /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -31,7 +33,7 @@ public async Task HandleAsync( inboundEnvelope.Headers); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Headers/HeadersWriterProducerBehavior.cs b/src/Silverback.Integration/Messaging/Headers/HeadersWriterProducerBehavior.cs index a321c2326..da2a629f5 100644 --- a/src/Silverback.Integration/Messaging/Headers/HeadersWriterProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Headers/HeadersWriterProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -17,7 +18,10 @@ public class HeadersWriterProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.HeadersWriter; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -25,7 +29,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH HeaderAttributeHelper.GetHeaders(context.Envelope.Message) .ForEach(header => context.Envelope.Headers.AddOrReplace(header.Name, header.Value)); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPoliciesHelper.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPoliciesHelper.cs index 170e53701..203fb3851 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPoliciesHelper.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPoliciesHelper.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -12,7 +13,8 @@ internal static class ErrorPoliciesHelper { public static async Task ApplyErrorPoliciesAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken = default) { var failedAttempts = context.Consumer.IncrementFailedAttempts(context.Envelope); @@ -25,7 +27,7 @@ public static async Task ApplyErrorPoliciesAsync( return false; return await errorPolicyImplementation - .HandleErrorAsync(context, exception) + .HandleErrorAsync(context, exception, cancellationToken) .ConfigureAwait(false); } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyChain.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyChain.cs index f90e39772..1371b22bb 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyChain.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyChain.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -90,7 +91,10 @@ public ErrorPolicyChainImplementation( public bool CanHandle(ConsumerPipelineContext context, Exception exception) => true; - public Task HandleErrorAsync(ConsumerPipelineContext context, Exception exception) + public Task HandleErrorAsync( + ConsumerPipelineContext context, + Exception exception, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(exception, nameof(exception)); @@ -98,7 +102,7 @@ public Task HandleErrorAsync(ConsumerPipelineContext context, Exception ex var nextPolicy = _policies.FirstOrDefault(policy => policy.CanHandle(context, exception)); if (nextPolicy != null) - return nextPolicy.HandleErrorAsync(context, exception); + return nextPolicy.HandleErrorAsync(context, exception, cancellationToken); _logger.LogInboundTrace(IntegrationLogEvents.PolicyChainCompleted, context.Envelope); diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyImplementation.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyImplementation.cs index 888031689..597e2faa9 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyImplementation.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/ErrorPolicyImplementation.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -146,12 +147,15 @@ public virtual bool CanHandle(ConsumerPipelineContext context, Exception excepti } /// - public async Task HandleErrorAsync(ConsumerPipelineContext context, Exception exception) + public async Task HandleErrorAsync( + ConsumerPipelineContext context, + Exception exception, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(exception, nameof(exception)); - var result = await ApplyPolicyAsync(context, exception).ConfigureAwait(false); + var result = await ApplyPolicyAsync(context, exception, cancellationToken).ConfigureAwait(false); if (_messageToPublishFactory == null) return result; @@ -162,7 +166,7 @@ public async Task HandleErrorAsync(ConsumerPipelineContext context, Except using var scope = _serviceProvider.CreateScope(); await scope.ServiceProvider.GetRequiredService() - .PublishAsync(message) + .PublishAsync(message, cancellationToken) .ConfigureAwait(false); return result; @@ -177,10 +181,16 @@ await scope.ServiceProvider.GetRequiredService() /// /// The exception that was thrown during the processing. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// action that the consumer should perform (e.g. skip the message or stop consuming). /// - protected abstract Task ApplyPolicyAsync(ConsumerPipelineContext context, Exception exception); + protected abstract Task ApplyPolicyAsync( + ConsumerPipelineContext context, + Exception exception, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/IErrorPolicyImplementation.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/IErrorPolicyImplementation.cs index c5147765d..a1f607162 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/IErrorPolicyImplementation.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/IErrorPolicyImplementation.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Inbound.Transaction; @@ -38,11 +39,17 @@ public interface IErrorPolicyImplementation /// /// The exception that was thrown during the processing. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains a /// boolean flag indicating whether the error was handled. If false is returned the exception will /// be rethrown and the consumer will stop. /// - Task HandleErrorAsync(ConsumerPipelineContext context, Exception exception); + Task HandleErrorAsync( + ConsumerPipelineContext context, + Exception exception, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/MoveMessageErrorPolicy.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/MoveMessageErrorPolicy.cs index a726fe774..b07214fb2 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/MoveMessageErrorPolicy.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/MoveMessageErrorPolicy.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -130,21 +131,25 @@ public override bool CanHandle(ConsumerPipelineContext context, Exception except protected override async Task ApplyPolicyAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(exception, nameof(exception)); _logger.LogMoved(context.Envelope, _endpoint); - await PublishToNewEndpointAsync(context.Envelope, exception).ConfigureAwait(false); + await PublishToNewEndpointAsync(context.Envelope, exception, cancellationToken).ConfigureAwait(false); await context.TransactionManager.RollbackAsync(exception, true).ConfigureAwait(false); return true; } - private async Task PublishToNewEndpointAsync(IRawInboundEnvelope envelope, Exception exception) + private async Task PublishToNewEndpointAsync( + IRawInboundEnvelope envelope, + Exception exception, + CancellationToken cancellationToken) { var outboundEnvelope = envelope is IInboundEnvelope deserializedEnvelope @@ -159,7 +164,7 @@ envelope is IInboundEnvelope deserializedEnvelope _transformationAction?.Invoke(outboundEnvelope, exception); - await _producer.ProduceAsync(outboundEnvelope).ConfigureAwait(false); + await _producer.ProduceAsync(outboundEnvelope, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/RetryErrorPolicy.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/RetryErrorPolicy.cs index 8fa0c1f81..b8affe48a 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/RetryErrorPolicy.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/RetryErrorPolicy.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -102,7 +103,8 @@ public RetryErrorPolicyImplementation( protected override async Task ApplyPolicyAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(exception, nameof(exception)); diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/SkipMessageErrorPolicy.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/SkipMessageErrorPolicy.cs index 0630ad5bd..3d120dedd 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/SkipMessageErrorPolicy.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/SkipMessageErrorPolicy.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -55,7 +56,8 @@ public SkipMessageErrorPolicyImplementation( protected override async Task ApplyPolicyAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(exception, nameof(exception)); @@ -69,7 +71,9 @@ protected override async Task ApplyPolicyAsync( } [SuppressMessage("", "CA1031", Justification = Justifications.ExceptionLogged)] - private async Task TryRollbackAsync(ConsumerPipelineContext context, Exception exception) + private async Task TryRollbackAsync( + ConsumerPipelineContext context, + Exception exception) { try { diff --git a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/StopConsumerErrorPolicy.cs b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/StopConsumerErrorPolicy.cs index 386aa52c3..664052440 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/StopConsumerErrorPolicy.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ErrorHandling/StopConsumerErrorPolicy.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -52,7 +53,8 @@ public StopConsumerErrorPolicyImplementation( protected override Task ApplyPolicyAsync( ConsumerPipelineContext context, - Exception exception) => + Exception exception, + CancellationToken cancellationToken = default) => Task.FromResult(false); } } diff --git a/src/Silverback.Integration/Messaging/Inbound/ExactlyOnce/ExactlyOnceGuardConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Inbound/ExactlyOnce/ExactlyOnceGuardConsumerBehavior.cs index 38efb3010..ec5e9dc89 100644 --- a/src/Silverback.Integration/Messaging/Inbound/ExactlyOnce/ExactlyOnceGuardConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Inbound/ExactlyOnce/ExactlyOnceGuardConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -33,13 +34,14 @@ public ExactlyOnceGuardConsumerBehavior(IInboundLogger public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); if (!await CheckIsAlreadyProcessedAsync(context).ConfigureAwait(false)) - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } private async Task CheckIsAlreadyProcessedAsync(ConsumerPipelineContext context) diff --git a/src/Silverback.Integration/Messaging/Inbound/PublisherConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Inbound/PublisherConsumerBehavior.cs index b2efd3c50..1f4aa707c 100644 --- a/src/Silverback.Integration/Messaging/Inbound/PublisherConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Inbound/PublisherConsumerBehavior.cs @@ -42,7 +42,10 @@ public PublisherConsumerBehavior(IInboundLogger logge public int SortIndex => BrokerBehaviorsSortIndexes.Consumer.Publisher; /// - public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public async Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -78,7 +81,7 @@ await PublishEnvelopeAsync(context, context.Envelope.Endpoint.ThrowIfUnhandled) await PublishEnvelopeAsync(context, throwIfUnhandled).ConfigureAwait(false); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } private static async Task PublishEnvelopeAsync( diff --git a/src/Silverback.Integration/Messaging/Inbound/Transaction/TransactionHandlerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Inbound/Transaction/TransactionHandlerConsumerBehavior.cs index 98d0b722a..b560f6e62 100644 --- a/src/Silverback.Integration/Messaging/Inbound/Transaction/TransactionHandlerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Inbound/Transaction/TransactionHandlerConsumerBehavior.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -36,7 +37,10 @@ public TransactionHandlerConsumerBehavior(IInboundLogger [SuppressMessage("", "CA2000", Justification = "ServiceScope is disposed with the Context")] - public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public async Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -53,7 +57,7 @@ public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorH _logger.LogProcessing(context.Envelope); - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); if (context.Sequence == null) { @@ -75,7 +79,7 @@ public async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorH // handled and it's safer to stop the consumer) if (context.Sequence != null) { - await context.Sequence.AbortAsync(SequenceAbortReason.Error, exception) + await context.Sequence.AbortAsync(SequenceAbortReason.Error, exception, cancellationToken) .ConfigureAwait(false); if (context.Sequence.Length > 0) @@ -90,7 +94,7 @@ await context.Sequence.AbortAsync(SequenceAbortReason.Error, exception) throw; } - if (!await HandleExceptionAsync(context, exception).ConfigureAwait(false)) + if (!await HandleExceptionAsync(context, exception, cancellationToken).ConfigureAwait(false)) throw; } } @@ -260,13 +264,14 @@ private void LogSequenceCompletedTrace(ConsumerPipelineContext context, ISequenc private async Task HandleExceptionAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken) { _logger.LogProcessingError(context.Envelope, exception); try { - bool handled = await ErrorPoliciesHelper.ApplyErrorPoliciesAsync(context, exception) + bool handled = await ErrorPoliciesHelper.ApplyErrorPoliciesAsync(context, exception, cancellationToken) .ConfigureAwait(false); if (!handled) diff --git a/src/Silverback.Integration/Messaging/Outbound/DefaultProduceStrategy.cs b/src/Silverback.Integration/Messaging/Outbound/DefaultProduceStrategy.cs index 72cb0d267..91d5eba45 100644 --- a/src/Silverback.Integration/Messaging/Outbound/DefaultProduceStrategy.cs +++ b/src/Silverback.Integration/Messaging/Outbound/DefaultProduceStrategy.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Messaging.Broker; @@ -27,11 +28,13 @@ public DefaultProduceStrategyImplementation(IBrokerCollection brokerCollection) _brokerCollection = brokerCollection; } - public Task ProduceAsync(IOutboundEnvelope envelope) + public Task ProduceAsync( + IOutboundEnvelope envelope, + CancellationToken cancellationToken = default) { Check.NotNull(envelope, nameof(envelope)); - return _brokerCollection.GetProducer(envelope.Endpoint).ProduceAsync(envelope); + return _brokerCollection.GetProducer(envelope.Endpoint).ProduceAsync(envelope, cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/Outbound/Enrichers/MessageEnricherProducerBehavior.cs b/src/Silverback.Integration/Messaging/Outbound/Enrichers/MessageEnricherProducerBehavior.cs index 5b818b7ef..3770ba8a7 100644 --- a/src/Silverback.Integration/Messaging/Outbound/Enrichers/MessageEnricherProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Outbound/Enrichers/MessageEnricherProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -16,7 +17,10 @@ public class MessageEnricherProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.MessageEnricher; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -24,9 +28,14 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH foreach (var enricher in context.Envelope.Endpoint.MessageEnrichers) { enricher.Enrich(context.Envelope); + + if (cancellationToken.IsCancellationRequested) + { + break; + } } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Outbound/IProduceStrategyImplementation.cs b/src/Silverback.Integration/Messaging/Outbound/IProduceStrategyImplementation.cs index 0f2b6594a..23498c381 100644 --- a/src/Silverback.Integration/Messaging/Outbound/IProduceStrategyImplementation.cs +++ b/src/Silverback.Integration/Messaging/Outbound/IProduceStrategyImplementation.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -17,9 +18,12 @@ public interface IProduceStrategyImplementation /// /// The containing the message to be produced. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task ProduceAsync(IOutboundEnvelope envelope); + Task ProduceAsync(IOutboundEnvelope envelope, CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Outbound/Routing/EndpointNameResolverProducerBehavior.cs b/src/Silverback.Integration/Messaging/Outbound/Routing/EndpointNameResolverProducerBehavior.cs index f5961b509..3c0aa80f4 100644 --- a/src/Silverback.Integration/Messaging/Outbound/Routing/EndpointNameResolverProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Outbound/Routing/EndpointNameResolverProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -18,7 +19,10 @@ public class EndpointNameResolverProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.EndpointNameResolver; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -35,7 +39,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH outboundEnvelope.ActualEndpointName = actualEndpointName; } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundQueueProducer.cs b/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundQueueProducer.cs index 4aae13eb0..f73f113dc 100644 --- a/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundQueueProducer.cs +++ b/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundQueueProducer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker; @@ -89,25 +90,28 @@ protected override void ProduceCore( Action onError) => throw new InvalidOperationException("Only asynchronous operations are supported."); - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName) + actualEndpointName, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { await _queueWriter.WriteAsync( message, @@ -120,31 +124,34 @@ await _queueWriter.WriteAsync( return null; } - /// + /// protected override async Task ProduceCoreAsync( object? message, Stream? messageStream, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError) + onError, + cancellationToken) .ConfigureAwait(false); - /// + /// protected override async Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { Check.NotNull(onSuccess, nameof(onSuccess)); Check.NotNull(onError, nameof(onError)); diff --git a/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundRouterBehavior.cs b/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundRouterBehavior.cs index c1a193e5f..2dcaf864f 100644 --- a/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundRouterBehavior.cs +++ b/src/Silverback.Integration/Messaging/Outbound/Routing/OutboundRouterBehavior.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Messaging.Messages; @@ -57,11 +58,14 @@ public OutboundRouterBehavior( public int SortIndex => IntegrationBehaviorsSortIndexes.OutboundRouter; /// - public async Task> HandleAsync(object message, MessageHandler next) + public async Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(next, nameof(next)); - var wasRouted = await WrapAndRepublishRoutedMessageAsync(message).ConfigureAwait(false); + var wasRouted = await WrapAndRepublishRoutedMessageAsync(message, cancellationToken).ConfigureAwait(false); // The routed message is discarded because it has been republished // as OutboundEnvelope and will be normally subscribable @@ -69,10 +73,12 @@ public OutboundRouterBehavior( if (wasRouted) return Array.Empty(); - return await next(message).ConfigureAwait(false); + return await next(message, cancellationToken).ConfigureAwait(false); } - private async Task WrapAndRepublishRoutedMessageAsync(object message) + private async Task WrapAndRepublishRoutedMessageAsync( + object message, + CancellationToken cancellationToken) { if (message is IOutboundEnvelope) return false; @@ -84,7 +90,7 @@ private async Task WrapAndRepublishRoutedMessageAsync(object message) await routesCollection .SelectMany(route => CreateOutboundEnvelopes(message, route)) - .ForEachAsync(envelope => _publisher.PublishAsync(envelope)) + .ForEachAsync(envelope => _publisher.PublishAsync(envelope, cancellationToken)) .ConfigureAwait(false); return true; diff --git a/src/Silverback.Integration/Messaging/Outbound/Routing/ProduceBehavior.cs b/src/Silverback.Integration/Messaging/Outbound/Routing/ProduceBehavior.cs index d93732fc3..2192dfa32 100644 --- a/src/Silverback.Integration/Messaging/Outbound/Routing/ProduceBehavior.cs +++ b/src/Silverback.Integration/Messaging/Outbound/Routing/ProduceBehavior.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Messaging.Publishing; @@ -36,17 +37,20 @@ public ProduceBehavior(IServiceProvider serviceProvider) public int SortIndex => IntegrationBehaviorsSortIndexes.OutboundProducer; /// - public async Task> HandleAsync(object message, MessageHandler next) + public async Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(next, nameof(next)); if (message is IOutboundEnvelope envelope) { _produceStrategyImplementation ??= envelope.Endpoint.Strategy.Build(_serviceProvider); - await _produceStrategyImplementation.ProduceAsync(envelope).ConfigureAwait(false); + await _produceStrategyImplementation.ProduceAsync(envelope, cancellationToken).ConfigureAwait(false); } - return await next(message).ConfigureAwait(false); + return await next(message, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/IOutboxWorker.cs b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/IOutboxWorker.cs index 75a80c502..0a661e2ea 100644 --- a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/IOutboxWorker.cs +++ b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/IOutboxWorker.cs @@ -14,12 +14,12 @@ public interface IOutboxWorker /// /// Processes the outbox. /// - /// + /// /// A to observe while waiting for the task to complete. /// /// /// A that represents the long running operations. /// - Task ProcessQueueAsync(CancellationToken stoppingToken); + Task ProcessQueueAsync(CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxProduceStrategy.cs b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxProduceStrategy.cs index e674bd15d..43f1b637f 100644 --- a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxProduceStrategy.cs +++ b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxProduceStrategy.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Silverback.Diagnostics; @@ -40,7 +41,9 @@ public OutboxProduceStrategyImplementation( _logger = logger; } - public Task ProduceAsync(IOutboundEnvelope envelope) + public Task ProduceAsync( + IOutboundEnvelope envelope, + CancellationToken cancellationToken = default) { Check.NotNull(envelope, nameof(envelope)); @@ -51,7 +54,7 @@ public Task ProduceAsync(IOutboundEnvelope envelope) if (producer == null || !producer.Endpoint.Equals(envelope.Endpoint)) producer = _producer = _outboundQueueBroker.GetProducer(envelope.Endpoint); - return producer.ProduceAsync(envelope); + return producer.ProduceAsync(envelope, cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxWorker.cs b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxWorker.cs index f7f33b456..98c23665e 100644 --- a/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxWorker.cs +++ b/src/Silverback.Integration/Messaging/Outbound/TransactionalOutbox/OutboxWorker.cs @@ -77,12 +77,12 @@ public OutboxWorker( /// [SuppressMessage("", "CA1031", Justification = Justifications.ExceptionLogged)] - public async Task ProcessQueueAsync(CancellationToken stoppingToken) + public async Task ProcessQueueAsync(CancellationToken cancellationToken = default) { try { using var scope = _serviceScopeFactory.CreateScope(); - await ProcessQueueAsync(scope.ServiceProvider, stoppingToken).ConfigureAwait(false); + await ProcessQueueAsync(scope.ServiceProvider, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -111,6 +111,9 @@ public async Task ProcessQueueAsync(CancellationToken stoppingToken) /// /// The callback to be invoked when the produce fails. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// @@ -120,13 +123,15 @@ protected virtual Task ProduceMessageAsync( IProducerEndpoint endpoint, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => _brokerCollection.GetProducer(endpoint).RawProduceAsync( actualEndpointName, content, headers, onSuccess, - onError); + onError, + cancellationToken); private static async Task AcknowledgeAllAsync( IOutboxReader outboxReader, @@ -141,7 +146,7 @@ await outboxReader.AcknowledgeAsync(messages.Where(message => !failedMessages.Co private async Task ProcessQueueAsync( IServiceProvider serviceProvider, - CancellationToken stoppingToken) + CancellationToken cancellationToken) { _logger.LogReadingMessagesFromOutbox(_batchSize); @@ -172,10 +177,11 @@ await ProcessMessageAsync( outboxMessages[i], failedMessages, outboxReader, - serviceProvider) + serviceProvider, + cancellationToken) .ConfigureAwait(false); - if (stoppingToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) break; } } @@ -196,7 +202,8 @@ private async Task ProcessMessageAsync( OutboxStoredMessage message, ConcurrentBag failedMessages, IOutboxReader outboxReader, - IServiceProvider serviceProvider) + IServiceProvider serviceProvider, + CancellationToken cancellationToken) { try { @@ -222,7 +229,8 @@ await ProduceMessageAsync( message.Headers, new LoggingEndpoint(message.EndpointName)), exception); - }) + }, + cancellationToken) .ConfigureAwait(false); } catch (Exception ex) diff --git a/src/Silverback.Integration/Messaging/Sequences/Chunking/ChunkStream.cs b/src/Silverback.Integration/Messaging/Sequences/Chunking/ChunkStream.cs index 4b1eca420..09746c88d 100644 --- a/src/Silverback.Integration/Messaging/Sequences/Chunking/ChunkStream.cs +++ b/src/Silverback.Integration/Messaging/Sequences/Chunking/ChunkStream.cs @@ -108,7 +108,7 @@ public override async Task ReadAsync( { if (await _asyncEnumerator.MoveNextAsync().ConfigureAwait(false)) { - _currentChunk = await _asyncEnumerator.Current.RawMessage.ReadAllAsync().ConfigureAwait(false); + _currentChunk = await _asyncEnumerator.Current.RawMessage.ReadAllAsync(cancellationToken).ConfigureAwait(false); _position = 0; } else diff --git a/src/Silverback.Integration/Messaging/Sequences/ISequence.cs b/src/Silverback.Integration/Messaging/Sequences/ISequence.cs index 568660d2e..9ae58b3e0 100644 --- a/src/Silverback.Integration/Messaging/Sequences/ISequence.cs +++ b/src/Silverback.Integration/Messaging/Sequences/ISequence.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker; using Silverback.Messaging.Broker.Behaviors; @@ -158,10 +159,16 @@ Task AddAsync( /// /// The exception that caused the abort, if an exception was thrown. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// - Task AbortAsync(SequenceAbortReason reason, Exception? exception = null); + Task AbortAsync( + SequenceAbortReason reason, + Exception? exception = null, + CancellationToken cancellationToken = default); /// /// Gets the identifiers of the messages belonging to the sequence. diff --git a/src/Silverback.Integration/Messaging/Sequences/RawSequencerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Sequences/RawSequencerConsumerBehavior.cs index 4e6b11bf1..835379906 100644 --- a/src/Silverback.Integration/Messaging/Sequences/RawSequencerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Sequences/RawSequencerConsumerBehavior.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -36,12 +37,13 @@ public RawSequencerConsumerBehavior( /// protected override Task PublishSequenceAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); - var processingTask = Task.Run(async () => await next(context).ConfigureAwait(false)); + var processingTask = Task.Run(async () => await next(context, cancellationToken).ConfigureAwait(false), cancellationToken); context.ProcessingTask ??= processingTask; diff --git a/src/Silverback.Integration/Messaging/Sequences/SequenceBase`1.cs b/src/Silverback.Integration/Messaging/Sequences/SequenceBase`1.cs index 69b58810d..84bb6c9e8 100644 --- a/src/Silverback.Integration/Messaging/Sequences/SequenceBase`1.cs +++ b/src/Silverback.Integration/Messaging/Sequences/SequenceBase`1.cs @@ -212,7 +212,10 @@ public Task AddAsync( } /// - public Task AbortAsync(SequenceAbortReason reason, Exception? exception = null) + public Task AbortAsync( + SequenceAbortReason reason, + Exception? exception = null, + CancellationToken cancellationToken = default) { if (reason == SequenceAbortReason.None) throw new ArgumentOutOfRangeException(nameof(reason), reason, "Reason not specified."); @@ -224,7 +227,7 @@ public Task AbortAsync(SequenceAbortReason reason, Exception? exception = null) "The exception must be specified if the reason is Error."); } - return AbortCoreAsync(reason, exception); + return AbortCoreAsync(reason, exception, cancellationToken); } /// @@ -534,7 +537,10 @@ private void ResetTimeout() }); } - private async Task AbortCoreAsync(SequenceAbortReason reason, Exception? exception) + private async Task AbortCoreAsync( + SequenceAbortReason reason, + Exception? exception, + CancellationToken cancellationToken) { bool alreadyAborted; @@ -583,7 +589,7 @@ private async Task AbortCoreAsync(SequenceAbortReason reason, Exception? excepti } await Context.SequenceStore.RemoveAsync(SequenceId).ConfigureAwait(false); - if (await RollbackTransactionAndNotifyProcessingCompletedAsync(exception).ConfigureAwait(false)) + if (await RollbackTransactionAndNotifyProcessingCompletedAsync(exception, cancellationToken).ConfigureAwait(false)) LogAbort(); _streamProvider.AbortIfPending(); @@ -593,7 +599,9 @@ private async Task AbortCoreAsync(SequenceAbortReason reason, Exception? excepti } [SuppressMessage("", "CA1031", Justification = "Exception notified")] - private async Task RollbackTransactionAndNotifyProcessingCompletedAsync(Exception? exception) + private async Task RollbackTransactionAndNotifyProcessingCompletedAsync( + Exception? exception, + CancellationToken cancellationToken) { var done = true; @@ -602,7 +610,7 @@ private async Task RollbackTransactionAndNotifyProcessingCompletedAsync(Ex switch (AbortReason) { case SequenceAbortReason.Error: - if (!await ErrorPoliciesHelper.ApplyErrorPoliciesAsync(Context, exception!) + if (!await ErrorPoliciesHelper.ApplyErrorPoliciesAsync(Context, exception!, cancellationToken) .ConfigureAwait(false)) { await Context.TransactionManager.RollbackAsync(exception).ConfigureAwait(false); diff --git a/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehavior.cs index de0df4541..24208fcc0 100644 --- a/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehavior.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -33,13 +34,16 @@ public SequencerConsumerBehavior( public override int SortIndex => BrokerBehaviorsSortIndexes.Consumer.Sequencer; /// - public override async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public override async Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { var rawSequence = Check.NotNull(context, nameof(context)).Sequence as ISequenceImplementation; try { - await base.HandleAsync(context, next).ConfigureAwait(false); + await base.HandleAsync(context, next, cancellationToken).ConfigureAwait(false); // Abort all pending sequences if the current message doesn't belong to a sequence if (context.Sequence == null) @@ -59,11 +63,12 @@ await context.SequenceStore /// protected override Task PublishSequenceAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken) { Check.NotNull(next, nameof(next)); - return next(context); + return next(context, cancellationToken); } } } diff --git a/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehaviorBase.cs b/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehaviorBase.cs index d6cb65173..0b00a5e2e 100644 --- a/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehaviorBase.cs +++ b/src/Silverback.Integration/Messaging/Sequences/SequencerConsumerBehaviorBase.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -51,7 +52,10 @@ protected SequencerConsumerBehaviorBase( public abstract int SortIndex { get; } /// - public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) + public virtual async Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -62,7 +66,7 @@ public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerB if (sequenceReader == null) { - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); return; } @@ -78,7 +82,7 @@ public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerB // GetSequenceAsync and AddAsync while (true) { - sequence = await GetSequenceAsync(context, next, sequenceReader).ConfigureAwait(false); + sequence = await GetSequenceAsync(context, next, sequenceReader, cancellationToken).ConfigureAwait(false); if (sequence == null) return; @@ -124,12 +128,16 @@ public virtual async Task HandleAsync(ConsumerPipelineContext context, ConsumerB /// /// The next behavior in the pipeline. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. /// protected abstract Task PublishSequenceAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next); + ConsumerBehaviorHandler next, + CancellationToken cancellationToken); /// /// When overridden in a derived class awaits for the sequence to be processed by the other twin behavior. @@ -221,7 +229,8 @@ private static void StartActivityIfNeeded(ISequence sequence) private async Task GetSequenceAsync( ConsumerPipelineContext context, ConsumerBehaviorHandler next, - ISequenceReader sequenceReader) + ISequenceReader sequenceReader, + CancellationToken cancellationToken) { var sequence = await sequenceReader.GetSequenceAsync(context).ConfigureAwait(false); @@ -240,7 +249,7 @@ private static void StartActivityIfNeeded(ISequence sequence) { StartActivityIfNeeded(sequence); - await PublishSequenceAsync(context, next).ConfigureAwait(false); + await PublishSequenceAsync(context, next, cancellationToken).ConfigureAwait(false); if (context.ProcessingTask != null) MonitorProcessingTaskPrematureCompletion(context.ProcessingTask, sequence); diff --git a/src/Silverback.Integration/Messaging/Sequences/SequencerProducerBehavior.cs b/src/Silverback.Integration/Messaging/Sequences/SequencerProducerBehavior.cs index c1ea112e0..d50b23311 100644 --- a/src/Silverback.Integration/Messaging/Sequences/SequencerProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Sequences/SequencerProducerBehavior.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Util; @@ -37,7 +38,10 @@ public SequencerProducerBehavior(IEnumerable sequenceWriters) public int SortIndex => BrokerBehaviorsSortIndexes.Producer.Sequencer; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -47,18 +51,21 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH if (!sequenceWriter.CanHandle(context.Envelope)) continue; + if (cancellationToken.IsCancellationRequested) + break; + var envelopesEnumerable = sequenceWriter.ProcessMessageAsync(context.Envelope); await foreach (var envelope in envelopesEnumerable.ConfigureAwait(false)) { var newContext = new ProducerPipelineContext(envelope, context.Producer, context.ServiceProvider); - await next(newContext).ConfigureAwait(false); + await next(newContext, cancellationToken).ConfigureAwait(false); } return; } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Serialization/DeserializerConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Serialization/DeserializerConsumerBehavior.cs index 7883090dd..3685b6bf2 100644 --- a/src/Silverback.Integration/Messaging/Serialization/DeserializerConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Serialization/DeserializerConsumerBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -34,12 +35,13 @@ public DeserializerConsumerBehavior(IInboundLogger /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); - var newEnvelope = await DeserializeAsync(context).ConfigureAwait(false); + var newEnvelope = await DeserializeAsync(context, cancellationToken).ConfigureAwait(false); if (newEnvelope == null) { @@ -49,10 +51,12 @@ public async Task HandleAsync( context.Envelope = newEnvelope; - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } - private static async Task DeserializeAsync(ConsumerPipelineContext context) + private static async Task DeserializeAsync( + ConsumerPipelineContext context, + CancellationToken cancellationToken) { var envelope = context.Envelope; @@ -63,7 +67,8 @@ public async Task HandleAsync( envelope.Endpoint.Serializer.DeserializeAsync( envelope.RawMessage, envelope.Headers, - new MessageSerializationContext(envelope.Endpoint, envelope.ActualEndpointName)) + new MessageSerializationContext(envelope.Endpoint, envelope.ActualEndpointName), + cancellationToken) .ConfigureAwait(false); envelope.Headers.AddIfNotExists( diff --git a/src/Silverback.Integration/Messaging/Serialization/IMessageSerializer.cs b/src/Silverback.Integration/Messaging/Serialization/IMessageSerializer.cs index 9b262f416..2b75cb4f3 100644 --- a/src/Silverback.Integration/Messaging/Serialization/IMessageSerializer.cs +++ b/src/Silverback.Integration/Messaging/Serialization/IMessageSerializer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -31,6 +32,9 @@ public interface IMessageSerializer /// /// The context information. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// with the serialized message. @@ -38,7 +42,8 @@ public interface IMessageSerializer ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// /// Deserializes the byte array back into a message object. @@ -52,6 +57,9 @@ public interface IMessageSerializer /// /// The context information. /// + /// + /// A used to cancel the operation. + /// /// /// A representing the asynchronous operation. The task result contains the /// deserialized message (or null when the input is null or empty) and the type of the message. @@ -59,6 +67,7 @@ public interface IMessageSerializer ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer.cs b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer.cs index 119207559..138e4d00e 100644 --- a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer.cs +++ b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Util; @@ -31,7 +32,8 @@ public sealed class JsonMessageSerializer : JsonMessageSerializerBase, IEquatabl public override ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -56,7 +58,8 @@ public sealed class JsonMessageSerializer : JsonMessageSerializerBase, IEquatabl public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { Check.NotNull(messageHeaders, nameof(messageHeaders)); @@ -71,7 +74,7 @@ public sealed class JsonMessageSerializer : JsonMessageSerializerBase, IEquatabl if (type == null) throw new MessageSerializerException("Missing type header."); - var deserializedObject = await JsonSerializer.DeserializeAsync(messageStream, type, Options) + var deserializedObject = await JsonSerializer.DeserializeAsync(messageStream, type, Options, cancellationToken) .ConfigureAwait(false) ?? throw new MessageSerializerException( "The deserialization returned null."); diff --git a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializerBase.cs b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializerBase.cs index 0d2711c9a..7f973e206 100644 --- a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializerBase.cs +++ b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializerBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; @@ -27,12 +28,14 @@ public abstract class JsonMessageSerializerBase : IMessageSerializer public abstract ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); /// public abstract ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context); + MessageSerializationContext context, + CancellationToken cancellationToken = default); } } diff --git a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer`1.cs b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer`1.cs index 0a63bdbe5..577691429 100644 --- a/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer`1.cs +++ b/src/Silverback.Integration/Messaging/Serialization/JsonMessageSerializer`1.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Util; @@ -30,7 +31,8 @@ public sealed class JsonMessageSerializer public override ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (message == null) return ValueTaskFactory.FromResult(null); @@ -49,7 +51,8 @@ public sealed class JsonMessageSerializer public override async ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (messageStream == null) return (null, _type); @@ -57,7 +60,7 @@ public sealed class JsonMessageSerializer if (messageStream.CanSeek && messageStream.Length == 0) return (null, _type); - var deserializedObject = await JsonSerializer.DeserializeAsync(messageStream, _type, Options) + var deserializedObject = await JsonSerializer.DeserializeAsync(messageStream, _type, Options, cancellationToken) .ConfigureAwait(false) ?? throw new MessageSerializerException("The deserialization returned null."); diff --git a/src/Silverback.Integration/Messaging/Serialization/NullMessageHandlingStrategy.cs b/src/Silverback.Integration/Messaging/Serialization/NullMessageHandlingStrategy.cs index 6c34e1803..2a421f86f 100644 --- a/src/Silverback.Integration/Messaging/Serialization/NullMessageHandlingStrategy.cs +++ b/src/Silverback.Integration/Messaging/Serialization/NullMessageHandlingStrategy.cs @@ -24,6 +24,6 @@ public enum NullMessageHandlingStrategy /// /// Silently skip the null message. /// - Skip = 1, + Skip = 1 } } diff --git a/src/Silverback.Integration/Messaging/Serialization/SerializerProducerBehavior.cs b/src/Silverback.Integration/Messaging/Serialization/SerializerProducerBehavior.cs index ff666c4d6..c130de338 100644 --- a/src/Silverback.Integration/Messaging/Serialization/SerializerProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Serialization/SerializerProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -17,7 +18,10 @@ public class SerializerProducerBehavior : IProducerBehavior public int SortIndex => BrokerBehaviorsSortIndexes.Producer.Serializer; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -28,7 +32,8 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH await context.Envelope.Endpoint.Serializer.SerializeAsync( context.Envelope.Message, context.Envelope.Headers, - new MessageSerializationContext(context.Envelope.Endpoint)) + new MessageSerializationContext(context.Envelope.Endpoint), + cancellationToken) .ConfigureAwait(false); } else if (context.Envelope.Message.GetType().GenericTypeArguments.Length == 1) @@ -38,7 +43,7 @@ await context.Envelope.Endpoint.Serializer.SerializeAsync( context.Envelope.Message.GetType().GenericTypeArguments[0].AssemblyQualifiedName); } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Validation/ValidatorConsumerBehavior.cs b/src/Silverback.Integration/Messaging/Validation/ValidatorConsumerBehavior.cs index 2c97a666d..62c637fec 100644 --- a/src/Silverback.Integration/Messaging/Validation/ValidatorConsumerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Validation/ValidatorConsumerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -34,7 +35,8 @@ public ValidatorConsumerBehavior(IInboundLogger logge /// public async Task HandleAsync( ConsumerPipelineContext context, - ConsumerBehaviorHandler next) + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -53,7 +55,7 @@ context.Envelope is IInboundEnvelope deserializeEnvelope && } } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Silverback.Integration/Messaging/Validation/ValidatorProducerBehavior.cs b/src/Silverback.Integration/Messaging/Validation/ValidatorProducerBehavior.cs index 460ff62ef..78767e102 100644 --- a/src/Silverback.Integration/Messaging/Validation/ValidatorProducerBehavior.cs +++ b/src/Silverback.Integration/Messaging/Validation/ValidatorProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Diagnostics; using Silverback.Messaging.Broker.Behaviors; @@ -30,7 +31,10 @@ public ValidatorProducerBehavior(IOutboundLogger logg public int SortIndex => BrokerBehaviorsSortIndexes.Producer.Validator; /// - public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public async Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { Check.NotNull(context, nameof(context)); Check.NotNull(next, nameof(next)); @@ -48,7 +52,7 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH } } - await next(context).ConfigureAwait(false); + await next(context, cancellationToken).ConfigureAwait(false); } } } diff --git a/tests/Silverback.Core.EfCore30.Tests/TestTypes/TestDbContext.cs b/tests/Silverback.Core.EfCore30.Tests/TestTypes/TestDbContext.cs index fd577838d..59e0f3940 100644 --- a/tests/Silverback.Core.EfCore30.Tests/TestTypes/TestDbContext.cs +++ b/tests/Silverback.Core.EfCore30.Tests/TestTypes/TestDbContext.cs @@ -44,6 +44,7 @@ public override Task SaveChangesAsync( CancellationToken cancellationToken = default) => _eventsPublisher.ExecuteSaveTransactionAsync( () => - base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken)); + base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken), + cancellationToken); } } diff --git a/tests/Silverback.Core.Model.Tests/Messaging/Publishing/EventPublisherTests.cs b/tests/Silverback.Core.Model.Tests/Messaging/Publishing/EventPublisherTests.cs index 1a1c82793..768bf6a97 100644 --- a/tests/Silverback.Core.Model.Tests/Messaging/Publishing/EventPublisherTests.cs +++ b/tests/Silverback.Core.Model.Tests/Messaging/Publishing/EventPublisherTests.cs @@ -2,6 +2,8 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -25,7 +27,20 @@ public EventPublisherTests() .AddFakeLogger() .AddSilverback() .UseModel() - .AddDelegateSubscriber((TestEvent _) => _receivedMessages++)); + .AddDelegateSubscriber((TestEvent _, CancellationToken ct) => + { + if (ct.IsCancellationRequested) + { + throw new OperationCanceledException(); + } + + return _receivedMessages++; + }) + .AddDelegateSubscriber(async (TestLongEvent _, CancellationToken ct) => + { + await Task.Delay(5000, ct); + return 0; + })); _publisher = serviceProvider.CreateScope().ServiceProvider.GetRequiredService(); } @@ -38,6 +53,25 @@ public async Task PublishAsync_Event_Published() _receivedMessages.Should().Be(1); } + [Fact] + public async Task PublishAsync_CanceledToken_ExceptionThrown() + { + Func act = () => _publisher.PublishAsync(new TestEvent(), new CancellationToken(true)); + + await act.Should().ThrowAsync().WithInnerException(typeof(OperationCanceledException)); + _receivedMessages.Should().Be(0); + } + + [Fact] + public async Task PublishAsync_CanceledTokenAfter3Secs_ExceptionThrown() + { + var cancellationTokenSource = new CancellationTokenSource(); + cancellationTokenSource.CancelAfter(3000); + Func act = () => _publisher.PublishAsync(new TestLongEvent(), cancellationTokenSource.Token); + + await act.Should().ThrowAsync(); + } + [Fact] public void Publish_Event_Published() { diff --git a/tests/Silverback.Core.Model.Tests/Messaging/Publishing/QueryPublisherTests.cs b/tests/Silverback.Core.Model.Tests/Messaging/Publishing/QueryPublisherTests.cs index 86d663423..7d65486c1 100644 --- a/tests/Silverback.Core.Model.Tests/Messaging/Publishing/QueryPublisherTests.cs +++ b/tests/Silverback.Core.Model.Tests/Messaging/Publishing/QueryPublisherTests.cs @@ -2,6 +2,8 @@ // This code is licensed under MIT license (see LICENSE file for details) using System; +using System.Reflection; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -23,7 +25,15 @@ public QueryPublisherTests() .AddFakeLogger() .AddSilverback() .UseModel() - .AddDelegateSubscriber((TestQuery _) => new[] { 1, 2, 3 })); + .AddDelegateSubscriber((TestQuery _, CancellationToken ct) => + { + if (ct.IsCancellationRequested) + { + throw new OperationCanceledException(); + } + + return new[] { 1, 2, 3 }; + })); _publisher = serviceProvider.CreateScope().ServiceProvider.GetRequiredService(); } @@ -36,6 +46,14 @@ public async Task ExecuteAsync_Query_ResultReturned() result.Should().BeEquivalentTo(new[] { 1, 2, 3 }); } + [Fact] + public async Task ExecuteAsync_CanceledToken_ExceptionThrown() + { + Func act = () => _publisher.ExecuteAsync(new TestQuery(), new CancellationToken(true)); + + await act.Should().ThrowAsync().WithInnerException(typeof(OperationCanceledException)); + } + [Fact] public void Execute_Query_ResultReturned() { diff --git a/tests/Silverback.Core.Model.Tests/TestTypes/Messages/TestLongEvent.cs b/tests/Silverback.Core.Model.Tests/TestTypes/Messages/TestLongEvent.cs new file mode 100644 index 000000000..c9f2d7760 --- /dev/null +++ b/tests/Silverback.Core.Model.Tests/TestTypes/Messages/TestLongEvent.cs @@ -0,0 +1,11 @@ +// Copyright (c) 2020 Sergio Aquilini +// This code is licensed under MIT license (see LICENSE file for details) + +using Silverback.Messaging.Messages; + +namespace Silverback.Tests.Core.Model.TestTypes.Messages +{ + public class TestLongEvent : IEvent + { + } +} diff --git a/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeMessageBehavior`1.cs b/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeMessageBehavior`1.cs index b4ee65512..7ae83106d 100644 --- a/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeMessageBehavior`1.cs +++ b/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeMessageBehavior`1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Publishing; @@ -17,9 +18,14 @@ public ChangeMessageBehavior(Func changedMessageFactory) _changedMessageFactory = changedMessageFactory; } - public Task> HandleAsync(object message, MessageHandler next) => - next(message is TSourceType + public Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) => + next( + message is TSourceType ? _changedMessageFactory(message) - : message); + : message, + cancellationToken); } } diff --git a/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeTestEventOneContentBehavior.cs b/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeTestEventOneContentBehavior.cs index f45877995..6a552335b 100644 --- a/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeTestEventOneContentBehavior.cs +++ b/tests/Silverback.Core.Tests/TestTypes/Behaviors/ChangeTestEventOneContentBehavior.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Publishing; using Silverback.Tests.Core.TestTypes.Messages; @@ -12,12 +13,15 @@ namespace Silverback.Tests.Core.TestTypes.Behaviors public class ChangeTestEventOneContentBehavior : IBehavior { [SuppressMessage("", "CA1822", Justification = Justifications.CalledBySilverback)] - public Task> HandleAsync(object message, MessageHandler next) + public Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) { if (message is TestEventOne testEventOne) testEventOne.Message = "behavior"; - return next(message); + return next(message, cancellationToken); } } } diff --git a/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestBehavior.cs b/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestBehavior.cs index 3a52b9bc2..30eb84fc5 100644 --- a/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestBehavior.cs +++ b/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestBehavior.cs @@ -2,6 +2,7 @@ // This code is licensed under MIT license (see LICENSE file for details) using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Publishing; @@ -20,13 +21,16 @@ public TestBehavior(IList? calls = null) public int ExitCount { get; private set; } - public Task> HandleAsync(object message, MessageHandler next) + public Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) { _calls?.Add("unsorted"); EnterCount++; - var result = next(message); + var result = next(message, cancellationToken); ExitCount++; diff --git a/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestSortedBehavior.cs b/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestSortedBehavior.cs index bfa48e17f..705fdd5eb 100644 --- a/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestSortedBehavior.cs +++ b/tests/Silverback.Core.Tests/TestTypes/Behaviors/TestSortedBehavior.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Publishing; @@ -20,11 +21,14 @@ public TestSortedBehavior(int sortIndex, IList calls) public int SortIndex { get; } - public Task> HandleAsync(object message, MessageHandler next) + public Task> HandleAsync( + object message, + MessageHandler next, + CancellationToken cancellationToken = default) { _calls.Add(SortIndex.ToString(CultureInfo.InvariantCulture)); - return next(message); + return next(message, cancellationToken); } } } diff --git a/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaBrokerTests.cs b/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaBrokerTests.cs index 4d7b430b3..838094d84 100644 --- a/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaBrokerTests.cs +++ b/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaBrokerTests.cs @@ -201,6 +201,6 @@ public void AddConsumer_DifferentEndpoint_DifferentInstanceIsReturned() consumer2.Should().NotBeSameAs(consumer); } - public void Dispose() => _broker?.Dispose(); + public void Dispose() => _broker.Dispose(); } } diff --git a/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaProducerTests.cs b/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaProducerTests.cs index 604e5b7b6..e7176fe28 100644 --- a/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaProducerTests.cs +++ b/tests/Silverback.Integration.Kafka.Tests/Messaging/Broker/KafkaProducerTests.cs @@ -60,6 +60,6 @@ public void Produce_SomeMessage_EndpointConfigurationIsNotAltered() endpoint.Should().BeEquivalentTo(endpointCopy); } - public void Dispose() => _broker?.Dispose(); + public void Dispose() => _broker.Dispose(); } } diff --git a/tests/Silverback.Integration.Kafka.Tests/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehaviorTests.cs b/tests/Silverback.Integration.Kafka.Tests/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehaviorTests.cs index 3a113fb90..907c92876 100644 --- a/tests/Silverback.Integration.Kafka.Tests/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehaviorTests.cs +++ b/tests/Silverback.Integration.Kafka.Tests/Messaging/Outbound/KafkaMessageKeyInitializerProducerBehaviorTests.cs @@ -33,7 +33,7 @@ public async Task HandleAsync_NoKeyMemberAttributeAndNoMessageId_RandomKafkaKeyI await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); var keyValue = envelope.Headers.GetValue("x-kafka-message-key"); keyValue.Should().NotBeNullOrEmpty(); @@ -59,7 +59,7 @@ public async Task HandleAsync_NoKeyMemberAttribute_MessageIdUsedAsKey() await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-kafka-message-key", "Heidi!")); } @@ -80,7 +80,7 @@ public async Task HandleAsync_SingleKeyMemberAttribute_KeyHeaderIsSet() await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-kafka-message-key", "1")); } @@ -101,7 +101,7 @@ public async Task HandleAsync_MultipleKeyMemberAttributes_KeyHeaderIsSet() await new KafkaMessageKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-kafka-message-key", "One=1,Two=2")); } diff --git a/tests/Silverback.Integration.RabbitMQ.Tests/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehaviorTests.cs b/tests/Silverback.Integration.RabbitMQ.Tests/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehaviorTests.cs index 7dcc4e9d8..c845fa8f5 100644 --- a/tests/Silverback.Integration.RabbitMQ.Tests/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehaviorTests.cs +++ b/tests/Silverback.Integration.RabbitMQ.Tests/Messaging/Outbound/RabbitRoutingKeyInitializerProducerBehaviorTests.cs @@ -33,7 +33,7 @@ public async Task HandleAsync_NoRoutingKeyAttribute_KeyHeaderIsNotSet() await new RabbitRoutingKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().NotContain(h => h.Name == "x-rabbit-routing-key"); } @@ -54,7 +54,7 @@ public async Task HandleAsync_SingleRoutingKeyAttribute_KeyHeaderIsSet() await new RabbitRoutingKeyInitializerProducerBehavior().HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().ContainEquivalentOf(new MessageHeader("x-rabbit-routing-key", "1")); } @@ -79,7 +79,7 @@ public async Task HandleAsync_MultipleRoutingKeyAttributes_KeyHeaderIsSet() envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); await act.Should().ThrowAsync(); } diff --git a/tests/Silverback.Integration.Tests.E2E/Kafka/NullMessageHandlingTests.cs b/tests/Silverback.Integration.Tests.E2E/Kafka/NullMessageHandlingTests.cs index b58055e10..7ec46f45d 100644 --- a/tests/Silverback.Integration.Tests.E2E/Kafka/NullMessageHandlingTests.cs +++ b/tests/Silverback.Integration.Tests.E2E/Kafka/NullMessageHandlingTests.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -358,7 +359,8 @@ private sealed class CustomSerializer : IMessageSerializer public ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { throw new NotSupportedException(); } @@ -366,7 +368,8 @@ private sealed class CustomSerializer : IMessageSerializer public ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { var wrapper = new RawMessage { diff --git a/tests/Silverback.Integration.Tests.E2E/TestTypes/Database/TestDbContext.cs b/tests/Silverback.Integration.Tests.E2E/TestTypes/Database/TestDbContext.cs index 17db20351..1cb449e36 100644 --- a/tests/Silverback.Integration.Tests.E2E/TestTypes/Database/TestDbContext.cs +++ b/tests/Silverback.Integration.Tests.E2E/TestTypes/Database/TestDbContext.cs @@ -49,7 +49,8 @@ public override Task SaveChangesAsync( CancellationToken cancellationToken = default) => _eventsPublisher.ExecuteSaveTransactionAsync( () => - base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken)); + base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken), + cancellationToken); protected override void OnModelCreating(ModelBuilder modelBuilder) { diff --git a/tests/Silverback.Integration.Tests.E2E/TestTypes/RemoveMessageTypeHeaderProducerBehavior.cs b/tests/Silverback.Integration.Tests.E2E/TestTypes/RemoveMessageTypeHeaderProducerBehavior.cs index 605a4ca76..48eae005f 100644 --- a/tests/Silverback.Integration.Tests.E2E/TestTypes/RemoveMessageTypeHeaderProducerBehavior.cs +++ b/tests/Silverback.Integration.Tests.E2E/TestTypes/RemoveMessageTypeHeaderProducerBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; using Silverback.Messaging.Messages; @@ -11,11 +12,14 @@ public class RemoveMessageTypeHeaderProducerBehavior : IProducerBehavior { public int SortIndex => int.MaxValue; - public Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) + public Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) { context.Envelope.Headers.Remove(DefaultMessageHeaders.MessageType); - return next(context); + return next(context, cancellationToken); } } } diff --git a/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehaviorTests.cs index 098496c26..2e3a8d368 100644 --- a/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerConsumerBehaviorTests.cs @@ -40,7 +40,7 @@ public async Task HandleAsync_BinaryFileMessage_BinaryFileMessageReturned() Substitute.For(), Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -69,7 +69,7 @@ public async Task HandleAsync_NoBinaryFileHeaders_EnvelopeUntouched() Substitute.For(), Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -102,7 +102,7 @@ public async Task HandleAsync_EndpointWithBinaryFileMessageSerializer_EnvelopeUn Substitute.For(), Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; diff --git a/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerProducerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerProducerBehaviorTests.cs index a25561ff7..5b3e48496 100644 --- a/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerProducerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/BinaryFiles/BinaryFileHandlerProducerBehaviorTests.cs @@ -33,7 +33,7 @@ public async Task HandleAsync_BinaryFileMessage_RawContentProduced() envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -58,7 +58,7 @@ public async Task HandleAsync_InheritedBinaryFileMessage_RawContentProduced() envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -85,7 +85,7 @@ public async Task HandleAsync_NonBinaryFileMessage_EnvelopeUntouched() envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -110,7 +110,7 @@ public async Task HandleAsync_EndpointWithBinaryFileMessageSerializer_EnvelopeUn envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; diff --git a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityConsumerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityConsumerBehaviorTests.cs index 7eb3e83ba..0dbe7b577 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityConsumerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityConsumerBehaviorTests.cs @@ -47,7 +47,7 @@ public async Task HandleAsync_WithTraceIdHeader_NewActivityStartedAndParentIdIsS Substitute.For(), Substitute.For(), Substitute.For()), - _ => + (_, _) => { Activity.Current.Should().NotBeNull(); Activity.Current!.ParentId.Should() @@ -86,7 +86,7 @@ public async Task HandleAsync_WithoutActivityHeaders_NewActivityIsStarted() Substitute.For(), Substitute.For(), Substitute.For()), - _ => + (_, _) => { Activity.Current.Should().NotBeNull(); Activity.Current!.Id.Should().NotBeNullOrEmpty(); diff --git a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityProducerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityProducerBehaviorTests.cs index b09a94f7a..8836ec28d 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityProducerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/ActivityProducerBehaviorTests.cs @@ -37,7 +37,7 @@ public async Task HandleAsync_StartedActivity_TraceIdHeaderIsSet() await new ActivityProducerBehavior(Substitute.For()).HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().Contain( header => @@ -53,7 +53,7 @@ public async Task HandleAsync_NoStartedActivity_ActivityStartedAndTraceIdHeaderI await new ActivityProducerBehavior(Substitute.For()).HandleAsync( new ProducerPipelineContext(envelope, Substitute.For(), Substitute.For()), - _ => Task.CompletedTask); + (_, _) => Task.CompletedTask); envelope.Headers.Should().Contain( header => header.Name == DefaultMessageHeaders.TraceId && !string.IsNullOrEmpty(header.Value)); diff --git a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehaviorTests.cs index ced9d8fcf..44e2b5f7b 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Diagnostics/FatalExceptionLoggerConsumerBehaviorTests.cs @@ -60,7 +60,7 @@ public async Task HandleAsync_ExceptionThrown_ExceptionLogged() Substitute.For(), Substitute.For(), Substitute.For()), - _ => throw new InvalidCastException()); + (_, _) => throw new InvalidCastException()); } catch { @@ -86,7 +86,7 @@ public async Task HandleAsync_ExceptionThrown_ExceptionRethrown() Substitute.For(), Substitute.For(), Substitute.For()), - _ => throw new InvalidCastException()); + (_, _) => throw new InvalidCastException()); await act.Should().ThrowExactlyAsync() .WithInnerExceptionExactly(); diff --git a/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/OutboundRouterBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/OutboundRouterBehaviorTests.cs index 333d7cec0..f37514b5a 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/OutboundRouterBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/OutboundRouterBehaviorTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -89,7 +90,8 @@ public async Task HandleAsync_Message_CorrectlyRoutedToStaticEndpoint( await _behavior.HandleAsync( message, - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); foreach (var expectedEndpointName in expectedEndpointNames) { @@ -118,7 +120,8 @@ public async Task HandleAsync_Message_CorrectlyRouted() await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); _broker.ProducedMessages.Should().HaveCount(1); } @@ -131,12 +134,14 @@ public async Task HandleAsync_Message_RoutedMessageIsFiltered() var messages = await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); messages.Should().BeEmpty(); messages = await _behavior.HandleAsync( new TestEventTwo(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); messages.Should().NotBeEmpty(); } @@ -148,7 +153,8 @@ public async Task HandleAsync_Message_RoutedMessageIsRepublishedWithoutAutoUnwra await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); _testSubscriber.ReceivedMessages.Should() .BeEmpty(); // Because TestSubscriber discards the envelopes @@ -163,12 +169,14 @@ public async Task HandleAsync_MessagesWithPublishToInternBusOption_RoutedMessage var messages = await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); messages.Should().BeEmpty(); messages = await _behavior.HandleAsync( new TestEventTwo(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); messages.Should().NotBeEmpty(); } @@ -182,7 +190,8 @@ public async Task await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); _testSubscriber.ReceivedMessages.Should().HaveCount(1); _testSubscriber.ReceivedMessages.First().Should().BeOfType(); @@ -200,7 +209,8 @@ public async Task HandleAsync_EnvelopeWithPublishToInternBusOption_OutboundEnvel new TestEventOne(), null, new TestProducerEndpoint("eventOne")), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); messages.Should().HaveCount(1); } @@ -217,7 +227,8 @@ public async Task HandleAsync_UnhandledMessageType_CorrectlyRelayed() await _behavior.HandleAsync( message, - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); _broker.ProducedMessages.Should().HaveCount(1); } @@ -232,13 +243,16 @@ public async Task HandleAsync_MultipleRoutesToMultipleBrokers_CorrectlyRelayed() await _behavior.HandleAsync( new TestEventOne(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( new TestEventThree(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( new TestEventTwo(), - nextMessage => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!); + (nextMessage, _) => Task.FromResult(new[] { nextMessage }.AsReadOnlyCollection())!, + CancellationToken.None); _broker.ProducedMessages.Should().HaveCount(2); _otherBroker.ProducedMessages.Should().HaveCount(1); diff --git a/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/ProduceBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/ProduceBehaviorTests.cs index 025c9c6e5..0dd82bacc 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/ProduceBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Outbound/Routing/ProduceBehaviorTests.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; @@ -62,13 +63,16 @@ public async Task HandleAsync_OutboundMessage_RelayedToEndpoint() await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _outbox.CommitAsync(); var queued = await _outbox.ReadAsync(10); @@ -89,13 +93,16 @@ public async Task HandleAsync_OutboundMessageWithOutboxStrategy_RelayedToOutbox( await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _behavior.HandleAsync( outboundEnvelope, - message => Task.FromResult(new[] { message }.AsReadOnlyCollection())!); + (message, _) => Task.FromResult(new[] { message }.AsReadOnlyCollection())!, + CancellationToken.None); await _outbox.CommitAsync(); var queued = await _outbox.ReadAsync(10); diff --git a/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorConsumerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorConsumerBehaviorTests.cs index 2216578d3..eb767c2a9 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorConsumerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorConsumerBehaviorTests.cs @@ -190,7 +190,7 @@ public async Task HandleAsync_None_WarningIsNotLogged(IIntegrationMessage messag IRawInboundEnvelope? result = null; await new ValidatorConsumerBehavior(_inboundLogger).HandleAsync( ConsumerPipelineContextHelper.CreateSubstitute(envelope, _serviceProvider), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -222,7 +222,7 @@ public async Task HandleAsync_ValidMessage_NoLogAndNoException(MessageValidation IRawInboundEnvelope? result = null; Func act = () => new ValidatorConsumerBehavior(_inboundLogger).HandleAsync( ConsumerPipelineContextHelper.CreateSubstitute(envelope, _serviceProvider), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -252,7 +252,7 @@ public async Task HandleAsync_LogWarning_WarningIsLogged( IRawInboundEnvelope? result = null; await new ValidatorConsumerBehavior(_inboundLogger).HandleAsync( ConsumerPipelineContextHelper.CreateSubstitute(envelope, _serviceProvider), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -283,7 +283,7 @@ public async Task HandleAsync_ThrowException_ExceptionIsThrown() IRawInboundEnvelope? result = null; Func act = () => new ValidatorConsumerBehavior(_inboundLogger).HandleAsync( ConsumerPipelineContextHelper.CreateSubstitute(envelope, _serviceProvider), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; diff --git a/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorProducerBehaviorTests.cs b/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorProducerBehaviorTests.cs index 8a16929a0..a91513aee 100644 --- a/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorProducerBehaviorTests.cs +++ b/tests/Silverback.Integration.Tests/Messaging/Validation/ValidatorProducerBehaviorTests.cs @@ -186,7 +186,7 @@ public async Task HandleAsync_None_WarningIsNotLogged(IIntegrationMessage messag envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -215,7 +215,7 @@ public async Task HandleAsync_ValidMessage_NoLogAndNoException(MessageValidation envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -243,7 +243,7 @@ public async Task HandleAsync_LogWarning_WarningIsLogged( envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; @@ -271,7 +271,7 @@ public async Task HandleAsync_ThrowException_ExceptionIsThrown() envelope, Substitute.For(), Substitute.For()), - context => + (context, _) => { result = context.Envelope; return Task.CompletedTask; diff --git a/tests/Silverback.Integration.Tests/TestTypes/EmptyBehavior.cs b/tests/Silverback.Integration.Tests/TestTypes/EmptyBehavior.cs index e152b4edf..48b00a1e0 100644 --- a/tests/Silverback.Integration.Tests/TestTypes/EmptyBehavior.cs +++ b/tests/Silverback.Integration.Tests/TestTypes/EmptyBehavior.cs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Sergio Aquilini // This code is licensed under MIT license (see LICENSE file for details) +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Broker.Behaviors; @@ -10,8 +11,14 @@ public class EmptyBehavior : IConsumerBehavior, IProducerBehavior { public int SortIndex => 0; - public Task HandleAsync(ConsumerPipelineContext context, ConsumerBehaviorHandler next) => next(context); + public Task HandleAsync( + ConsumerPipelineContext context, + ConsumerBehaviorHandler next, + CancellationToken cancellationToken = default) => next(context, cancellationToken); - public Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorHandler next) => next(context); + public Task HandleAsync( + ProducerPipelineContext context, + ProducerBehaviorHandler next, + CancellationToken cancellationToken = default) => next(context, cancellationToken); } } diff --git a/tests/Silverback.Integration.Tests/TestTypes/TestErrorPolicy.cs b/tests/Silverback.Integration.Tests/TestTypes/TestErrorPolicy.cs index 2308d8cf2..6ebaf4a4e 100644 --- a/tests/Silverback.Integration.Tests/TestTypes/TestErrorPolicy.cs +++ b/tests/Silverback.Integration.Tests/TestTypes/TestErrorPolicy.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using NSubstitute; using Silverback.Diagnostics; @@ -57,7 +58,8 @@ public TestErrorPolicyImplementation( protected override Task ApplyPolicyAsync( ConsumerPipelineContext context, - Exception exception) + Exception exception, + CancellationToken cancellationToken = default) { Applied = true; return Task.FromResult(false); diff --git a/tests/Silverback.Integration.Tests/TestTypes/TestOtherProducer.cs b/tests/Silverback.Integration.Tests/TestTypes/TestOtherProducer.cs index 11a6faf58..db5ebb1b3 100644 --- a/tests/Silverback.Integration.Tests/TestTypes/TestOtherProducer.cs +++ b/tests/Silverback.Integration.Tests/TestTypes/TestOtherProducer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Threading; using System.Threading.Tasks; using NSubstitute; using Silverback.Diagnostics; @@ -87,18 +88,21 @@ protected override void ProduceCore( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName); + actualEndpointName, + cancellationToken); protected override Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { ProducedMessages.Add(new ProducedMessage(messageBytes, headers, Endpoint)); return Task.FromResult(null); @@ -110,14 +114,16 @@ protected override async Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError); + onError, + cancellationToken); protected override Task ProduceCoreAsync( object? message, @@ -125,7 +131,8 @@ protected override Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { ProducedMessages.Add(new ProducedMessage(messageBytes, headers, Endpoint)); onSuccess.Invoke(null); diff --git a/tests/Silverback.Integration.Tests/TestTypes/TestProducer.cs b/tests/Silverback.Integration.Tests/TestTypes/TestProducer.cs index ebd308acd..6ea533e76 100644 --- a/tests/Silverback.Integration.Tests/TestTypes/TestProducer.cs +++ b/tests/Silverback.Integration.Tests/TestTypes/TestProducer.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using NSubstitute; using Silverback.Diagnostics; @@ -91,18 +92,21 @@ protected override void ProduceCore( object? message, Stream? messageStream, IReadOnlyCollection? headers, - string actualEndpointName) => + string actualEndpointName, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, - actualEndpointName); + actualEndpointName, + cancellationToken); protected override Task ProduceCoreAsync( object? message, byte[]? messageBytes, IReadOnlyCollection? headers, - string actualEndpointName) + string actualEndpointName, + CancellationToken cancellationToken = default) { PerformMockProduce(messageBytes, headers); return Task.FromResult(null); @@ -114,14 +118,16 @@ protected override async Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) => + Action onError, + CancellationToken cancellationToken = default) => await ProduceCoreAsync( message, - await messageStream.ReadAllAsync().ConfigureAwait(false), + await messageStream.ReadAllAsync(cancellationToken).ConfigureAwait(false), headers, actualEndpointName, onSuccess, - onError); + onError, + cancellationToken); protected override Task ProduceCoreAsync( object? message, @@ -129,7 +135,8 @@ protected override Task ProduceCoreAsync( IReadOnlyCollection? headers, string actualEndpointName, Action onSuccess, - Action onError) + Action onError, + CancellationToken cancellationToken = default) { PerformMockProduce(messageBytes, headers); onSuccess.Invoke(null); diff --git a/tests/Silverback.Integration.Tests/TestTypes/TestSerializer.cs b/tests/Silverback.Integration.Tests/TestTypes/TestSerializer.cs index f1b7281b9..45ad1e169 100644 --- a/tests/Silverback.Integration.Tests/TestTypes/TestSerializer.cs +++ b/tests/Silverback.Integration.Tests/TestTypes/TestSerializer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using Silverback.Messaging.Messages; using Silverback.Messaging.Serialization; @@ -20,7 +21,8 @@ public class TestSerializer : IMessageSerializer public ValueTask SerializeAsync( object? message, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { throw new NotSupportedException(); } @@ -28,7 +30,8 @@ public class TestSerializer : IMessageSerializer public ValueTask<(object? Message, Type MessageType)> DeserializeAsync( Stream? messageStream, MessageHeaderCollection messageHeaders, - MessageSerializationContext context) + MessageSerializationContext context, + CancellationToken cancellationToken = default) { if (MustFailCount > FailCount) { @@ -36,7 +39,7 @@ public class TestSerializer : IMessageSerializer throw new InvalidOperationException("Test failure"); } - return new JsonMessageSerializer().DeserializeAsync(messageStream, messageHeaders, context); + return new JsonMessageSerializer().DeserializeAsync(messageStream, messageHeaders, context, cancellationToken); } } }