Как добавить динамические политики повторов, используя ядро dotnet и Polly
У меня есть консольное приложение dotnet core (2.1), и я использую Polly, чтобы обернуть сегмент моего кода с помощью политики повторных попыток. Это прекрасно работает с простым примером использования, показанным ниже:
private void ProcessRun()
{
var policy = Policy.Handle<SocketException>().WaitAndRetryAsync(
retryCount: 3
sleepDurationProvider: attempt => TimeSpan.FromSeconds(10),
onRetry: (exception, calculatedWaitDuration) =>
{
Console.WriteLine($"Retry policy executed for type SocketException");
});
try
{
CancellationTokenSource _cts = new CancellationTokenSource()
PollyRetryWaitPolicy.ExecuteAsync(async token => {
MyOperation(token);
}, _cts.Token)
.ContinueWith(p => {
if (p.IsFaulted || p.Status == TaskStatus.Canceled)
{
Console.WriteLine("faulted or was cancelled");
}
})
.ConfigureAwait(false);
}
catch (Exception ex) {
Console.WriteLine($"Exception has occurred: {ex.Message}");
}
}
Затем я проверяю это с помощью этого кода:
private void MyOperation()
{
Thread.Sleep(2000);
throw new SocketException();
}
Когда код выполняется, исключение сокета перехватывается, как и ожидалось.
Я искал свой гибкий способ обернуть мой код несколькими политиками вместо одной. Я изменил код, чтобы динамически добавлять несколько обернутых политик Polly Retry вместе, чтобы можно было перехватывать более одного типа ошибок и легко изменять исключения, которые я ищу. Я изменил код на это:
internal PolicyWrap PollyRetryWaitPolicy;
public void AddRetryWaitPolicy<T>(int waitTimeInSeconds, int retryAttempts)
where T: Exception
{
// Setup the polly policy that will be added to the executing code.
var policy = Policy.Handle<T>().WaitAndRetryAsync(
retryCount: retryAttempts,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(waitTimeInSeconds),
onRetry: (exception, calculatedWaitDuration) =>
{
Console.WriteLine($"Retry policy executed for type {typeof(T).Name}");
});
if (host.PollyRetryWaitPolicy == null)
{
// NOTE: Only add this timeout policy as it seems to need at least one
// policy before it can wrap! (suppose that makes sense).
var timeoutPolicy = Policy
.TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
}
else
{
PollyRetryWaitPolicy.WrapAsync(policy);
}
}
private void ProcessRun()
{
AddRetryWaitPolicy<SocketException>(10, 5);
AddRetryWaitPolicy<InvalidOperationException>(5, 2);
try
{
Console.WriteLine($"Calling HostedProcess.Run() method. AwaitResult: {awaitResult}");
CancellationTokenSource _cts = new CancellationTokenSource()
PollyRetryWaitPolicy.ExecuteAsync(async token => {
MyOperation(token);
}, _cts.Token)
.ContinueWith(p => {
if (p.IsFaulted || p.Status == TaskStatus.Canceled)
{
Console.WriteLine("Process has faulted or was cancelled");
}
})
.ConfigureAwait(false);
}
catch (Exception ex) {
Console.WriteLine($"Exception has occurred: {ex.Message}");
}
}
Когда я тестирую этот код, приведенный выше код работает как положено и повторяется 5 раз.
private void MyOperation()
{
Thread.Sleep(2000);
throw new SocketException();
}
НО, когда я пытаюсь со следующим, он не повторяет 2 раза, как ожидалось (это не повторяет вообще):
private void MyOperation()
{
Thread.Sleep(2000);
throw new InvalidOperationException();
}
Что я делаю неправильно? Смысл всего вышеперечисленного заключается в динамическом множествении политик, как мне требуется. Есть ли лучший способ сделать что-то кроме WrapPolicy?
Спасибо за указатели заранее!
1 ответ
Вот:
if (host.PollyRetryWaitPolicy == null)
{
// NOTE: Only add this timeout policy as it seems to need at least one
// policy before it can wrap! (suppose that makes sense).
var timeoutPolicy = Policy
.TimeoutAsync(TimeSpan.FromSeconds(waitTimeInSeconds), TimeoutStrategy.Pessimistic);
PollyRetryWaitPolicy = policy.WrapAsync(timeoutPolicy);
}
else
{
PollyRetryWaitPolicy.WrapAsync(policy);
}
кажется if
филиал и else
В филиале используется непоследовательный подход к последовательности политики повтора и политики тайм-аута в переносе.
- в
if
ветвь, политика тайм-аута обернута внутри политики повтора. Таким образом, тайм-аут действует как тайм-аут на попытку. - в
else
ветвь, любая существующая политика повторных попыток истекает вне новой политики повторных попыток. Таким образом, политика тайм-аута выходит за рамки новой политики повторных попыток.- Новая политика повторов настроена на повтор после
waitTimeInSeconds
; но политика тайм-аута также настроена на тайм-аут выполнения послеwaitTimeInSeconds
, Таким образом, как только происходит первая повторная попытка (для настроенной второй или последующей политики повторных попыток), тайм-аут прерывает все выполнение. Так что повторение никогда не происходит, как вы заметили.
- Новая политика повторов настроена на повтор после
Чтобы вылечить это, вы можете изменить else
филиал в:
PollyRetryWaitPolicy = policy.WrapAsync(PollyRetryWaitPolicy);
Предыстория: см. Рекомендации вики PolicyWrap по упорядочению политик в PolicyWrap. В зависимости от того, место вы TimeoutPolicy
внутри или снаружи RetryPolicy
, TimeoutPolicy
действует (когда внутри) как тайм-аут на попытку, или (когда снаружи) как общее время ожидания для всех попыток.