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
{
//
}
CodePudding user response:
You can Handle the generic Exception and validate the errorCode to return bool value.
Policy
.Handle<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'
return true;
}
}
return false;
})
.WaitAndRetry(3, _ => TimeSpan.FromSeconds(3))
.Execute(DoSomething);
CodePudding user response:
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 fails (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");
}