#pragma once #include "retry_policy.h" #include "utils.h" #include <library/cpp/retry/protos/retry_options.pb.h> #include <util/datetime/base.h> #include <util/generic/maybe.h> #include <util/generic/typetraits.h> #include <util/generic/yexception.h> #include <functional> struct TRetryOptions { ui32 RetryCount; // TotalDuration = SleepDuration +/- SleepRandomDelta + (attempt * SleepIncrement) + (2**attempt * SleepExponentialMultiplier) TDuration SleepDuration; TDuration SleepRandomDelta; TDuration SleepIncrement; TDuration SleepExponentialMultiplier; std::function<void(TDuration)> SleepFunction; TRetryOptions(ui32 retryCount = 3, TDuration sleepDuration = TDuration::Seconds(1), TDuration sleepRandomDelta = TDuration::Zero(), TDuration sleepIncrement = TDuration::Zero(), TDuration sleepExponentialMultiplier = TDuration::Zero(), std::function<void(TDuration)> sleepFunction = [](TDuration d) { Sleep(d); }) // can't use Sleep itself due to Win compilation error : RetryCount(retryCount) , SleepDuration(sleepDuration) , SleepRandomDelta(sleepRandomDelta) , SleepIncrement(sleepIncrement) , SleepExponentialMultiplier(sleepExponentialMultiplier) , SleepFunction(sleepFunction) { } TRetryOptions& WithCount(ui32 retryCount) { RetryCount = retryCount; return *this; } TRetryOptions& WithSleep(TDuration sleepDuration) { SleepDuration = sleepDuration; return *this; } TRetryOptions& WithRandomDelta(TDuration sleepRandomDelta) { SleepRandomDelta = sleepRandomDelta; return *this; } TRetryOptions& WithIncrement(TDuration sleepIncrement) { SleepIncrement = sleepIncrement; return *this; } TRetryOptions& WithExponentialMultiplier(TDuration sleepExponentialMultiplier) { SleepExponentialMultiplier = sleepExponentialMultiplier; return *this; } TRetryOptions& WithSleepFunction(std::function<void(TDuration)> sleepFunction) { SleepFunction = sleepFunction; return *this; } // for compatibility attempt == 0 by default TDuration GetTimeToSleep(ui32 attempt = 0) const { return SleepDuration + NRetryPrivate::AddRandomDelta(SleepRandomDelta) + NRetryPrivate::AddIncrement(attempt, SleepIncrement) + NRetryPrivate::AddExponentialMultiplier(attempt, SleepExponentialMultiplier); } static TRetryOptions Count(ui32 retryCount) { return TRetryOptions(retryCount); } static TRetryOptions Default() { return TRetryOptions(); } static TRetryOptions NoRetry() { return TRetryOptions(0); } }; TRetryOptions MakeRetryOptions(const NRetry::TRetryOptionsPB& retryOptions); namespace NRetryDetails { template <class TException> class TRetryOptionsPolicy : public IRetryPolicy<const TException&> { public: explicit TRetryOptionsPolicy(const TRetryOptions& opts) : Opts(opts) { } using IRetryState = typename IRetryPolicy<const TException&>::IRetryState; class TRetryState : public IRetryState { public: explicit TRetryState(const TRetryOptions& opts) : Opts(opts) { } TMaybe<TDuration> GetNextRetryDelay(const TException&) override { if (Attempt == Opts.RetryCount) { return Nothing(); } return Opts.GetTimeToSleep(Attempt++); } private: const TRetryOptions Opts; size_t Attempt = 0; }; typename IRetryState::TPtr CreateRetryState() const override { return std::make_unique<TRetryState>(Opts); } private: const TRetryOptions Opts; }; } // namespace NRetryDetails template <class TException> typename IRetryPolicy<const TException&>::TPtr MakeRetryPolicy(const TRetryOptions& opts) { return std::make_shared<NRetryDetails::TRetryOptionsPolicy<TException>>(opts); } template <class TException> typename IRetryPolicy<const TException&>::TPtr MakeRetryPolicy(const NRetry::TRetryOptionsPB& opts) { return MakeRetryPolicy<TException>(MakeRetryOptions(opts)); } template <typename TResult, typename TException = yexception> TMaybe<TResult> DoWithRetry(std::function<TResult()> func, const typename IRetryPolicy<const TException&>::TPtr& retryPolicy, bool throwLast = true, std::function<void(const TException&)> onFail = {}, std::function<void(TDuration)> sleepFunction = {}) { typename IRetryPolicy<const TException&>::IRetryState::TPtr retryState; while (true) { try { return func(); } catch (const TException& ex) { if (onFail) { onFail(ex); } if (!retryState) { retryState = retryPolicy->CreateRetryState(); } if (const TMaybe<TDuration> delay = retryState->GetNextRetryDelay(ex)) { if (*delay) { if (sleepFunction) { sleepFunction(*delay); } else { Sleep(*delay); } } } else { if (throwLast) { throw; } break; } } } return Nothing(); } template <typename TResult, typename TException = yexception> TMaybe<TResult> DoWithRetry(std::function<TResult()> func, std::function<void(const TException&)> onFail, TRetryOptions retryOptions, bool throwLast = true) { return DoWithRetry<TResult, TException>(std::move(func), MakeRetryPolicy<TException>(retryOptions), throwLast, std::move(onFail), retryOptions.SleepFunction); } template <typename TResult, typename TException = yexception> TMaybe<TResult> DoWithRetry(std::function<TResult()> func, TRetryOptions retryOptions, bool throwLast = true) { return DoWithRetry<TResult, TException>(std::move(func), MakeRetryPolicy<TException>(retryOptions), throwLast, {}, retryOptions.SleepFunction); } template <typename TException = yexception> bool DoWithRetry(std::function<void()> func, const typename IRetryPolicy<const TException&>::TPtr& retryPolicy, bool throwLast = true, std::function<void(const TException&)> onFail = {}, std::function<void(TDuration)> sleepFunction = {}) { auto f = [&]() { func(); return nullptr; }; return DoWithRetry<void*, TException>(f, retryPolicy, throwLast, std::move(onFail), std::move(sleepFunction)).Defined(); } template <typename TException = yexception> bool DoWithRetry(std::function<void()> func, std::function<void(const TException&)> onFail, TRetryOptions retryOptions, bool throwLast) { return DoWithRetry<TException>(std::move(func), MakeRetryPolicy<TException>(retryOptions), throwLast, onFail, retryOptions.SleepFunction); } template <typename TException = yexception> bool DoWithRetry(std::function<void()> func, TRetryOptions retryOptions, bool throwLast = true) { return DoWithRetry<TException>(std::move(func), MakeRetryPolicy<TException>(retryOptions), throwLast, {}, retryOptions.SleepFunction); } template <class TRetCode> TRetCode DoWithRetryOnRetCode(std::function<TRetCode()> func, const typename IRetryPolicy<TRetCode>::TPtr& retryPolicy, std::function<void(TDuration)> sleepFunction = {}) { auto retryState = retryPolicy->CreateRetryState(); while (true) { TRetCode code = func(); if (const TMaybe<TDuration> delay = retryState->GetNextRetryDelay(code)) { if (*delay) { if (sleepFunction) { sleepFunction(*delay); } else { Sleep(*delay); } } } else { return code; } } } bool DoWithRetryOnRetCode(std::function<bool()> func, TRetryOptions retryOptions); Y_DECLARE_PODTYPE(TRetryOptions);