#pragma once #include #include #include #include #include #include #include //! 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 struct IRetryPolicy { using TPtr = std::shared_ptr; using TRetryClassFunction = std::function::TFuncParam...)>; //! Retry state of single request. struct IRetryState { using TPtr = std::unique_ptr; 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 GetNextRetryDelay(typename TTypeTraits::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::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::max(), TDuration maxTime = TDuration::Max()); }; template struct TNoRetryPolicy : IRetryPolicy { using IRetryState = typename IRetryPolicy::IRetryState; struct TNoRetryState : IRetryState { TMaybe GetNextRetryDelay(typename TTypeTraits::TFuncParam...) override { return Nothing(); } }; typename IRetryState::TPtr CreateRetryState() const override { return std::make_unique(); } }; namespace NRetryDetails { inline TDuration RandomizeDelay(TDuration baseDelay) { const TDuration::TValue half = baseDelay.GetValue() / 2; return TDuration::FromValue(half + RandomNumber(half)); } } // namespace NRetryDetails template struct TExponentialBackoffPolicy : IRetryPolicy { using IRetryPolicy = IRetryPolicy; 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 GetNextRetryDelay(typename TTypeTraits::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(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 struct TFixedIntervalPolicy : IRetryPolicy { using IRetryPolicy = IRetryPolicy; 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 GetNextRetryDelay(typename TTypeTraits::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(RetryClassFunction, Delay, LongRetryDelay, MaxRetries, MaxTime); } const TDuration Delay; const TDuration LongRetryDelay; const size_t MaxRetries; const TDuration MaxTime; typename IRetryPolicy::TRetryClassFunction RetryClassFunction; }; template typename IRetryPolicy::TPtr IRetryPolicy::GetNoRetryPolicy() { return std::make_shared>(); } template typename IRetryPolicy::TPtr IRetryPolicy::GetExponentialBackoffPolicy(TRetryClassFunction retryClassFunction, TDuration minDelay, TDuration minLongRetryDelay, TDuration maxDelay, size_t maxRetries, TDuration maxTime, double scaleFactor) { return std::make_shared>(std::move(retryClassFunction), minDelay, minLongRetryDelay, maxDelay, maxRetries, maxTime, scaleFactor); } template typename IRetryPolicy::TPtr IRetryPolicy::GetFixedIntervalPolicy(TRetryClassFunction retryClassFunction, TDuration delay, TDuration longRetryDelay, size_t maxRetries, TDuration maxTime) { return std::make_shared>(std::move(retryClassFunction), delay, longRetryDelay, maxRetries, maxTime); }