Software Design Blog

Journey of Rinat Abdullin

.NET Exception Handling Action Policies Application Block

A while ago I’ve talked a bit about the concept of Action Policies and exception handlers. Let’s get back to the topic for a while.

Overview

In short, Action Policy is just some reusable decorator that could be easily applied to any given method call or code block to augment the execution.

An Exception Handler is just a logical part of an Action Policy that determines which exceptions do we handle and how do we do that.

Microsoft Exception Handling Application Block from the Enterprise Library attempted to provide consistent and configurable way to handle exceptions across the application. Yet, the encircling action policy remained hardcoded (that’s simple try-catch block).

try
{
  SomeFragileCall();      
}
catch (Exception ex)
{
  bool rethrow = ExceptionPolicy
    .HandleException(ex, "Global Policy");
  if (rethrow)
  {
   throw;
  }
}

Let me introduce you with another alternative, that provides a consistent way of handling exceptions as well as defining and leveraging the action policies.

That’s the Lokad Shared Libraries, that provide you with the functionality to leverage and the fluent API to make it simple.

First, we define which exceptions to handle, then we pick the action policy to leverage that. After that we just reuse the action policy in the code.

Using policies

Actual usage of Lokad Action policies is trivial in C#:

// perform action within the policy
policy.Do(() => repository.AddMessages(someMessages));

// execute a function within the policy
var recordSet = policy.Get(() => repository.GetRecords(someCriteria));

Exception Handlers

Generic exception handlers allow to handle selected exceptions unconditionally and are defined like:

ActionPolicy.Handle<DbException>()
ActionPolicy.Handle<DbException, TimeoutException>()
ActionPolicy.Handle<DbException, TimeoutException, RetriableException>()

Custom exception handler provides much greater flexibility and is defined like:

var handler = ActionPolicy.From(GlobalPolicy);

// handler definition
static bool GlobalPolicy(Exception ex)    
{
  if (ex.Message.Contains("Password"))      
  {
    throw new SecuredException(ex);
  }
  return false;
}

or you could use some inlining along the way:

var log = SomeStaticIoC.Resolve<ILog>();
var handler = ActionPolicy.From(ex => WebClientPolicy(ex,log));

// handler definition
static bool WebClientPolicy(Exception ex, ILog log)
{
  if (ex is WebException)      
  {
    log.Error(ex, "Captured exception. Rethrowing...");
    throw new ConnectionException(ex);
  }
  return false;
}

Retry Action Policies

Exception handling action policies are defined with the fluent API by extending the exception handlers.

We will use as an example the handler for DbExceptions (that’s any SqlException), but keep in mind that any other handler definition would work here.

Retry Action For N Times is defined by:

var policy = ActionPolicy.Handle<DbException>().Retry(3);

This policy will retry the underlying call for the 3 times before failing and rethrowing the last exception. This is useful, for example, while making some DB code more tolerant to the deadlocks or adding reliability to the smart client applications.

However, we might want to add a sleep period between the retries (to avoid hammering the DB) and/or log the exception. This could be done with:

var log = ...;
var policy = ActionPolicy
  .Handle<DbException>()
  .Retry((ex,i) => 
    {
      log.DebugFormat(ex, "Retrying exception for the {0} time", i);
      SystemUtil.Sleep(0.5.Seconds());
    });

A couple of notes here:

  • 3.Minutes() or 0.5.Seconds() syntax is supported as long as you use Lokad.Shared (or your own other TimeSpanExtensions implementation)

  • SystemUtil.Sleep is just like Thread.Sleep with one exception that it simplifies unit testing a lot. I’ll talk about this some time later.

Additionally, we might go a bit smarter with this policy and add sleep intervals that grow with every retry:

var policy = ActionPolicy
  .Handle<DbException>()
  .Retry((ex,i) => 
    {
      log.DebugFormat(ex, "Retrying exception for the {0} time", i);
      SystemUtil.Sleep((0.5 + i).Seconds());
    });

Retry Forever and WaitAndRetry are simple variations of the previous policy.

Circuit Breaker Action Policy

Circuit Breaker is a special case. It is defined as:

var policy = ActionPolicy
  .Handle<DbException>()
  .CircuitBreaker(1.Minutes(), 2);

Yet the description is slightly longer. Circuit breaker was first mentioned to me by Nicholas Blumhardt. It is described in the “Release It!” book (which I strongly recommend reading to any developer)

It works like this.

In real life circuit breakers helps us when there is a power surge in the power lines or they get overloaded. Then they simply fail fast. This helps to prevent some permanent damage at little or no cost.

Same story is with the distributed systems. When some remote or fragile subsystem breaks and starts failing (throwing exceptions), circuit breaker breaks and starts failing fast. This gives the underlying system chance to recover and saves us from some timeouts.

After some preset time interval passes, circuit breaker allows once method call to come through. If it succeeds, then the breaker is back to the normal operation; protection mode is entered again otherwise.

That’s exactly how this .NET circuit breaker works as well.

Number of exceptions that are needed to break the circuit and the TimeSpan for staying open are defined when the policy is created.

Note, that this circuit breaker policy is safe for the concurrent operations.

Windows Azure Compatibility

Composed action policies are compatible with the retry policies of the StorageClient project in Windows Azure. That’s how simple it is to use them together:

var policy = ActionPolicy.Handle<StorageServerException>().Retry(5);
queueService.RetryPolicy = policy.Do;

Obviously, you can provide more complex handling and retrying logic here, than a simple “Retry 5 times on a StorageServerException”.

Here’s a larger piece of code that registers QueueStorage in the Autofac IoC container, optionally supplying it our global action policy:

var queueUri = new Uri(QueueEndpoint);
var accountInfo = new StorageAccountInfo(queueUri, null, 
  AccountName, AccountKey);

builder.Register(c =>
{
  var queueService = QueueStorage.Create(accountInfo);
  // if we have an action policy around, apply it
  ActionPolicy policy;
  if (c.TryResolve(out policy))
  {
    queueService.RetryPolicy = policy.Do;
  }                 
  return queueService;
});

It works in a similar way for the other storage classes - blob containers and tables.

Other Benefits

Other benefits of this Application Block are similar to the other blocks from the Lokad Shared Libraries:

  • Production-proof
  • Active development
  • Small codebase
  • Open Source

So what to do next?

  • Grab the latest version of Lokad.Shared.dll from Lokad Libraries package and start playing
  • Check the Lokad Shared Libraries for the:

    • Latest versions
    • Documentation links
    • Access to the sources
  • Feel free to ask questions, tell about your rule ideas and just send any feedback