retry_policy.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #pragma once
  2. #include <util/datetime/base.h>
  3. #include <util/generic/maybe.h>
  4. #include <util/generic/typetraits.h>
  5. #include <util/random/random.h>
  6. #include <functional>
  7. #include <limits>
  8. #include <memory>
  9. //! Retry policy.
  10. //! Calculates delay before next retry (if any).
  11. //! Has several default implementations:
  12. //! - exponential backoff policy;
  13. //! - retries with fixed interval;
  14. //! - no retries.
  15. enum class ERetryErrorClass {
  16. // This error shouldn't be retried.
  17. NoRetry,
  18. // This error could be retried in short period of time.
  19. ShortRetry,
  20. // This error requires waiting before it could be retried.
  21. LongRetry,
  22. };
  23. template <class... TArgs>
  24. struct IRetryPolicy {
  25. using TPtr = std::shared_ptr<IRetryPolicy>;
  26. using TRetryClassFunction = std::function<ERetryErrorClass(typename TTypeTraits<TArgs>::TFuncParam...)>;
  27. //! Retry state of single request.
  28. struct IRetryState {
  29. using TPtr = std::unique_ptr<IRetryState>;
  30. virtual ~IRetryState() = default;
  31. //! Calculate delay before next retry if next retry is allowed.
  32. //! Returns empty maybe if retry is not allowed anymore.
  33. [[nodiscard]] virtual TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) = 0;
  34. };
  35. virtual ~IRetryPolicy() = default;
  36. //! Function that is called after first error
  37. //! to find out a futher retry behaviour.
  38. //! Retry state is expected to be created for the whole single retry session.
  39. [[nodiscard]] virtual typename IRetryState::TPtr CreateRetryState() const = 0;
  40. //!
  41. //! Default implementations.
  42. //!
  43. static TPtr GetNoRetryPolicy(); // Denies all kind of retries.
  44. //! Randomized exponential backoff policy.
  45. static TPtr GetExponentialBackoffPolicy(TRetryClassFunction retryClassFunction,
  46. TDuration minDelay = TDuration::MilliSeconds(10),
  47. // Delay for statuses that require waiting before retry (such as OVERLOADED).
  48. TDuration minLongRetryDelay = TDuration::MilliSeconds(200),
  49. TDuration maxDelay = TDuration::Seconds(30),
  50. size_t maxRetries = std::numeric_limits<size_t>::max(),
  51. TDuration maxTime = TDuration::Max(),
  52. double scaleFactor = 2.0);
  53. //! Randomized fixed interval policy.
  54. static TPtr GetFixedIntervalPolicy(TRetryClassFunction retryClassFunction,
  55. TDuration delay = TDuration::MilliSeconds(100),
  56. // Delay for statuses that require waiting before retry (such as OVERLOADED).
  57. TDuration longRetryDelay = TDuration::MilliSeconds(300),
  58. size_t maxRetries = std::numeric_limits<size_t>::max(),
  59. TDuration maxTime = TDuration::Max());
  60. };
  61. template <class... TArgs>
  62. struct TNoRetryPolicy : IRetryPolicy<TArgs...> {
  63. using IRetryState = typename IRetryPolicy<TArgs...>::IRetryState;
  64. struct TNoRetryState : IRetryState {
  65. TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam...) override {
  66. return Nothing();
  67. }
  68. };
  69. typename IRetryState::TPtr CreateRetryState() const override {
  70. return std::make_unique<TNoRetryState>();
  71. }
  72. };
  73. namespace NRetryDetails {
  74. inline TDuration RandomizeDelay(TDuration baseDelay) {
  75. const TDuration::TValue half = baseDelay.GetValue() / 2;
  76. return TDuration::FromValue(half + RandomNumber<TDuration::TValue>(half));
  77. }
  78. } // namespace NRetryDetails
  79. template <class... TArgs>
  80. struct TExponentialBackoffPolicy : IRetryPolicy<TArgs...> {
  81. using IRetryPolicy = IRetryPolicy<TArgs...>;
  82. using IRetryState = typename IRetryPolicy::IRetryState;
  83. struct TExponentialBackoffState : IRetryState {
  84. TExponentialBackoffState(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
  85. TDuration minDelay,
  86. TDuration minLongRetryDelay,
  87. TDuration maxDelay,
  88. size_t maxRetries,
  89. TDuration maxTime,
  90. double scaleFactor)
  91. : MinLongRetryDelay(minLongRetryDelay)
  92. , MaxDelay(maxDelay)
  93. , MaxRetries(maxRetries)
  94. , MaxTime(maxTime)
  95. , ScaleFactor(scaleFactor)
  96. , StartTime(maxTime != TDuration::Max() ? TInstant::Now() : TInstant::Zero())
  97. , CurrentDelay(minDelay)
  98. , AttemptsDone(0)
  99. , RetryClassFunction(std::move(retryClassFunction))
  100. {
  101. }
  102. TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) override {
  103. const ERetryErrorClass errorClass = RetryClassFunction(args...);
  104. if (errorClass == ERetryErrorClass::NoRetry || AttemptsDone >= MaxRetries || StartTime && TInstant::Now() - StartTime >= MaxTime) {
  105. return Nothing();
  106. }
  107. if (errorClass == ERetryErrorClass::LongRetry) {
  108. CurrentDelay = Max(CurrentDelay, MinLongRetryDelay);
  109. }
  110. const TDuration delay = NRetryDetails::RandomizeDelay(CurrentDelay);
  111. if (CurrentDelay < MaxDelay) {
  112. CurrentDelay = Min(CurrentDelay * ScaleFactor, MaxDelay);
  113. }
  114. ++AttemptsDone;
  115. return delay;
  116. }
  117. const TDuration MinLongRetryDelay;
  118. const TDuration MaxDelay;
  119. const size_t MaxRetries;
  120. const TDuration MaxTime;
  121. const double ScaleFactor;
  122. const TInstant StartTime;
  123. TDuration CurrentDelay;
  124. size_t AttemptsDone;
  125. typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
  126. };
  127. TExponentialBackoffPolicy(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
  128. TDuration minDelay,
  129. TDuration minLongRetryDelay,
  130. TDuration maxDelay,
  131. size_t maxRetries,
  132. TDuration maxTime,
  133. double scaleFactor)
  134. : MinDelay(minDelay)
  135. , MinLongRetryDelay(minLongRetryDelay)
  136. , MaxDelay(maxDelay)
  137. , MaxRetries(maxRetries)
  138. , MaxTime(maxTime)
  139. , ScaleFactor(scaleFactor)
  140. , RetryClassFunction(std::move(retryClassFunction))
  141. {
  142. Y_ASSERT(RetryClassFunction);
  143. Y_ASSERT(MinDelay < MaxDelay);
  144. Y_ASSERT(MinLongRetryDelay < MaxDelay);
  145. Y_ASSERT(MinLongRetryDelay >= MinDelay);
  146. Y_ASSERT(ScaleFactor > 1.0);
  147. Y_ASSERT(MaxRetries > 0);
  148. Y_ASSERT(MaxTime > MinDelay);
  149. }
  150. typename IRetryState::TPtr CreateRetryState() const override {
  151. return std::make_unique<TExponentialBackoffState>(RetryClassFunction, MinDelay, MinLongRetryDelay, MaxDelay, MaxRetries, MaxTime, ScaleFactor);
  152. }
  153. const TDuration MinDelay;
  154. const TDuration MinLongRetryDelay;
  155. const TDuration MaxDelay;
  156. const size_t MaxRetries;
  157. const TDuration MaxTime;
  158. const double ScaleFactor;
  159. typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
  160. };
  161. template <class... TArgs>
  162. struct TFixedIntervalPolicy : IRetryPolicy<TArgs...> {
  163. using IRetryPolicy = IRetryPolicy<TArgs...>;
  164. using IRetryState = typename IRetryPolicy::IRetryState;
  165. struct TFixedIntervalState : IRetryState {
  166. TFixedIntervalState(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
  167. TDuration delay,
  168. TDuration longRetryDelay,
  169. size_t maxRetries,
  170. TDuration maxTime)
  171. : Delay(delay)
  172. , LongRetryDelay(longRetryDelay)
  173. , MaxRetries(maxRetries)
  174. , MaxTime(maxTime)
  175. , StartTime(maxTime != TDuration::Max() ? TInstant::Now() : TInstant::Zero())
  176. , AttemptsDone(0)
  177. , RetryClassFunction(std::move(retryClassFunction))
  178. {
  179. }
  180. TMaybe<TDuration> GetNextRetryDelay(typename TTypeTraits<TArgs>::TFuncParam... args) override {
  181. const ERetryErrorClass errorClass = RetryClassFunction(args...);
  182. if (errorClass == ERetryErrorClass::NoRetry || AttemptsDone >= MaxRetries || StartTime && TInstant::Now() - StartTime >= MaxTime) {
  183. return Nothing();
  184. }
  185. const TDuration delay = NRetryDetails::RandomizeDelay(errorClass == ERetryErrorClass::LongRetry ? LongRetryDelay : Delay);
  186. ++AttemptsDone;
  187. return delay;
  188. }
  189. const TDuration Delay;
  190. const TDuration LongRetryDelay;
  191. const size_t MaxRetries;
  192. const TDuration MaxTime;
  193. const TInstant StartTime;
  194. size_t AttemptsDone;
  195. typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
  196. };
  197. TFixedIntervalPolicy(typename IRetryPolicy::TRetryClassFunction retryClassFunction,
  198. TDuration delay,
  199. TDuration longRetryDelay,
  200. size_t maxRetries,
  201. TDuration maxTime)
  202. : Delay(delay)
  203. , LongRetryDelay(longRetryDelay)
  204. , MaxRetries(maxRetries)
  205. , MaxTime(maxTime)
  206. , RetryClassFunction(std::move(retryClassFunction))
  207. {
  208. Y_ASSERT(RetryClassFunction);
  209. Y_ASSERT(MaxTime > Delay);
  210. Y_ASSERT(MaxTime > LongRetryDelay);
  211. Y_ASSERT(LongRetryDelay >= Delay);
  212. }
  213. typename IRetryState::TPtr CreateRetryState() const override {
  214. return std::make_unique<TFixedIntervalState>(RetryClassFunction, Delay, LongRetryDelay, MaxRetries, MaxTime);
  215. }
  216. const TDuration Delay;
  217. const TDuration LongRetryDelay;
  218. const size_t MaxRetries;
  219. const TDuration MaxTime;
  220. typename IRetryPolicy::TRetryClassFunction RetryClassFunction;
  221. };
  222. template <class... TArgs>
  223. typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetNoRetryPolicy() {
  224. return std::make_shared<TNoRetryPolicy<TArgs...>>();
  225. }
  226. template <class... TArgs>
  227. typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetExponentialBackoffPolicy(TRetryClassFunction retryClassFunction,
  228. TDuration minDelay,
  229. TDuration minLongRetryDelay,
  230. TDuration maxDelay,
  231. size_t maxRetries,
  232. TDuration maxTime,
  233. double scaleFactor)
  234. {
  235. return std::make_shared<TExponentialBackoffPolicy<TArgs...>>(std::move(retryClassFunction), minDelay, minLongRetryDelay, maxDelay, maxRetries, maxTime, scaleFactor);
  236. }
  237. template <class... TArgs>
  238. typename IRetryPolicy<TArgs...>::TPtr IRetryPolicy<TArgs...>::GetFixedIntervalPolicy(TRetryClassFunction retryClassFunction,
  239. TDuration delay,
  240. TDuration longRetryDelay,
  241. size_t maxRetries,
  242. TDuration maxTime)
  243. {
  244. return std::make_shared<TFixedIntervalPolicy<TArgs...>>(std::move(retryClassFunction), delay, longRetryDelay, maxRetries, maxTime);
  245. }