diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/retry | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/retry')
-rw-r--r-- | library/cpp/retry/protos/retry_options.proto | 9 | ||||
-rw-r--r-- | library/cpp/retry/protos/ya.make | 16 | ||||
-rw-r--r-- | library/cpp/retry/retry.cpp | 26 | ||||
-rw-r--r-- | library/cpp/retry/retry.h | 133 | ||||
-rw-r--r-- | library/cpp/retry/retry_ut.cpp | 117 | ||||
-rw-r--r-- | library/cpp/retry/ut/ya.make | 13 | ||||
-rw-r--r-- | library/cpp/retry/utils.cpp | 20 | ||||
-rw-r--r-- | library/cpp/retry/utils.h | 10 | ||||
-rw-r--r-- | library/cpp/retry/ya.make | 17 |
9 files changed, 361 insertions, 0 deletions
diff --git a/library/cpp/retry/protos/retry_options.proto b/library/cpp/retry/protos/retry_options.proto new file mode 100644 index 00000000000..063fff01412 --- /dev/null +++ b/library/cpp/retry/protos/retry_options.proto @@ -0,0 +1,9 @@ +package NRetry; + +message TRetryOptionsPB { + optional uint32 MaxTries = 1 [default = 5]; + optional uint32 InitialSleepMs = 2 [default = 0]; + optional uint32 SleepIncrementMs = 3 [default = 0]; + optional uint32 RandomDeltaMs = 4 [default = 10]; + optional uint32 ExponentalMultiplierMs = 5 [default = 100]; +} diff --git a/library/cpp/retry/protos/ya.make b/library/cpp/retry/protos/ya.make new file mode 100644 index 00000000000..ad8ea2086a3 --- /dev/null +++ b/library/cpp/retry/protos/ya.make @@ -0,0 +1,16 @@ +PROTO_LIBRARY() + +OWNER( + anskor +) + +SRCS( + retry_options.proto +) + +PEERDIR() + +EXCLUDE_TAGS(GO_PROTO) + +END() + diff --git a/library/cpp/retry/retry.cpp b/library/cpp/retry/retry.cpp new file mode 100644 index 00000000000..92466cdeca5 --- /dev/null +++ b/library/cpp/retry/retry.cpp @@ -0,0 +1,26 @@ +#include "retry.h" + +#include <util/stream/output.h> + +void DoWithRetry(std::function<void()> func, TRetryOptions retryOptions) { + DoWithRetry(func, retryOptions, true); +} + +bool DoWithRetryOnRetCode(std::function<bool()> func, TRetryOptions retryOptions) { + for (ui32 attempt = 0; attempt <= retryOptions.RetryCount; ++attempt) { + if (func()) { + return true; + } + auto sleep = retryOptions.SleepFunction; + sleep(retryOptions.GetTimeToSleep(attempt)); + } + return false; +} + +TRetryOptions MakeRetryOptions(const NRetry::TRetryOptionsPB& retryOptions) { + return TRetryOptions(retryOptions.GetMaxTries(), + TDuration::MilliSeconds(retryOptions.GetInitialSleepMs()), + TDuration::MilliSeconds(retryOptions.GetRandomDeltaMs()), + TDuration::MilliSeconds(retryOptions.GetSleepIncrementMs()), + TDuration::MilliSeconds(retryOptions.GetExponentalMultiplierMs())); +} diff --git a/library/cpp/retry/retry.h b/library/cpp/retry/retry.h new file mode 100644 index 00000000000..c47ff5070fe --- /dev/null +++ b/library/cpp/retry/retry.h @@ -0,0 +1,133 @@ +#pragma once + +#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); + } +}; + +template <typename TResult, typename TException = yexception> +TMaybe<TResult> DoWithRetry(std::function<TResult()> func, std::function<void(const TException&)> onFail, TRetryOptions retryOptions, bool throwLast) { + for (ui32 attempt = 0; attempt <= retryOptions.RetryCount; ++attempt) { + try { + return func(); + } catch (TException& ex) { + onFail(ex); + if (attempt == retryOptions.RetryCount) { + if (throwLast) { + throw; + } + } else { + auto sleep = retryOptions.SleepFunction; + sleep(retryOptions.GetTimeToSleep(attempt)); + } + } + } + return Nothing(); +} + +template <typename TResult, typename TException = yexception> +TMaybe<TResult> DoWithRetry(std::function<TResult()> func, TRetryOptions retryOptions, bool throwLast) { + return DoWithRetry<TResult, TException>(func, [](const TException&){}, retryOptions, throwLast); +} + +template <typename TException = yexception> +bool DoWithRetry(std::function<void()> func, std::function<void(const TException&)> onFail, TRetryOptions retryOptions, bool throwLast) { + auto f = [&]() { + func(); + return nullptr; + }; + return DoWithRetry<void*, TException>(f, onFail, retryOptions, throwLast).Defined(); +} + +template <typename TException = yexception> +bool DoWithRetry(std::function<void()> func, TRetryOptions retryOptions, bool throwLast) { + auto f = [&]() { + func(); + return nullptr; + }; + return DoWithRetry<void*, TException>(f, [](const TException&){}, retryOptions, throwLast).Defined(); +} + +void DoWithRetry(std::function<void()> func, TRetryOptions retryOptions); + +bool DoWithRetryOnRetCode(std::function<bool()> func, TRetryOptions retryOptions); + +Y_DECLARE_PODTYPE(TRetryOptions); + +TRetryOptions MakeRetryOptions(const NRetry::TRetryOptionsPB& retryOptions); diff --git a/library/cpp/retry/retry_ut.cpp b/library/cpp/retry/retry_ut.cpp new file mode 100644 index 00000000000..92153e987eb --- /dev/null +++ b/library/cpp/retry/retry_ut.cpp @@ -0,0 +1,117 @@ +#include "retry.h" + +#include <library/cpp/testing/unittest/registar.h> + +namespace { + class TDoOnSecondOrThrow { + public: + ui32 operator()() { + if (attempt++ != 1) { + throw yexception(); + } + return 42; + } + + private: + ui32 attempt = 0; + }; + + class TDoOnSecondOrFail { + public: + bool operator()() { + return (attempt++ == 1); + } + + private: + ui32 attempt = 0; + }; +} + +Y_UNIT_TEST_SUITE(Retry) { + Y_UNIT_TEST(RetryOnExceptionSuccess) { + UNIT_ASSERT_NO_EXCEPTION(DoWithRetry(TDoOnSecondOrThrow{}, TRetryOptions(1, TDuration::Zero()))); + } + Y_UNIT_TEST(RetryOnExceptionSuccessWithOnFail) { + ui32 value = 0; + std::function<void(const yexception&)> cb = [&value](const yexception&){ value += 1; }; + UNIT_ASSERT_NO_EXCEPTION(DoWithRetry<ui32>(TDoOnSecondOrThrow{}, cb, TRetryOptions(1, TDuration::Zero()), true)); + UNIT_ASSERT_EQUAL(value, 1); + } + Y_UNIT_TEST(RetryOnExceptionFail) { + UNIT_ASSERT_EXCEPTION(DoWithRetry(TDoOnSecondOrThrow{}, TRetryOptions(0, TDuration::Zero())), yexception); + } + Y_UNIT_TEST(RetryOnExceptionFailWithOnFail) { + ui32 value = 0; + std::function<void(const yexception&)> cb = [&value](const yexception&) { value += 1; }; + UNIT_ASSERT_EXCEPTION(DoWithRetry<ui32>(TDoOnSecondOrThrow{}, cb, TRetryOptions(0, TDuration::Zero()), true), yexception); + UNIT_ASSERT_EQUAL(value, 1); + } + + Y_UNIT_TEST(RetryOnExceptionSuccessWithValue) { + std::function<ui32()> f = TDoOnSecondOrThrow{}; + UNIT_ASSERT(42 == *DoWithRetry<ui32>(f, TRetryOptions(1, TDuration::Zero()), false)); + } + Y_UNIT_TEST(RetryOnExceptionSuccessWithValueWithOnFail) { + ui32 value = 0; + std::function<ui32()> f = TDoOnSecondOrThrow{}; + std::function<void(const yexception&)> cb = [&value](const yexception&){ value += 1; }; + UNIT_ASSERT(42 == *DoWithRetry<ui32>(f, cb, TRetryOptions(1, TDuration::Zero()), false)); + UNIT_ASSERT_EQUAL(value, 1); + } + Y_UNIT_TEST(RetryOnExceptionFailWithValue) { + std::function<ui32()> f = TDoOnSecondOrThrow{}; + UNIT_ASSERT(!DoWithRetry<ui32>(f, TRetryOptions(0, TDuration::Zero()), false).Defined()); + } + Y_UNIT_TEST(RetryOnExceptionFailWithValueWithOnFail) { + ui32 value = 0; + std::function<ui32()> f = TDoOnSecondOrThrow{}; + std::function<void(const yexception&)> cb = [&value](const yexception&){ value += 1; }; + UNIT_ASSERT(!DoWithRetry<ui32>(f, cb, TRetryOptions(0, TDuration::Zero()), false).Defined()); + UNIT_ASSERT_EQUAL(value, 1); + } + + Y_UNIT_TEST(RetryOnExceptionSuccessWithValueAndRethrow) { + std::function<ui32()> f = TDoOnSecondOrThrow{}; + UNIT_ASSERT(42 == *DoWithRetry<ui32>(f, TRetryOptions(1, TDuration::Zero()), true)); + } + Y_UNIT_TEST(RetryOnExceptionSuccessWithValueAndRethrowWithOnFail) { + ui32 value = 0; + std::function<ui32()> f = TDoOnSecondOrThrow{}; + std::function<void(const yexception&)> cb = [&value](const yexception&){ value += 1; }; + UNIT_ASSERT(42 == *DoWithRetry<ui32>(f, cb, TRetryOptions(1, TDuration::Zero()), true)); + UNIT_ASSERT_EQUAL(value, 1); + } + Y_UNIT_TEST(RetryOnExceptionFailWithValueAndRethrow) { + std::function<ui32()> f = TDoOnSecondOrThrow{}; + UNIT_ASSERT_EXCEPTION(DoWithRetry<ui32>(f, TRetryOptions(0, TDuration::Zero()), true), yexception); + } + Y_UNIT_TEST(RetryOnExceptionFailWithValueAndRethrowWithOnFail) { + ui32 value = 0; + std::function<ui32()> f = TDoOnSecondOrThrow{}; + std::function<void(const yexception&)> cb = [&value](const yexception&){ value += 1; }; + UNIT_ASSERT_EXCEPTION(42 == *DoWithRetry<ui32>(f, cb, TRetryOptions(0, TDuration::Zero()), true), yexception); + UNIT_ASSERT_EQUAL(value, 1); + } + + Y_UNIT_TEST(RetryOnRetCodeSuccess) { + UNIT_ASSERT(true == DoWithRetryOnRetCode(TDoOnSecondOrFail{}, TRetryOptions(1, TDuration::Zero()))); + } + Y_UNIT_TEST(RetryOnRetCodeFail) { + UNIT_ASSERT(false == DoWithRetryOnRetCode(TDoOnSecondOrFail{}, TRetryOptions(0, TDuration::Zero()))); + } + Y_UNIT_TEST(MakeRetryOptionsFromProto) { + NRetry::TRetryOptionsPB protoOptions; + protoOptions.SetMaxTries(1); + protoOptions.SetInitialSleepMs(2); + protoOptions.SetSleepIncrementMs(3); + protoOptions.SetRandomDeltaMs(4); + protoOptions.SetExponentalMultiplierMs(5); + + const TRetryOptions options = MakeRetryOptions(protoOptions); + UNIT_ASSERT_EQUAL(options.RetryCount, 1); + UNIT_ASSERT_EQUAL(options.SleepDuration, TDuration::MilliSeconds(2)); + UNIT_ASSERT_EQUAL(options.SleepIncrement, TDuration::MilliSeconds(3)); + UNIT_ASSERT_EQUAL(options.SleepRandomDelta, TDuration::MilliSeconds(4)); + UNIT_ASSERT_EQUAL(options.SleepExponentialMultiplier, TDuration::MilliSeconds(5)); + } +} diff --git a/library/cpp/retry/ut/ya.make b/library/cpp/retry/ut/ya.make new file mode 100644 index 00000000000..ff8259bfdbc --- /dev/null +++ b/library/cpp/retry/ut/ya.make @@ -0,0 +1,13 @@ +UNITTEST_FOR(library/cpp/retry) + +OWNER( + salmin + osado + g:yabs-small +) + +SRCS( + retry_ut.cpp +) + +END() diff --git a/library/cpp/retry/utils.cpp b/library/cpp/retry/utils.cpp new file mode 100644 index 00000000000..24d943f5185 --- /dev/null +++ b/library/cpp/retry/utils.cpp @@ -0,0 +1,20 @@ +#include "utils.h" + +#include <util/random/random.h> + +TDuration NRetryPrivate::AddRandomDelta(TDuration maxDelta) { + if (maxDelta == TDuration::Zero()) { + return TDuration::Zero(); + } + + const TDuration delta = TDuration::MicroSeconds(RandomNumber(2 * maxDelta.MicroSeconds())); + return delta - maxDelta; +} + +TDuration NRetryPrivate::AddIncrement(ui32 attempt, TDuration increment) { + return TDuration::MicroSeconds(attempt * increment.MicroSeconds()); +} + +TDuration NRetryPrivate::AddExponentialMultiplier(ui32 attempt, TDuration exponentialMultiplier) { + return TDuration::MicroSeconds((1ull << Min(63u, attempt)) * exponentialMultiplier.MicroSeconds()); +} diff --git a/library/cpp/retry/utils.h b/library/cpp/retry/utils.h new file mode 100644 index 00000000000..a8fd3d1a898 --- /dev/null +++ b/library/cpp/retry/utils.h @@ -0,0 +1,10 @@ +#pragma once + +#include <util/datetime/base.h> + +namespace NRetryPrivate { + TDuration AddRandomDelta(TDuration delta); + TDuration AddIncrement(ui32 attempt, TDuration increment); + TDuration AddExponentialMultiplier(ui32 attempt, TDuration exponentialMultiplier); + +} diff --git a/library/cpp/retry/ya.make b/library/cpp/retry/ya.make new file mode 100644 index 00000000000..31e0c6a259e --- /dev/null +++ b/library/cpp/retry/ya.make @@ -0,0 +1,17 @@ +LIBRARY() + +OWNER( + osado + g:yabs-small +) + +SRCS( + retry.cpp + utils.cpp +) + +PEERDIR( + library/cpp/retry/protos +) + +END() |