Обеспечьте выполнение политики Polly хотя бы один раз - PullRequest
1 голос
/ 16 июня 2020

Итак, я пишу несколько попыток c для получения блокировки с помощью Polly. Общее значение тайм-аута будет предоставлено вызывающим API. Я знаю, что могу заключить политику в общий тайм-аут. Однако, если предоставленное значение тайм-аута слишком низкое, есть ли способ обеспечить выполнение политики хотя бы один раз? в Polly был способ express это требование.

var result = Policy.Timeout(timeoutFromApiCaller)
                    .Wrap(Policy.HandleResult(false)
                                .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500))
                    .Execute(() => this.TryEnterLock());

Если timeoutFromApiCaller - это, скажем, 1 tick и есть большая вероятность, что для достижения политики тайм-аута потребуется больше времени, тогда делегат не будет не будет вызван (политика будет тайм-аут и выбросит TimeoutRejectedException).

То, что я хотел бы, можно выразить как:

var result = this.TryEnterLock();

if (!result)
{
    result = Policy.Timeout(timeoutFromApiCaller)
                   .Wrap(Policy.HandleResult(false)
                               .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500))
                   .Execute(() => this.TryEnterLock());
}

Но это было бы очень хорошо если бы это можно было выразить чисто по-полли ...

1 Ответ

1 голос
/ 17 июня 2020

Если честно, я не понимаю, что означает 1 галочка , в вашем случае? Это наносекунда или больше? Ваш глобальный тайм-аут должен быть больше вашего локального тайм-аута.

Но, как я вижу, у вас не указан локальный . TryEnterLock должен получить TimeSpan, чтобы не блокировать вызывающего абонента на бесконечное время. Если вы посмотрите на встроенные примитивы syn c, большинство из них предоставляют такие возможности: Monitor.TryEnter , SpinLock.TryEnter , WaitHandle.WaitOne , et c.

Итак, подытожим:

var timeoutPolicy = Policy.Timeout(TimeSpan.FromMilliseconds(1000));
var retryPolicy = Policy.HandleResult(false)
       .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500));
var resilientStrategy = Policy.Wrap(timeoutPolicy, retryPolicy);    

var result = resilientStrategy.Execute(() => this.TryEnterLock(TimeSpan.FromMilliseconds(100))); 

Значения тайм-аута и задержки должны быть скорректированы в соответствии с потребностями вашего бизнеса. Я настоятельно рекомендую вам регистрировать, когда срабатывает глобальный тайм-аут (onTimeout / onTimeoutAsync) и когда повторяются попытки (onRetry / onRetryAsync), чтобы иметь возможность точно настроить / откалибровать эти значения.


РЕДАКТИРОВАТЬ: на основе комментариев к этому сообщению

Как оказалось, нет контроля над timeoutFromApiCaller, поэтому он может быть произвольно маленьким. (В данном примере это всего несколько наносекунд, с целью подчеркнуть проблему.) Итак, чтобы иметь хотя бы один гарантированный вызов, мы должны использовать Резервную политику .

Вместо того, чтобы вручную вызывать TryEnterLock вне политик, мы должны называть его последним действием для удовлетворения требований. Поскольку политики используют эскалацию, поэтому всякий раз, когда внутренний выходит из строя, он делегирует проблему следующей внешней политике.

Итак, если предоставленный тайм-аут настолько мал, что Действие не может быть завершено sh до этого периода, тогда оно выдаст TimeoutRejectedException. С помощью Fallback мы можем справиться с этим, и действие может быть выполнено снова , но теперь без ограничения времени ожидания. Это даст нам желаемую хотя бы одну гарантию.

var atLeastOnce = Policy.Handle<TimeoutRejectedException>
    .Fallback((ct) => this.TryEnterLock());
var globalTimeout = Policy.Timeout(TimeSpan.FromMilliseconds(1000));
var foreverRetry = Policy.HandleResult(false)
       .WaitAndRetryForever(_ => TimeSpan.FromMilliseconds(500));

var resilientStrategy = Policy.Wrap(atLeastOnce, globalTimeout, foreverRetry);    
var result = resilientStrategy.Execute(() => this.TryEnterLock()); 
...