aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/retry/retry_policy.h
diff options
context:
space:
mode:
authorVasily Gerasimov <UgnineSirdis@gmail.com>2022-02-16 14:31:05 +0300
committerVasily Gerasimov <UgnineSirdis@gmail.com>2022-02-16 14:31:05 +0300
commit91baa58196a4f481f246c747dc20c9b25ff96cb4 (patch)
treed94e4d04f452da6d8bcda3fde8ba2e1c6707a583 /library/cpp/retry/retry_policy.h
parentc1dea0408f5bf27e7e79d0439cddb420bec298f5 (diff)
downloadydb-91baa58196a4f481f246c747dc20c9b25ff96cb4.tar.gz
YQ-842 Move IRetryPolicy from PQ SDK to library
ref:6f3f663c57e22b6d97bfc34fb795be48ad8e6ec0
Diffstat (limited to 'library/cpp/retry/retry_policy.h')
-rw-r--r--library/cpp/retry/retry_policy.h289
1 files changed, 289 insertions, 0 deletions
diff --git a/library/cpp/retry/retry_policy.h b/library/cpp/retry/retry_policy.h
new file mode 100644
index 00000000000..a2f94840b86
--- /dev/null
+++ b/library/cpp/retry/retry_policy.h
@@ -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);
+}