123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 |
- #pragma once
- #include <util/datetime/base.h>
- #include <util/generic/maybe.h>
- #include <util/generic/typetraits.h>
- #include <util/random/random.h>
- #include <functional>
- #include <limits>
- #include <memory>
- //! Retry policy.
- //! Calculates delay before next retry (if any).
- //! Has several default implementations:
- //! - exponential backoff policy;
- //! - retries with fixed interval;
- //! - no retries.
- enum class ERetryErrorClass {
- // This error shouldn't be retried.
- NoRetry,
- // This error could be retried in short period of time.
- ShortRetry,
- // This error requires waiting before it could be retried.
- LongRetry,
- };
- template <class... TArgs>
- struct IRetryPolicy {
- using TPtr = std::shared_ptr<IRetryPolicy>;
- using TRetryClassFunction = std::function<ERetryErrorClass(typename TTypeTraits<TArgs>::TFuncParam...)>;
- //! Retry state of single request.
- struct IRetryState {
- using TPtr = std::unique_ptr<IRetryState>;
- virtual ~IRetryState() = default;
- //! Calculate delay before next retry if next retry is allowed.
- //! Returns empty maybe if retry is not allowed anymore.
- [[nodiscard]] virtual TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) = 0;
- };
- virtual ~IRetryPolicy() = default;
- //! Function that is called after first error
- //! to find out a futher retry behaviour.
- //! Retry state is expected to be created for the whole single retry session.
- [[nodiscard]] virtual typename IRetryState::TPtr CreateRetryState() const = 0;
- //!
- //! Default implementations.
- //!
- static TPtr GetNoRetryPolicy(); // Denies all kind of retries.
- //! Randomized exponential backoff policy.
- static TPtr GetExponentialBackoffPolicy(TRetryClassFunction retryClassFunction,
- TDuration minDelay = TDuration::MilliSeconds(10),
- // Delay for statuses that require waiting before retry (such as OVERLOADED).
- TDuration minLongRetryDelay = TDuration::MilliSeconds(200),
- TDuration maxDelay = TDuration::Seconds(30),
- size_t maxRetries = std::numeric_limits<size_t>::max(),
- TDuration maxTime = TDuration::Max(),
- double scaleFactor = 2.0);
- //! Randomized fixed interval policy.
- static TPtr GetFixedIntervalPolicy(TRetryClassFunction retryClassFunction,
- TDuration delay = TDuration::MilliSeconds(100),
- // Delay for statuses that require waiting before retry (such as OVERLOADED).
- TDuration longRetryDelay = TDuration::MilliSeconds(300),
- size_t maxRetries = std::numeric_limits<size_t>::max(),
- TDuration maxTime = TDuration::Max());
- };
- template <class... TArgs>
- struct TNoRetryPolicy : IRetryPolicy<TArgs...> {
- using IRetryState = typename IRetryPolicy<TArgs...>::IRetryState;
- struct TNoRetryState : IRetryState {
- TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam...) override {
- return Nothing();
- }
- };
- typename IRetryState::TPtr CreateRetryState() const override {
- return std::make_unique<TNoRetryState>();
- }
- };
- namespace NRetryDetails {
- inline TDuration RandomizeDelay(TDuration baseDelay) {
- const TDuration::TValue half = baseDelay.GetValue() / 2;
- return TDuration::FromValue(half + RandomNumber<TDuration::TValue>(half));
- }
- } // namespace NRetryDetails
- template <class... TArgs>
- struct TExponentialBackoffPolicy : IRetryPolicy<TArgs...> {
- using IRetryPolicy = IRetryPolicy<TArgs...>;
- using IRetryState = typename IRetryPolicy::IRetryState;
- struct TExponentialBackoffState : IRetryState {
- TExponentialBackoffState(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
- TDuration minDelay,
- TDuration minLongRetryDelay,
- TDuration maxDelay,
- size_t maxRetries,
- TDuration maxTime,
- double scaleFactor)
- : MinLongRetryDelay(minLongRetryDelay)
- , MaxDelay(maxDelay)
- , MaxRetries(maxRetries)
- , MaxTime(maxTime)
- , ScaleFactor(scaleFactor)
- , StartTime(maxTime != TDuration::Max() ? TInstant::Now() : TInstant::Zero())
- , CurrentDelay(minDelay)
- , AttemptsDone(0)
- , RetryClassFunction(std::move(retryClassFunction))
- {
- }
- TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) override {
- const ERetryErrorClass errorClass = RetryClassFunction(args...);
- if (errorClass == ERetryErrorClass::NoRetry || AttemptsDone >= MaxRetries || StartTime && TInstant::Now() - StartTime >= MaxTime) {
- return Nothing();
- }
- if (errorClass == ERetryErrorClass::LongRetry) {
- CurrentDelay = Max(CurrentDelay, MinLongRetryDelay);
- }
- const TDuration delay = NRetryDetails::RandomizeDelay(CurrentDelay);
- if (CurrentDelay < MaxDelay) {
- CurrentDelay = Min(CurrentDelay * ScaleFactor, MaxDelay);
- }
- ++AttemptsDone;
- return delay;
- }
- const TDuration MinLongRetryDelay;
- const TDuration MaxDelay;
- const size_t MaxRetries;
- const TDuration MaxTime;
- const double ScaleFactor;
- const TInstant StartTime;
- TDuration CurrentDelay;
- size_t AttemptsDone;
- typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
- };
- TExponentialBackoffPolicy(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
- TDuration minDelay,
- TDuration minLongRetryDelay,
- TDuration maxDelay,
- size_t maxRetries,
- TDuration maxTime,
- double scaleFactor)
- : MinDelay(minDelay)
- , MinLongRetryDelay(minLongRetryDelay)
- , MaxDelay(maxDelay)
- , MaxRetries(maxRetries)
- , MaxTime(maxTime)
- , ScaleFactor(scaleFactor)
- , RetryClassFunction(std::move(retryClassFunction))
- {
- Y_ASSERT(RetryClassFunction);
- Y_ASSERT(MinDelay < MaxDelay);
- Y_ASSERT(MinLongRetryDelay < MaxDelay);
- Y_ASSERT(MinLongRetryDelay >= MinDelay);
- Y_ASSERT(ScaleFactor > 1.0);
- Y_ASSERT(MaxRetries > 0);
- Y_ASSERT(MaxTime > MinDelay);
- }
- typename IRetryState::TPtr CreateRetryState() const override {
- return std::make_unique<TExponentialBackoffState>(RetryClassFunction, MinDelay, MinLongRetryDelay, MaxDelay, MaxRetries, MaxTime, ScaleFactor);
- }
- const TDuration MinDelay;
- const TDuration MinLongRetryDelay;
- const TDuration MaxDelay;
- const size_t MaxRetries;
- const TDuration MaxTime;
- const double ScaleFactor;
- typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
- };
- template <class... TArgs>
- struct TFixedIntervalPolicy : IRetryPolicy<TArgs...> {
- using IRetryPolicy = IRetryPolicy<TArgs...>;
- using IRetryState = typename IRetryPolicy::IRetryState;
- struct TFixedIntervalState : IRetryState {
- TFixedIntervalState(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
- TDuration delay,
- TDuration longRetryDelay,
- size_t maxRetries,
- TDuration maxTime)
- : Delay(delay)
- , LongRetryDelay(longRetryDelay)
- , MaxRetries(maxRetries)
- , MaxTime(maxTime)
- , StartTime(maxTime != TDuration::Max() ? TInstant::Now() : TInstant::Zero())
- , AttemptsDone(0)
- , RetryClassFunction(std::move(retryClassFunction))
- {
- }
- TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) override {
- const ERetryErrorClass errorClass = RetryClassFunction(args...);
- if (errorClass == ERetryErrorClass::NoRetry || AttemptsDone >= MaxRetries || StartTime && TInstant::Now() - StartTime >= MaxTime) {
- return Nothing();
- }
- const TDuration delay = NRetryDetails::RandomizeDelay(errorClass == ERetryErrorClass::LongRetry ? LongRetryDelay : Delay);
- ++AttemptsDone;
- return delay;
- }
- const TDuration Delay;
- const TDuration LongRetryDelay;
- const size_t MaxRetries;
- const TDuration MaxTime;
- const TInstant StartTime;
- size_t AttemptsDone;
- typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
- };
- TFixedIntervalPolicy(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
- TDuration delay,
- TDuration longRetryDelay,
- size_t maxRetries,
- TDuration maxTime)
- : Delay(delay)
- , LongRetryDelay(longRetryDelay)
- , MaxRetries(maxRetries)
- , MaxTime(maxTime)
- , RetryClassFunction(std::move(retryClassFunction))
- {
- Y_ASSERT(RetryClassFunction);
- Y_ASSERT(MaxTime > Delay);
- Y_ASSERT(MaxTime > LongRetryDelay);
- Y_ASSERT(LongRetryDelay >= Delay);
- }
- typename IRetryState::TPtr CreateRetryState() const override {
- return std::make_unique<TFixedIntervalState>(RetryClassFunction, Delay, LongRetryDelay, MaxRetries, MaxTime);
- }
- const TDuration Delay;
- const TDuration LongRetryDelay;
- const size_t MaxRetries;
- const TDuration MaxTime;
- typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
- };
- template <class... TArgs>
- typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetNoRetryPolicy() {
- return std::make_shared<TNoRetryPolicy<TArgs...>>();
- }
- template <class... TArgs>
- typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetExponentialBackoffPolicy(TRetryClassFunction retryClassFunction,
- TDuration minDelay,
- TDuration minLongRetryDelay,
- TDuration maxDelay,
- size_t maxRetries,
- TDuration maxTime,
- double scaleFactor)
- {
- return std::make_shared<TExponentialBackoffPolicy<TArgs...>>(std::move(retryClassFunction), minDelay, minLongRetryDelay, maxDelay, maxRetries, maxTime, scaleFactor);
- }
- template <class... TArgs>
- typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetFixedIntervalPolicy(TRetryClassFunction retryClassFunction,
- TDuration delay,
- TDuration longRetryDelay,
- size_t maxRetries,
- TDuration maxTime)
- {
- return std::make_shared<TFixedIntervalPolicy<TArgs...>>(std::move(retryClassFunction), delay, longRetryDelay, maxRetries, maxTime);
- }
|