|
@@ -0,0 +1,289 @@
|
|
|
+#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.
|
|
|
+ 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.
|
|
|
+ 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(MaxRetries > 0);
|
|
|
+ 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);
|
|
|
+}
|