Search code examples
c#concurrencypollyretry-logiccosmos

How to use Polly for retry mechanism on specific exception?


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);
    }
  }
}

Solution

  • 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
              });