Krumelur
Krumelur

Reputation: 32497

RetryWhen and exception logic

I have been looking at implementing a basic retry logic for a simple network stream. The idea is that there are three types of exceptions to handle:

While sketching this up, I end up with something like

var retriedObservable = observable.RetryWhen(x => x.SelectMany(ex =>
{
    if (ex is ShouldCompleteException)
    {
        return Observable.Return<Exception>(null);
    }
    else if (ex is TransientNetworkException)
    {
        return Observable.Return(ex).Delay(TimeSpan.FromSeconds(1));
    }
    else
    {
        return Observable.Throw<Exception>(ex);
    }
}).TakeUntil(ex => ex == null));

Edit: This example is highly simplified, showing only the three typical error handling patterns I see in my code. In the real world, there's a lot more to it of course.

The code works but looks overly complicated, and I'm not sure if the special 'null' value will hit me in the face at some point.

I also tried combinations of .Where() and .Merge(), but the code quickly becomes unreadable.

Is there a cleaner way (i.e. more canonical) to accomplish this basic error handling?

Upvotes: 0

Views: 837

Answers (2)

Shlomo
Shlomo

Reputation: 14350

This helps a little:

var retriedObservable2 = observable.RetryWhen(exStream => Observable.Merge(
    exStream.OfType<ShouldCompleteException>().Select(_ => (Exception)null),
    exStream.NotOfType(typeof(ShouldCompleteException)).OfType<TransientNetworkException>().Delay(TimeSpan.FromSeconds(1)),
    exStream.NotOfTypes(typeof(ShouldCompleteException), typeof(TransientNetworkException)).SelectMany(e => Observable.Throw<Exception>(e))
).TakeUntil(ex => ex == null));

Which uses the following extension messages:

public static class X
{
    public static IObservable<TSource> NotOfType<TSource>(this IObservable<TSource> source, Type t)
    {
        return source.Where(o => !t.IsInstanceOfType(o));
    }

    public static IObservable<TSource> NotOfTypes<TSource>(this IObservable<TSource> source, params Type[] ts)
    {
        return source.Where(o => ts.All(t => !t.IsInstanceOfType(o)));
    }
}

Upvotes: 2

Danijel
Danijel

Reputation: 1185

There are already great libraries for this, like Polly. But, if you want to implement your own, you can do something like the following.

You can create a RetryPolicyHandler class. Also, you can abstract it with an interface and use with any of the IoC containers if you want. Add a method like RetryExecuteAsync with the following implementation.

public async Task RetryExecuteAsync(Func<Task> action)
{
    int retryCount = default(int);
    int maxNumberOfRetries = 5; // Or get from settings

    while (true)
    {
        try
        {
            await action().ConfigureAwait(false);

            break;
        }
        catch (ArgumentNullException)
        {
            // Something specific about this exception
            if (++retryCount > maxNumberOfRetries)
                throw;
        }
        catch (Exception)
        { 
            if (++retryCount > maxNumberOfRetries)
                throw;
        }
    }
}

And then you can use it like this.

await this.retryPolicyHandler.RetryExecuteAsync(async () =>  
{
    // Whatever code you want to retry
}).ConfigureAwait(false);

You can also create other specific methods if you want. For instance, ExecuteDeadlockRetryAsync with catching SqlException or ExecuteHttpCallRetryAsync for handling HttpRequestException and handle them in another way.

Upvotes: 0

Related Questions