How to use Polly for retry mechanism on specific exception?

380 views Asked by At

I am trying to implement retry mechanism on DbUpdateConcurrencyException (link). There are some ways to that, like add counter or have logic in catch block to retry (this link shows that).

Here, I am trying Polly to do that and not getting desired result.

What am I missing here? Current, logic goes to catch block and throws the error instead of retry.

Code:

public class MyService
{
  private readonly ILogger<MyService> _logger;

  // Using Polly for retry
  private readonly RetryPolicy _policy = Policy
           .Handle<DbUpdateConcurrencyException>()
           .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

  public MyService(ILogger<MyService> logger)
  {
    _mapper = mapper;
  }

  public Task<ServiceResponse<MyDto>> UpsertSpecificationsWithRetryAsync(MyDto myDto, CancellationToken cancellationToken)
  {
    // Expecting this to be called 3 times in case of failure, every second
    return _policy.Execute(async ct => await UpsertSpecificationsAsync(myDto, cancellationToken), cancellationToken);
  }

  public async Task<ServiceResponse<MyDto>> UpsertSpecificationsAsync(MyDto myDto,
           CancellationToken cancellationToken)
  {
    try
    {
     
     // To test the retry mechanism
      throw new DbUpdateConcurrencyException();
    }
    catch (Exception exception)
    {
      _logger.Error(message: $"Exception while upserting options. AtdDto: {{@atdDto}} Exception: {{@exception}}",
          args: new object[] { myDto, exception });

      return GetExceptionServiceResponse<MyDto>(exception.GetBaseException().Message);
    }
  }
}
2

There are 2 answers

1
Mohammad Aghazadeh On BEST ANSWER

try changing this :

private readonly RetryPolicy _policy = Policy
           .Handle<DbUpdateConcurrencyException>()
           .WaitAndRetry(3, _ => TimeSpan.FromSeconds(1));

to this :

private readonly AsyncRetryPolicy _policy = Policy
          .Handle<DbUpdateConcurrencyException>()
          .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1));

and this :

public Task<ServiceResponse<MyDto>> UpsertSpecificationsWithRetryAsync(MyDto myDto, CancellationToken cancellationToken)
{
    // Expecting this to be called 3 times in case of failure, every second
    return _policy.Execute(async ct => await UpsertSpecificationsAsync(myDto, cancellationToken), cancellationToken);
}

to this :

public async Task<ServiceResponse<MyDto>> UpsertSpecificationsWithRetryAsync(MyDto myDto, CancellationToken cancellationToken)
{
    // Expecting this to be called 3 times in case of failure, every second
     return (await _policy.ExecuteAndCaptureAsync(() => UpsertSpecificationsAsync(myDto, cancellationToken)))?.Result;
}

And finally, if you want to log the error in the catch block, you should throw it after loging

try
{     
   // To test the retry mechanism
    throw new DbUpdateConcurrencyException();
}
catch (Exception exception)
{
    _logger.Error(message: $"Exception while upserting options. AtdDto: {{@atdDto}} Exception: {{@exception}}",
          args: new object[] { myDto, exception });
     throw;
      ///return GetExceptionServiceResponse<MyDto>(exception.GetBaseException().Message);
 }

But for loging error, Polly provides it to you and you can do the following instead of using try catch:

 private readonly AsyncRetryPolicy _policy = Policy
          .Handle<DbUpdateConcurrencyException>()
          .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(1), (exception, _) =>
          {
              //log error
          });
3
Peter Csala On

In case of Polly v7 (or earlier) the policy declaration should be aware of the to-be-decorated method's nature.

In your case your the to-be-decorated method is asynchronous whereas you have defined the policy for synchronous methods.

Either change your to-be-decorated method to be synchronous or define the policy for asynchronous operation.

The latter case is showed by Mohammad Aghazadeh so, I won't it repeat that here. BUT I want to mention two corrections here:

return (await _policy.ExecuteAndCaptureAsync(() => UpsertSpecificationsAsync(myDto, cancellationToken)))?.Result;
  1. ExecuteAsync is enough, you don't need ExecuteAndCaptureAsync
  2. UpsertSpecificationsAsync should receive the ct as the second parameter

So, the correct way would be

return await _policy.ExecuteAsync(ct => UpsertSpecificationsAsync(myDto, ct), cancellationToken);