Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for CancellationToken in publishers #202

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

igorgiovannini
Copy link
Contributor

This PR adds support for CancellationToken in publishers (and properly propagates it through the chain) as described in issue #116

@igorgiovannini igorgiovannini marked this pull request as draft July 5, 2023 15:36
@igorgiovannini igorgiovannini marked this pull request as ready for review July 5, 2023 19:26
Copy link
Owner

@BEagle1984 BEagle1984 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again for the contribution and sorry that it took so long to get to a proper review.

/// <returns>
/// The <see cref="ISilverbackBuilder" /> so that additional calls can be chained.
/// </returns>
public static ISilverbackBuilder AddDelegateSubscriber<TMessage>(
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grandissimo! <3

@@ -251,6 +251,9 @@ public override void Dispose()
{
foreach (var lazyStream in _lazyStreams)
{
if (cancellationToken.IsCancellationRequested)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably more correct to throw in this case.

Usually, a cancellable method is supposed to throw the OperationCancelledException when it aborts. This allows the caller to know whether it has actually completed before the cancellation occurred or it had to cancel. (There's a practical cancellationToken.ThrowIfCancellationRequested().)

I didn't carefully check the implications of a throw in here though.

/// <returns>
/// A <see cref="Task" /> representing the asynchronous operation.
/// </returns>
Task PublishAsync(object message);
Task PublishAsync(object message, CancellationToken cancellationToken = default);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an optional parameter unfortunately introduces a subtle breaking change.

The overload to be invoked is resolved at compile time and at that moment the compiler adds the default values as well. Therefore if you compiled with a version that didn't declare the optional parameter but you run against the later version which does, you will still run into the MissingMethodException.

Let's imagine to have a common library that references the current version of Silverback and calls one of these methods. This library is published as nuget and referenced in a .net application. If the application would upgrade to the new version of Silverback with the optional parameter, it would start to throw.

I tried it, to be absolutely sure: Unhandled exception. System.MissingMethodException: Method not found: 'System.Threading.Tasks.Task Silverback.Messaging.Publishing.IPublisher.PublishAsync(System.Object)'.

It would be sufficient to recompile the common library, that's true, but it's still a breaking change that I'd prefer to avoid.
The solution is simple (but a bit annoying) and is to add an overload with the extra parameter, instead of using an optional one.

@igorgiovannini if you don't have time or you're not up for it, I will take care of it. 😃

{
Check.NotNull(context, nameof(context));
Check.NotNull(next, nameof(next));

foreach (var enricher in context.Envelope.Endpoint.MessageEnrichers)
{
enricher.Enrich(context.Envelope);

if (cancellationToken.IsCancellationRequested)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as mentioned already, we maybe should throw.

.ConfigureAwait(false);

if (stoppingToken.IsCancellationRequested)
if (cancellationToken.IsCancellationRequested)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, we should probably throw and handle (log?) if needed.

@@ -47,18 +51,21 @@ public async Task HandleAsync(ProducerPipelineContext context, ProducerBehaviorH
if (!sequenceWriter.CanHandle(context.Envelope))
continue;

if (cancellationToken.IsCancellationRequested)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw instead of silently aborting here too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants