JasperFx 0.9.14


Next

Cascading Messages

Previous

Message Handler Discovery

Error Handling Edit on GitHub


Note! Jasper originally used its own extensible error handling left over from its FubuMVC predecessor, but as of v0.9.5, Jasper uses Polly under the covers for the message exception handling with just some custom extension methods for Jasper specific things. You will be able to use all of Polly's many, many features with Jasper messaging retries.

The sad truth is that Jasper will not unfrequently hit exceptions as it processes messages. In all cases, Jasper will first log the exception using the standard ASP.Net Core ILogger abstraction. After that, it walks through the configured error handling policies to determine what to do next with the message. In the absence of any confugured error handling policies, Jasper will move any message that causes an exception into the error queue for the transport that the message arrived from on the first attempt.

Today, Jasper has the ability to:

  • Enforce a maximum number of attempts to short circuit retries, with the default number being 1
  • Selectively apply remediation actions for a given type of Exception
  • Choose to re-execute the message immediately
  • Choose to re-execute the message later
  • Just bail out and move the message out to the error queues
  • Apply error handling policies globally, by configured policies, or explicitly by chain
  • Use custom Polly error handling to do whatever you want, as long as the policy returns a Jasper IContinuation object

IContinuation

If you want to write a custom error handler, you may need to write a custom IContinuation that just tells Jasper "what do I do now with this message?":


public interface IContinuation
{
    Task Execute(IMessageContext context, DateTime utcNow);
}

Internally, Jasper has built in IContinuation strategies for retrying messages, moving messages to the error queue, and requeueing messages among others.

Exponential Backoff Policies

By integrating Polly for our retry policies, Jasper gets exponential backoff retry scheduling nearly for free.

To reschedule a message to be retried later at increasingly longer wait times, use this syntax:


public class AppWithErrorHandling : JasperRegistry
{
    public AppWithErrorHandling()
    {
        // On a SqlException, reschedule the message to be retried
        // at 3 seconds, then 15, then 30 seconds later
        Handlers.Retries.Add(x => x.Handle<SqlException>()
            .Reschedule(3.Seconds(), 15.Seconds(), 30.Seconds()));


    }
}

Configuring Global Error Handling Rules

To establish global error handling policies that apply to all message types, use the JasperBusRegistry.ErrorHandling syntax as shown below:


public class GlobalRetryApp : JasperRegistry
{
    public GlobalRetryApp()
    {
        Handlers.Retries.Add(x => x.Handle<TimeoutException>().Reschedule(5.Seconds()));

        Handlers.Retries.Add(x => x.Handle<SecurityException>().MoveToErrorQueue());

        // You can also apply an additional filter on the
        // exception type for finer grained policies
        Handlers.Retries.Add(x => x.Handle<SocketException>(ex => ex.Message.Contains("not responding"))
            .Reschedule(5.Seconds()));
    }
}

In all cases, the global error handling is executed after any message type specific error handling.

Explicit Chain Configuration

To configure specific error handling polices for a certain message (or closely related messages), you can either use some in the box attributes on the message handler methods as shown below:


public class AttributeUsingHandler
{
    [RescheduleLater(typeof(IOException), 5)]
    [RetryOn(typeof(SqlException))]
    [RequeueOn(typeof(InvalidOperationException))]
    [MoveToErrorQueueOn(typeof(DivideByZeroException))]
    [MaximumAttempts(2)]
    public void Handle(InvoiceCreated created)
    {
        // handle the invoice created message
    }
}

If you prefer -- or have a use case that isn't supported by the attributes, you can take advantage of Jasper's Configure(HandlerChain) convention to do it programmatically. To opt into this, add a static method with the signature public static void Configure(HandlerChain) to your handler class as shown below:


public class MyErrorCausingHandler
{
    public static void Configure(HandlerChain chain)
    {
        chain.Retries.Add(x => x.Handle<IOException>()
            .Requeue());

        chain.Retries.MaximumAttempts = 3;
    }


    public void Handle(InvoiceCreated created)
    {
        // handle the invoice created message
    }

    public void Handle(InvoiceApproved approved)
    {
        // handle the invoice approved message
    }
}

Do note that if a message handler class handles multiple message types, this method is applied to each message type chain separately.

Configuring through Policies

If you want to apply error handling to chains via some kind of policy, you can use an IHandlerPolicy like the one shown below:


public class ErrorHandlingPolicy : IHandlerPolicy
{
    public void Apply(HandlerGraph graph, JasperGenerationRules rules)
    {
        var matchingChains = graph.Chains.Where(x => x.MessageType.IsInNamespace("MyApp.Messages"));

        foreach (var chain in matchingChains)
        {
            chain.Retries.MaximumAttempts = 2;
            chain.Retries.Add(x => x.Handle<SqlException>()
                .Requeue());
        }
    }
}

To apply this policy, use this syntax in your JasperBusRegistry:


public class MyApp : JasperRegistry
{
    public MyApp()
    {
        Handlers.GlobalPolicy<ErrorHandlingPolicy>();
    }
}

Filtering on Exceptions

To selectively respond to a certain exception type, you have access to all of Polly's exception filtering mechanisms as shown below:


public class FilteredApp : JasperRegistry
{
    public FilteredApp()
    {
        Handlers.Retries.Add(x => x.Handle<SqlException>().Requeue());

        Handlers.Retries.Add(x => x.Handle<InvalidOperationException>().Retry());
    }
}

Built in Error Handling Actions

The most common exception handling actions are shown below:


public class ContinuationTypes : JasperRegistry
{
    public ContinuationTypes()
    {
        var policy = Policy<IContinuation>.Handle<SqlException>()
            .Retry(3);

        // Try to execute the message again without going
        // back through the queue
        Handlers.Retries.Add(x => x.Handle<SqlException>().Retry());

        // Retry the message again, but wait for the specified time
        Handlers.Retries.Add(x => x.Handle<SqlException>().Reschedule(3.Seconds()));

        // Put the message back into the queue where it will be
        // attempted again
        Handlers.Retries.Add(x => x.Handle<SqlException>().Requeue());

        // Move the message into the error queue for this transport
        Handlers.Retries.Add(x => x.Handle<SqlException>().MoveToErrorQueue());
    }
}

The RetryLater() function uses Scheduled Message Execution.

See also Dead Letter Envelopes for more information.