Getting Started
Architecture
NServiceBus
Transports
Persistence
ServiceInsight
ServicePulse
ServiceControl
Monitoring

Custom Recoverability

Component: NServiceBus
NuGet Package: NServiceBus (9.x)

This sample demonstrates how to use custom recoverability policy. Here the default recoverability is invoked when a specific exception type happens and at all other times it uses a custom recoverability action.

Sample structure

The sample contains three projects:

  • Shared - The Shared project is for shared classes including message definitions
  • Client - A console application responsible for sending the messages.
  • Server - A console application responsible for receiving the messages from the client.

Running the sample

To start, the sample is configured to run without failing:

  1. Run the solution. Two console applications start, the Client and the Server.
  2. In the Client console, press "Enter" to send a message.
  3. In the Server console, this message is received and an entry is logged in the console

Client output

Press 'Enter' to send a message.
Press any key to exit
Sent a new message with id: 87283b82a892456d81eb1bfa05fb72e2

Server output

Press any key to exit
Message received. Id: 87283b82a892456d81eb1bfa05fb72e2

Fault tolerance with custom recoverability

In the 'Server' application, open Program.cs. There is a custom policy MyCustomRetryPolicy where the default NServiceBus recoverability policy is invoked when an ArgumentNullException exception is encountered and at all other times the message is not retried and sent directly to the "error" queue.

private static Func<RecoverabilityConfig, ErrorContext, RecoverabilityAction> MyCustomRetryPolicy()
{
    return (config, errorContext) =>
      errorContext.Exception is ArgumentNullException
          ? DefaultRecoverabilityPolicy.Invoke(config, errorContext)
          : RecoverabilityAction.MoveToError("error");
}

The custom policy is added to the recoverability as below

recoverability.CustomPolicy(MyCustomRetryPolicy());

Custom headers are added to the message before sending to the "error" queue.

recoverability.Failed(
failed =>
{
    failed.HeaderCustomization(headers =>
    {
        if (headers.ContainsKey("NServiceBus.ExceptionInfo.Message"))
        {
            headers["NServiceBus.ExceptionInfo.Message"] = "message override";
        }
    });
});

Make the handler fail

In the 'Server' application, open MyHandler.cs. Uncomment the throw new ArgumentNullException line.

public class MyHandler :
    IHandleMessages<MyMessage>
{
    static ILog log = LogManager.GetLogger<MyHandler>();

    public Task Handle(MyMessage message, IMessageHandlerContext context)
    {
       log.Info($"Message received. Id: {message.Id}");
       //throw new ArgumentNullException("Uh oh - something went wrong....");
       //throw new DivideByZeroException("DivideByZeroException - something went wrong....");
       return Task.CompletedTask;
    }
}

As per the custom recoverability policy, when the solution is run, the exception is thrown and the default recoverability policy will be invoked before finally moving to the "error" queue.

Server Output

INFO  Message received. Id: 76d8b5c3-c41b-4179-b995-4e68f3c5b7eb
INFO  Immediate Retry is going to retry message '1e0d3ee9-3ab0-4be9-bf45-b0330060b9e8' because of an exception:
System.ArgumentNullException: Value cannot be null. (Parameter 'Uh oh - something went wrong....')
   at MyHandler.Handle(MyMessage message, IMessageHandlerContext context) in C:\Particular\docs.particular.net\samples\custom-recoverability\Core_8\Server\MyHandler.cs:line 15
   at NServiceBus.Pipeline.MessageHandler.Invoke(Object message, IMessageHandlerContext handlerContext) in /_/src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs:line 43

WARN  Delayed Retry will reschedule message '1e0d3ee9-3ab0-4be9-bf45-b0330060b9e8' after a delay of 00:00:30 because of an exception:
System.ArgumentNullException: Value cannot be null. (Parameter 'Uh oh - something went wrong....')
   at MyHandler.Handle(MyMessage message, IMessageHandlerContext context) in C:\Particular\docs.particular.net\samples\custom-recoverability\Core_8\Server\MyHandler.cs:line 15
   at NServiceBus.Pipeline.MessageHandler.Invoke(Object message, IMessageHandlerContext handlerContext) in /_/src/NServiceBus.Core/Pipeline/Incoming/MessageHandler.cs:line 43
   at NServiceBus.InvokeHandlerTerminator.Terminate(IInvokeHandlerContext context) in /_/src/NServiceBus.Core/Pipeline/Incoming/InvokeHandlerTerminator.cs:line 33
   at NServiceBus.LoadHandlersConnector.Invoke(IIncomingLogicalMessageContext context, Func`2 stage) in /_/src/NServiceBus.Core/Pipeline/Incoming/LoadHandlersConnector.cs:line 40

INFO  Message received. Id: 76d8b5c3-c41b-4179-b995-4e68f3c5b7eb
ERROR Moving message '1e0d3ee9-3ab0-4be9-bf45-b0330060b9e8' to the error queue 'error' because processing failed due to an exception:
System.ArgumentNullException: Value cannot be null. (Parameter 'Uh oh - something went wrong....')

Delayed Retries can be turned off by uncommenting the below line in the Server Program.cs:

//recoverability.Delayed(settings =>
//{
//    settings.NumberOfRetries(0);
//});

Now when the sample is re-run, the message is sent to the error queue without the delayed retries after successive immediate retries.

Now, in the "Server" application, open MyHandler.cs. Comment out throw new ArgumentNullException and uncomment the throw new DivideByZeroException line. When the solution is run and the exception is thrown, the message is directly moved to the "error" queue without any retries (as per the custom policy).

 INFO  Message received. Id: 5570fc08-098d-4784-8902-0205ab0ae594
2023-07-01 23:09:03.270 ERROR Moving message 'e6bd7712-cf5a-4927-923e-b0330065571a' to the error queue 'error' because processing failed due to an exception:
System.DivideByZeroException: DivideByZeroException - something went wrong....
   at MyHandler.Handle(MyMessage message, IMessageHandlerContext context) in samples

Samples

Related Articles

  • Custom Recoverability Policy
    Shows how to take full control over Recoverability by implementing a Recoverability Policy.
  • Recoverability
    Explains how exceptions are handled, and actions retried, during message processing.