Introduction
I am in the process of reading this very book: Cloud Design Patterns which discusses a great many patterns for the cloud (geared primarily at Azure), and there is talk of a “Circuit Breaker” and “Retry Pattern”.
The “Circuit Breaker” pattern is a new one to me, but the “Retry Pattern” is certainly something I have seen many times before. The basic idea is this:
You want to run a bit of code, but some Exception could occur, so you have some sort of retry policy in place, say one that retries every 10 seconds, that might work, if the Exception is a transient one caused by the network say.
Now I don’t know about you but I have hand coded this sort of thing a lot and never spent too much time (apart from this attempt I did when using Rx which does actually work very well:
The thing is, this is a recurring issue, so surely there is help out there for this issue. Turns out there is.
I have seen a few libraries that help out in this area, here are a few that I have found.
Palmer
This project is hosted at GutHub: https://github.com/mitchdenny/palmer/.
And here are a few examples (taken from Github).
Retry For Some Time
Retry for 15 seconds:
Retry .On<WebException>()
.For(TimeSpan.FromSeconds(15))
.With(context => { // Code that might periodically fail due to connectivity issues. });
Retry Forever
Keep Retrying:
Retry .On<WebException>()
.Indefinitely()
.With(context => { // Code that might throw a web exception. });
Multiple Exceptions
You can also deal with multiple exceptions:
Retry .On<WebException>()
.For(5)
.AndOn<SqlException>()
.For(5)
.With(context => { // Code that might throw a web exception, or a sql exception. });
For more examples, check out the Palmer github link posted above.
Polly
There is another nice library, which you can use which is also hosted at GitHub, it is called “Polly”: https://github.com/michael-wolfenden/Polly
So let's see some examples, again these are taken directly from the GitHub readme.
Step 1: Specify the Type of Exceptions You Want the Policy to Handle
Policy .Handle<DivideByZeroException>()
Policy .Handle<SqlException>(ex => ex.Number == 1205)
Policy .Handle<DivideByZeroException>()
.Or<ArgumentException>()
Policy .Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => x.ParamName == "example")
Step 2: Specify How the Policy Should Handle Those Exceptions
Retry
Policy .Handle<DivideByZeroException>() .Retry()
Policy .Handle<DivideByZeroException>() .Retry(3)
Policy .Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount) => {
Policy .Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount, context) => {
Retry Forever
Policy .Handle<DivideByZeroException>()
.RetryForever()
Policy .Handle<DivideByZeroException>()
.RetryForever(exception => {
Policy .Handle<DivideByZeroException>()
.RetryForever((exception, context) => {
Retry and Wait
Policy .Handle<DivideByZeroException>()
.WaitAndRetry(new[]
{ TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3) });
Policy .Handle<DivideByZeroException>()
.WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() },
(exception, timeSpan) => {
Policy .Handle<DivideByZeroException>()
.WaitAndRetry(new[] { 1.Seconds(), 2.Seconds(), 3.Seconds() },
(exception, timeSpan, context) => {
Policy .Handle<DivideByZeroException>()
.WaitAndRetry(5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)) );
Policy .Handle<DivideByZeroException>()
.WaitAndRetry( 5, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, context) => {
Step 3: Execute the Policy
policy.Execute(() => DoSomething());
var policy = Policy .Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount, context) =>
{ var methodThatRaisedException = context["methodName"];
Log(exception, methodThatRaisedException); });
policy.Execute( () => DoSomething(), new Dictionary<string, object>()
{{ "methodName", "some method" }} );
var policy = Policy .Handle<DivideByZeroException>() .Retry();
var result = policy.Execute(() => DoSomething());
var policy = Policy .Handle<DivideByZeroException>()
.Retry(3, (exception, retryCount, context) =>
{ object methodThatRaisedException = context["methodName"];
Log(exception, methodThatRaisedException) });
var result = policy.Execute( () => DoSomething(),
new Dictionary<string, object>()
{{ "methodName", "some method" }} );
Policy .Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.Retry()
.Execute(() => DoSomething());
Transient Application Block
There is also the Microsoft Patterns & Practices offering “Transient Application Block”. Now P&P pattern can be, well a bit over the top to be frank. This one is not so bad though, it's mainly geared toward working with Azure, but like a lot of stuff in Azure can be used outside of Azure.
There is a nice CodeProject article on how to use this application block outside of Azure which you can read right here:
Here is a small example of how to use this block taken from the article noted here...
static void Main(string[] args)
{
try
{
var retryStrategy = new Incremental
(RETRY_COUNT, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(2));
var retryPolicy = new RetryPolicy<CustomTransientErrorDetectionStrategy>(retryStrategy);
retryPolicy.ExecuteAction(NavigateTo);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
static private void NavigateTo()
{
Console.WriteLine(DateTime.Now);
WebClient wc = new WebClient();
wc.DownloadString("<a href="file:}
...where the above code makes use of this custom ITransientErrorDetectionStrategy
implementation:
internal class CustomTransientErrorDetectionStrategy : ITransientErrorDetectionStrategy
{
public bool IsTransient(Exception ex)
{
if (ex is WebException)
return true;
return false;
}
}
Conclusion
Anyway there you go, I know I have done nothing more than paste a few links to other peoples' work here, but there may be some people that did not know about these very useful libraries and you may think aha that is the one for me. So happy fault handling.
Until next time!