aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/retry
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/retry
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/retry')
-rw-r--r--library/cpp/retry/protos/retry_options.proto9
-rw-r--r--library/cpp/retry/protos/ya.make16
-rw-r--r--library/cpp/retry/retry.cpp26
-rw-r--r--library/cpp/retry/retry.h133
-rw-r--r--library/cpp/retry/retry_ut.cpp117
-rw-r--r--library/cpp/retry/ut/ya.make13
-rw-r--r--library/cpp/retry/utils.cpp20
-rw-r--r--library/cpp/retry/utils.h10
-rw-r--r--library/cpp/retry/ya.make17
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()