Search code examples
c#list.net-corepollyretry-logic

Generic method which handles exception list with polly package


I need to handle list of exception in generic way. I need to rewrite the code in Polly style.

I have existing working code where custom generic exception handled:

readonly TimeSpan maxDelay;
readonly int initialRetryDelayInMilliseconds;
readonly double backoffFactor;
readonly int maxAttemptCount;
readonly string methodName;
readonly List<Exception> allowedExceptions;

public ExponentialBackoffService(string _methodName,int _initialRetryDelayInMilliseconds, TimeSpan _maxDelay, double _backoffFactor, int _maxAttemptCount, List<Exception> _allowedExceptions = null)
{
    initialRetryDelayInMilliseconds = _initialRetryDelayInMilliseconds;
    backoffFactor = _backoffFactor;
    maxAttemptCount = _maxAttemptCount;
    maxDelay = _maxDelay;
    methodName = _methodName;
    allowedExceptions = _allowedExceptions;
}

public virtual async Task<T> Do<T>(Func<Task<T>> task)
{
    TraceLogger.Log("ExponentialBackoffService", 0, "ExponentialBackoffService task do", true);

    var exceptions = new List<Exception>();
    for (int attempted = 0; attempted < maxAttemptCount; attempted++)
    {
        var trace = TraceLog.GenerateTraceLog($"ExponentialBackoffService call started==>{attempted}");
        trace.Category = "ExponentialBackoffService";
        await Logger.WriteLogAsync(trace);

        try
        {
            if (attempted > 0)
            {
                TimeSpan retryDelayInMilliseconds = CalculateExponentialBackoff(TimeSpan.FromMilliseconds(initialRetryDelayInMilliseconds), attempted);
                await Task.Delay(retryDelayInMilliseconds);
                await Logger.WriteLogAsync(GenerateTrace(retryDelayInMilliseconds));
            }
            return await task();
        }
        catch (Exception ex)
        {
            if (AllowAllException() || AllowSpecificExceptions(ex))
            {
                exceptions.Add(ex);
            }
            else { throw; }
        }
        finally
        {
            var trace1 = TraceLog.GenerateTraceLog($"ExponentialBackoffService call completed==>{attempted}");
            trace1.Category = "ExponentialBackoffService";
            await Logger.WriteLogAsync(trace1);
        }
    }
    throw new AggregateException(exceptions);
}
 
public virtual bool AllowAllException()
{
    return  allowedExceptions == null || allowedExceptions?.Count() == 0;
}

public virtual bool AllowSpecificExceptions(Exception currentException)
{
    bool isCurrentExceptionAllowed = false;
    if(allowedExceptions?.Count() > 0)
    {
        foreach (Exception exception in allowedExceptions)
        {
            if(exception.GetType()== currentException.GetType())
            {
                isCurrentExceptionAllowed = true;
                break;
            }
        }
        return isCurrentExceptionAllowed;
    }
    return true;
}

Need to rewrite the above code in polly style.

1st question

var existingPolicy = Policy.WaitAndRetryAsyc(BackOff.DecorrletedJitterBackOffV2)

Here,need to define retry logic and logging for each retry; no exception definition

2nd question

if(allowedExceptions?.Count() > 0)
{
    for(int i ;i<allowedExceptions.count();i++)
    {
        var dynamicPolicy=Policy.Handle<allowedExceptions[i].GetType>;//create dynamic policy for handle exception
        existingPolicy.WrapAsync(dynamicPolicy);//appending each exception as per exceptionList
    }
}

According to caller , will send a list of exception, those generic exceptions need to be added in the policy

And last execute

existingPolicy.executeAsync(task);

Solution

  • Let me first try to reply to your second question.

    Since your allowedExceptions is a List<Exception> that's why you can write this:

    Policy.Handle<Exception>(ex => allowedExceptions.Contains(ex))
          ...
    

    or simply just

    Policy.Handle<Exception>(allowedExceptions.Contains)
          ...
    

    In both cases you define a policy which should trigger only if an exception is thrown and that exception is one of the allowed exceptions.


    Now let's focus on your first question.

    In case of Polly V7 you can define a policy which should trigger either if a specific exception is thrown (Handle<TEx> / HandleInner<TEx>) or the result object is in a given state (HandleResult<TResult>(Predicate p)).

    So, if you want to retry n times unconditionally then you can do the following workaround:

    Policy.HandleResult<object?>(_ => true)
    

    That means your to-be-decorated method needs to return either with a reference type instance or a null.

    Or you can define the trigger for bool:

    Policy.HandleResult<bool>(_ => true)
    

    It's up to you which one do you prefer. It's a workaround because Polly V7 does not support unconditional triggering.