Search code examples
c#json.netpollyretry-logic

retry insert 3 times only when error code 403002 occurs


I came to know through Polly I can re-try the execution up to multiple configurable time like below example,

 Policy
.Handle<SqlException>(ex => ex.Number == 1205)
.Or<ArgumentException>(ex => ex.ParamName == "example")
.WaitAndRetry(3, _ => TimeSpan.FromSeconds(3))
.Execute(DoSomething);

In my case below I am doing JSON serialization and all to find the error code 403002 and only on this error I want to re-try my code await deviceClient.SendEventAsync(new Message()); with a configuration time.

How to achieve this with Polly?

try
        {
            await deviceClient.SendEventAsync(new Message());
        }
        catch (Exception ex)
        {
            var errorMessage = ex.InnerException?.Message;

            if (errorMessage != null)
            {
                dynamic error = JsonConvert.DeserializeObject(errorMessage);
                if (error != null && error.errorCode == 403002)
                {
                    // want to re-try again 'await deviceClient.SendEventAsync'
                }
                else
                {
                    _logger.LogError($"Other error occurred : {error}");
                }
            }
        }
        finally
        {
            //
        }

Solution

  • I might be late to the party, but let me put my 2 cents here.

    user1672994's suggested solution will not work for SendEventAsync, because the policy definition is created for sync function whereas your to-be-decorated one is async.

    There is simple fix for that: use WaitAndRetryAsync instead.


    I would also suggest to extract your should trigger retry delegate into a separate method to make your policy definition short and concise like this:

    var retryPolicy = Policy
      .Handle<Exception>(ShouldTriggerRetry)
      .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(3)); 
    
    bool ShouldTriggerRetry(Exception ex)
    {       
        var errorMessage = ex.InnerException?.Message;
        if (string.IsNullOrEmpty(errorMessage)) return false;
        var error = JsonConvert.DeserializeObject<dynamic>(errorMessage);
    
        var shouldTrigger = error?.errorCode == 403002;
        if(!shouldTrigger) _logger.LogError($"Other error occurred : {error}");
        return shouldTrigger;
    }
    

    One final thought: As I can see in your code you have a finally block. If you need to do some cleanup between the retry attempts then you should do that inside onRetry or onRetryAsync delegate (depends on your cleanup code: sync or async).

    var retryPolicy = Policy
      .Handle<Exception>(ShouldTriggerRetry)
      .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(3), SyncCleanup);
    
    //...
    
    void SyncCleanup(Exception ex, TimeSpan sleep)
    {
       //...
    }
    
    var retryPolicy = Policy
      .Handle<Exception>(ShouldTriggerRetry)
      .WaitAndRetryAsync(3, _ => TimeSpan.FromSeconds(3), AsyncCleanup);
    
    //...
    
    Task AsyncCleanup(Exception ex, TimeSpan sleep)
    {
        //...
    }
    

    WaitAndRetryAsync has 19 overloads so, you might need to scrutinise them if you need to access for example the retry counter or the Context in your onRetry(Async) delegate.


    UPDATE #1

    Where should I put await deviceClient.SendEventAsync(new Message())

    In case of Polly you define a policy and then you use it. The above code was just the definition. The usage looks like this:

    await retryPolicy.ExecuteAsync(async () => await deviceClient.SendEventAsync(new Message()));
    

    or like this

    await retryPolicy.ExecuteAsync(() => deviceClient.SendEventAsync(new Message()));
    

    Which block I should put my code plus after 3 fail I want to log message and initialize some variable

    If all your attempts fail (4 in your case: the initial attempt + 3 retries) then the retry policy will throw the original exception

    try
    {
        await retryPolicy.ExecuteAsync(async () => await deviceClient.SendEventAsync(new Message()));
    }
    catch(Exception ex)
    {
        _logger.LogError(ex, "Operation failed 4 times");
    }