Commit 11440055 authored by Daan De Meyer's avatar Daan De Meyer Committed by Facebook GitHub Bot

Add Decider argument to retryN and retryWithExponentialBackoff

Summary:
This allows customizing the exceptions for which retry
is triggered. This is useful when we only want to retry
in case of a few specific exceptions but have others
cause the operation to fail immediately.

Reviewed By: lewissbaker

Differential Revision: D24766615

fbshipit-source-id: f97451673f575bef511399cbde6c1ad110f9493a
parent 9939b376
...@@ -87,13 +87,18 @@ auto retryWhen(Func func, RetryDelayFunc retryDelay) ...@@ -87,13 +87,18 @@ auto retryWhen(Func func, RetryDelayFunc retryDelay)
namespace detail { namespace detail {
template <typename Decider>
class RetryImmediatelyWithLimit { class RetryImmediatelyWithLimit {
public: public:
explicit RetryImmediatelyWithLimit(uint32_t maxRetries) noexcept template <typename Decider2>
: retriesRemaining_(maxRetries) {} explicit RetryImmediatelyWithLimit(
uint32_t maxRetries,
Decider2&& decider) noexcept
: retriesRemaining_(maxRetries),
decider_(static_cast<Decider2&&>(decider)) {}
Task<void> operator()(exception_wrapper&& ew) & { Task<void> operator()(exception_wrapper&& ew) & {
if (retriesRemaining_ == 0) { if (retriesRemaining_ == 0 || !decider_(ew)) {
co_yield folly::coro::co_error(std::move(ew)); co_yield folly::coro::co_error(std::move(ew));
} }
...@@ -107,41 +112,56 @@ class RetryImmediatelyWithLimit { ...@@ -107,41 +112,56 @@ class RetryImmediatelyWithLimit {
private: private:
uint32_t retriesRemaining_; uint32_t retriesRemaining_;
Decider decider_;
};
struct AlwaysRetry {
bool operator()(const folly::exception_wrapper&) noexcept { return true; }
}; };
} // namespace detail } // namespace detail
// Executes the operation returned by func(), retrying it up to // Executes the operation returned by func(), retrying it up to
// 'maxRetries' times on failure with no delay between retries. // 'maxRetries' times on failure with no delay between retries.
template <typename Func, typename Decider>
auto retryN(uint32_t maxRetries, Func&& func, Decider&& decider) {
return folly::coro::retryWhen(
static_cast<Func&&>(func),
detail::RetryImmediatelyWithLimit<remove_cvref_t<Decider>>{
maxRetries, static_cast<Decider&&>(decider)});
}
template <typename Func> template <typename Func>
auto retryN(uint32_t maxRetries, Func&& func) { auto retryN(uint32_t maxRetries, Func&& func) {
return folly::coro::retryWhen( return folly::coro::retryN(
static_cast<Func&&>(func), detail::RetryImmediatelyWithLimit{maxRetries}); maxRetries, static_cast<Func&&>(func), detail::AlwaysRetry{});
} }
namespace detail { namespace detail {
template <typename URNG> template <typename URNG, typename Decider>
class ExponentialBackoffWithJitter { class ExponentialBackoffWithJitter {
public: public:
template <typename URNG2> template <typename URNG2, typename Decider2>
explicit ExponentialBackoffWithJitter( explicit ExponentialBackoffWithJitter(
Timekeeper* tk, Timekeeper* tk,
uint32_t maxRetries, uint32_t maxRetries,
Duration minBackoff, Duration minBackoff,
Duration maxBackoff, Duration maxBackoff,
double relativeJitterStdDev, double relativeJitterStdDev,
URNG2&& rng) noexcept URNG2&& rng,
Decider2&& decider) noexcept
: timeKeeper_(tk), : timeKeeper_(tk),
maxRetries_(maxRetries), maxRetries_(maxRetries),
retryCount_(0), retryCount_(0),
minBackoff_(minBackoff), minBackoff_(minBackoff),
maxBackoff_(maxBackoff), maxBackoff_(maxBackoff),
relativeJitterStdDev_(relativeJitterStdDev), relativeJitterStdDev_(relativeJitterStdDev),
randomGen_(static_cast<URNG2&&>(rng)) {} randomGen_(static_cast<URNG2&&>(rng)),
decider_(static_cast<Decider2&&>(decider)) {}
Task<void> operator()(exception_wrapper&& ew) & { Task<void> operator()(exception_wrapper&& ew) & {
if (retryCount_ == maxRetries_) { if (retryCount_ == maxRetries_ || !decider_(ew)) {
co_yield folly::coro::co_error(std::move(ew)); co_yield folly::coro::co_error(std::move(ew));
} }
...@@ -179,6 +199,7 @@ class ExponentialBackoffWithJitter { ...@@ -179,6 +199,7 @@ class ExponentialBackoffWithJitter {
const Duration maxBackoff_; const Duration maxBackoff_;
const double relativeJitterStdDev_; const double relativeJitterStdDev_;
URNG randomGen_; URNG randomGen_;
Decider decider_;
}; };
} // namespace detail } // namespace detail
...@@ -186,8 +207,9 @@ class ExponentialBackoffWithJitter { ...@@ -186,8 +207,9 @@ class ExponentialBackoffWithJitter {
// Executes the operation returned from 'func()', retrying it on failure // Executes the operation returned from 'func()', retrying it on failure
// up to 'maxRetries' times, with an exponential backoff, doubling the backoff // up to 'maxRetries' times, with an exponential backoff, doubling the backoff
// on average for each retry, applying some random jitter, up to the specified // on average for each retry, applying some random jitter, up to the specified
// maximum backoff. // maximum backoff, passing each error to decider to decide whether to retry or
template <typename Func, typename URNG> // not.
template <typename Func, typename URNG, typename Decider>
auto retryWithExponentialBackoff( auto retryWithExponentialBackoff(
uint32_t maxRetries, uint32_t maxRetries,
Duration minBackoff, Duration minBackoff,
...@@ -195,16 +217,39 @@ auto retryWithExponentialBackoff( ...@@ -195,16 +217,39 @@ auto retryWithExponentialBackoff(
double relativeJitterStdDev, double relativeJitterStdDev,
Timekeeper* timeKeeper, Timekeeper* timeKeeper,
URNG&& rng, URNG&& rng,
Func&& func) { Func&& func,
Decider&& decider) {
return folly::coro::retryWhen( return folly::coro::retryWhen(
static_cast<Func&&>(func), static_cast<Func&&>(func),
detail::ExponentialBackoffWithJitter<remove_cvref_t<URNG>>{ detail::ExponentialBackoffWithJitter<
timeKeeper, remove_cvref_t<URNG>,
remove_cvref_t<Decider>>{timeKeeper,
maxRetries,
minBackoff,
maxBackoff,
relativeJitterStdDev,
static_cast<URNG&&>(rng),
static_cast<Decider&&>(decider)});
}
template <typename Func, typename URNG>
auto retryWithExponentialBackoff(
uint32_t maxRetries,
Duration minBackoff,
Duration maxBackoff,
double relativeJitterStdDev,
Timekeeper* timeKeeper,
URNG&& rng,
Func&& func) {
return folly::coro::retryWithExponentialBackoff(
maxRetries, maxRetries,
minBackoff, minBackoff,
maxBackoff, maxBackoff,
relativeJitterStdDev, relativeJitterStdDev,
static_cast<URNG&&>(rng)}); timeKeeper,
static_cast<URNG&&>(rng),
static_cast<Func&&>(func),
detail::AlwaysRetry{});
} }
template <typename Func> template <typename Func>
...@@ -241,4 +286,23 @@ auto retryWithExponentialBackoff( ...@@ -241,4 +286,23 @@ auto retryWithExponentialBackoff(
static_cast<Func&&>(func)); static_cast<Func&&>(func));
} }
template <typename Func, typename Decider>
auto retryWithExponentialBackoff(
uint32_t maxRetries,
Duration minBackoff,
Duration maxBackoff,
double relativeJitterStdDev,
Func&& func,
Decider&& decider) {
return folly::coro::retryWithExponentialBackoff(
maxRetries,
minBackoff,
maxBackoff,
relativeJitterStdDev,
static_cast<Timekeeper*>(nullptr),
ThreadLocalPRNG(),
static_cast<Func&&>(func),
static_cast<Decider&&>(decider));
}
} // namespace folly::coro } // namespace folly::coro
...@@ -78,6 +78,23 @@ TEST(RetryN, EventualSuccess) { ...@@ -78,6 +78,23 @@ TEST(RetryN, EventualSuccess) {
}()); }());
} }
TEST(RetryN, NeverRetry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
int runCount = 0;
folly::Try<void> result =
co_await folly::coro::co_awaitTry(folly::coro::retryN(
3,
[&]() -> folly::coro::Task<void> {
++runCount;
if (runCount <= 2) {
co_yield folly::coro::co_error(SomeError{runCount});
}
},
[](const folly::exception_wrapper&) { return false; }));
EXPECT_EQ(1, runCount);
}());
}
TEST(RetryWithJitter, Success) { TEST(RetryWithJitter, Success) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> { folly::coro::blockingWait([]() -> folly::coro::Task<void> {
int runCount = 0; int runCount = 0;
...@@ -146,4 +163,62 @@ TEST(RetryWithJitter, EventualSuccess) { ...@@ -146,4 +163,62 @@ TEST(RetryWithJitter, EventualSuccess) {
}()); }());
} }
TEST(RetryWithDecider, AlwaysRetry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
int runCount = 0;
folly::Try<void> result = co_await folly::coro::co_awaitTry(
folly::coro::retryWithExponentialBackoff(
5, 0ms, 1ms, 0.0, [&]() -> folly::coro::Task<void> {
++runCount;
co_yield folly::coro::co_error(SomeError(1));
}));
EXPECT_TRUE(result.hasException());
EXPECT_EQ(6, runCount);
}());
}
TEST(RetryWithDecider, NeverRetry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
int runCount = 0;
folly::Try<void> result = co_await folly::coro::co_awaitTry(
folly::coro::retryWithExponentialBackoff(
5,
0ms,
1ms,
0.0,
[&]() -> folly::coro::Task<void> {
++runCount;
co_yield folly::coro::co_error(SomeError(1));
},
[](const folly::exception_wrapper&) { return false; }));
EXPECT_TRUE(result.hasException());
EXPECT_EQ(1, runCount);
}());
}
TEST(RetryWithDecider, SometimesRetry) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
int runCount = 0;
folly::Try<void> result = co_await folly::coro::co_awaitTry(
folly::coro::retryWithExponentialBackoff(
5,
0ms,
1ms,
0.0,
[&]() -> folly::coro::Task<void> {
++runCount;
co_yield folly::coro::co_error(SomeError(runCount));
},
[](const folly::exception_wrapper& ew) {
try {
ew.throw_exception();
} catch (const SomeError& e) {
return e.value < 3;
}
}));
EXPECT_TRUE(result.hasException());
EXPECT_EQ(3, runCount);
}());
}
#endif // FOLLY_HAS_COROUTINES #endif // FOLLY_HAS_COROUTINES
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment