Recoverability changes in Version 6

Component: NServiceBus
This page targets a pre-release version and is subject to change prior to the final release.

Renaming

First Level Retries (FLR) has been renamed to Immediate Retries.

Second Level Retries (SLR) have been renamed to Delayed Retries.

Code First API

Recoverability can now be configured using a code first API:

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    customizations: immediate =>
    {
        immediate.NumberOfRetries(3);
    });
recoverability.Delayed(
    customizations: delayed =>
    {
        var numberOfRetries = delayed.NumberOfRetries(5);
        numberOfRetries.TimeIncrease(TimeSpan.FromSeconds(30));
    });

The api enables setting all automatic retry parameters that were previously available only with configuration source approach.

Disabling Immediate Retries

The TransportConfig API is obsolete and no longer used to disable Immediate Retries. Equivalent behavior can be achieved through code API by setting Immediate Retries to 0:

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    customizations: immediate =>
    {
        immediate.NumberOfRetries(0);
    });
5.x NServiceBus
class ConfigTransport :
    IProvideConfiguration<TransportConfig>
{
    public TransportConfig GetConfiguration()
    {
        return new TransportConfig
        {
            MaxRetries = 0
        };
    }
}

Disabling Delayed Retries

The SecondLevelRetries Feature is no used to disable Delayed Retries. Equivalent behavior can be achieved through code API by setting Delayed Retries to 0:

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Delayed(
    customizations: delayed =>
    {
        delayed.NumberOfRetries(0);
    });
5.x NServiceBus
busConfiguration.DisableFeature<SecondLevelRetries>();

MaxRetries value for First Level Retries

In NServiceBus 5 MaxRetries parameter for First Level Retries defined the number of immediate message deliveries, including initial delivery. As a result when set to 1 resulted in no immediate retries being performed. In NServiceBus 6 the meaning of this parameter has been changed and now it defines the number of immediate retries alone (excluding initial delivery).

In order to get the same behavior in NServiceBus 6 as in NServiceBus 5 the value configured in version 6 should be one less than in version 5.

Custom Retry Policy

In NServiceBus 5 custom retry policies provided ability to control Second Level Retries, in NServiceBus 6 custom retry policy concept has been substituted by recoverability policy which enables control over every stage of automatic retries as well as error handling.

Recoverability policy operates on a RecoverabilityConfig and ErrorContext instead of a TransportMessage which was the case for custom retry policy.

Custom recoverability policy can be registered using CustomPolicy method:

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Delayed(
    customizations: delayed =>
    {
        // desired number of retries
        delayed.NumberOfRetries(3);
    });
recoverability.CustomPolicy(MyCustomRetryPolicy);
5.x NServiceBus
var retriesSettings = busConfiguration.SecondLevelRetries();
retriesSettings.CustomRetryPolicy(MyCustomRetryPolicy);

Custom recoverability policy doesn't have to fully override the default behavior - for example it can provide custom logic for a specific type of error. In any case the default behavior can be reused by calling Invoke method on DefaultRecoverabilityPolicy class.

A following snippet is an example of custom recoverability policy that changes delay value calculated by default policy.

6.x NServiceBus
static RecoverabilityAction MyCustomRetryPolicy(RecoverabilityConfig config, ErrorContext context)
{
    var numberOfRetries = context.DelayedDeliveriesPerformed;
    var exceptionInstance = context.Exception;

    // call the default recoverability of default behavior is desired
    var action = DefaultRecoverabilityPolicy.Invoke(config, context);

    var delayedRetryAction = action as DelayedRetry;
    if (delayedRetryAction != null)
    {
        // perform some logic and decide when to do delayed retries
        return RecoverabilityAction.DelayedRetry(TimeSpan.FromSeconds(5));
    }

    return action;
}
5.x NServiceBus
static TimeSpan MyCustomRetryPolicy(TransportMessage transportMessage)
{
    var numberOfRetries = transportMessage.NumberOfRetries();
    var exceptionType = transportMessage.ExceptionType();

    // perform some logic and decide when to retry
    return TimeSpan.FromSeconds(5);
}


static int NumberOfRetries(this TransportMessage transportMessage)
{
    string value;
    var headers = transportMessage.Headers;
    if (headers.TryGetValue(Headers.Retries, out value))
    {
        return int.Parse(value);
    }
    return 0;
}

static string ExceptionType(this TransportMessage transportMessage)
{
    var headers = transportMessage.Headers;
    return headers["NServiceBus.ExceptionInfo.ExceptionType"];
}

The configuration passed to the custom recoverability policy contains values configured via the recoverability configuration API.

TransportConfig

NServiceBus.Config.TransportConfig has been deprecated. The same can now be achieved via the code API.

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    customizations: immediate =>
    {
        immediate.NumberOfRetries(2);
    });
5.x NServiceBus
<configuration>
  <configSections>
    <section name="TransportConfig"
             type="NServiceBus.Config.TransportConfig, NServiceBus.Core"/>
  </configSections>
  <TransportConfig MaxRetries="2" />
</configuration>

SecondLevelRetriesConfig

NServiceBus.Config.SecondLevelRetriesConfig has been deprecated. The same can now be achieved via the code API.

6.x NServiceBus
var recoverability = endpointConfiguration.Recoverability();
recoverability.Delayed(
    customizations: delayed =>
    {
        delayed.NumberOfRetries(3);
        delayed.TimeIncrease(TimeSpan.FromSeconds(10));
    });
5.x NServiceBus
<configSections>
  <section name="SecondLevelRetriesConfig"
           type="NServiceBus.Config.SecondLevelRetriesConfig, NServiceBus.Core"/>
  </configSections>
<SecondLevelRetriesConfig Enabled="true"
                          TimeIncrease="00:00:10"
                          NumberOfRetries="3" />

Legacy .Retries queue

The .Retries Satellite queue is no longer necessary when running Version 6 of NServiceBus. However, when starting a Version 6 endpoint, unless explicitly configured, a dedicated receiver that watches the .retries queue will still be started. This default configuration is mainly for a one-time scenario necessary to prevent message loss when upgrading from Version 5 to Version 6.

When Upgrading from Versions 5 and below

In NServiceBus Versions 5 and below, the Delayed Retries of NServiceBus used the [endpoint_name].Retries queue to durably store messages before persisting them for retries. When upgrading a Version 5 endpoint to Version 6, during the process of stopping the endpoint, there is a possibility that the .retries queue may contain some of these messages that were meant to be delayed and retried. Therefore to prevent message loss, when starting up a Version 6 endpoint, the .retries satellite receiver runs to serve a one-time purpose of forwarding those messages from the .retries queue to the endpoint's main queue to be retried appropriately.

Once the upgrade is done the receiver can be safely disabled and the .Retries queue can be safely deleted.

When Creating New Endpoints using Version 6

In Version 6, the only reason that the .retries queue exists is so that Version 5 endpoints can be migrated to Version 6 without any message loss. Any endpoint that is written using Version 6, can safely use the following configuration API to disable the satellite.

IManageMessageFailures is now obsolete.

The IManageMessageFailures interface was the extension point to customize the handling of second level retries before a message failure is forwarded to the error queue.

This same functionality and more can be achieved using the message processing pipeline. See also Customizing error handling with the pipeline.

RepeatedFailuresOverTimeCircuitBreaker has been made internal

If are using RepeatedFailuresOverTimeCircuitBreaker instead include the source code in the project.

Critical Error Action

The API for defining a Critical Error Action has been changed to be a custom delegate.

6.x NServiceBus
endpointConfiguration.DefineCriticalErrorAction(
    new Func<ICriticalErrorContext, Task>(context =>
    {
        // place custom handling here
        return Task.CompletedTask;
    }));
5.x NServiceBus
busConfiguration.DefineCriticalErrorAction(
    new Action<string, Exception>((error, exception) =>
    {
        // place custom handling here
    }));

Notifications

The BusNotifications class has been renamed to Notifications.

BusNotifications previously exposed the available notification hooks as observables implementing IObservable. This required implementing the IObserver interface or including Reactive-Extensions to use this API. In Version 6 the notifications API has been changed for easier usage. It exposes regular events instead of observables. To continue using Reactive-Extensions the events API can be transformed into IObservables like this:

7-pre NServiceBus
var errorsNotifications = notifications.Errors;
var failedMessages = Observable.FromEventPattern<EventHandler<FailedMessage>, FailedMessage>(
    handler => errorsNotifications.MessageSentToErrorQueue += handler,
    handler => errorsNotifications.MessageSentToErrorQueue -= handler);

var subscription = failedMessages
    .Subscribe(x =>
    {
        var failedMessage = x.EventArgs;
        log.Error($"Message {failedMessage.MessageId} moved to error queue", failedMessage.Exception);
    });
6.x NServiceBus
var errorsNotifications = notifications.Errors;
var failedMessages = Observable.FromEventPattern<EventHandler<FailedMessage>, FailedMessage>(
    handler => errorsNotifications.MessageSentToErrorQueue += handler,
    handler => errorsNotifications.MessageSentToErrorQueue -= handler);

var subscription = failedMessages
    .Subscribe(x =>
    {
        var failedMessage = x.EventArgs;
        log.Error($"Message {failedMessage.MessageId} moved to error queue", failedMessage.Exception);
    });

Notification subscriptions can now also be registered at configuration time on the EndpointConfiguration.Notifications property. See the error notifications documentation for more details and samples.

Access to exception details

Exception details is now available on the passed Notifications parameter and not as NServiceBus.ExceptionInfo.* headers on the provided message.

Delayed delivery error notifications

In Versions 6 and above the TimeoutManager does not provide any error notifications. When an error occurs during processing of a deferred message by the TimeoutManager, the message will be retried and possibly moved to the error queue. The user will not be notified about these events.

Note that in Versions 5 and below, when the user subscribes to error notifications they receive notification in the situation described above.

Timeout Automatic retries

Previously configuring the number of times a message will be retried by the First Level Retries (FLR) mechanism also determined how many times the TimeoutManager attempted to retry dispatching a deferred message in case an exception was thrown. From Version 6, the TimeoutManager will attempt the dispatch five times (this number is not configurable anymore). The configuration of the FLR mechanism for non-deferred message dispatch has not been changed.

Related Articles


Last modified