Skip to content
This repository was archived by the owner on Jan 7, 2022. It is now read-only.

Commit 19daa59

Browse files
MohamedBassemfacebook-github-bot
authored andcommitted
Implment a generic RetryHandler in common
Summary: A new API to provide generic retrying mechanism for custom functions. It's a thin wrapper around folly::future::retrying to support non future based functions. Reviewed By: mcrnic Differential Revision: D17738231 fbshipit-source-id: 2801f3330ebb3e18e2e9c888ba405f5a3de32ddf
1 parent 315b32f commit 19daa59

File tree

3 files changed

+187
-0
lines changed

3 files changed

+187
-0
lines changed

logdevice/common/RetryHandler.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc. and its affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
#include "logdevice/common/RetryHandler.h"

logdevice/common/RetryHandler.h

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc. and its affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
#pragma once
9+
10+
#include <chrono>
11+
12+
#include <folly/Expected.h>
13+
#include <folly/executors/InlineExecutor.h>
14+
#include <folly/futures/Retrying.h>
15+
16+
namespace facebook { namespace logdevice {
17+
18+
/**
19+
* @file RetryHandler provides an API to retry a certain function if it fails
20+
* with exponential backoff and optional jitter.
21+
*
22+
* It is a thin-wrapper around folly::futures::retrying to support retrying for
23+
* code that's not future based.
24+
*
25+
*/
26+
template <class T>
27+
class RetryHandler {
28+
public:
29+
using Result = folly::Expected<T, T>;
30+
31+
/**
32+
* Retries the function until either we exhaust all the retries or
33+
* should_retry returns false.
34+
*
35+
* @param func: The function we want to retry, takes as a param the trial
36+
* number.
37+
* @param should_retry: Given the output of func, returns true to retry, or
38+
* false to stop retrying.
39+
*
40+
* @returns A folly::Expected indicating either:
41+
* 1- Success and contains the returned value.
42+
* 2- Error which means that we exhausted all the retries. It will also
43+
* hold the last returned value from the function.
44+
*/
45+
static folly::SemiFuture<Result>
46+
run(folly::Function<T(size_t trial_num) const> func,
47+
folly::Function<bool(const T&) const> should_retry,
48+
size_t max_tries,
49+
std::chrono::milliseconds backoff_min,
50+
std::chrono::milliseconds backoff_max,
51+
double jitter_param) {
52+
return folly::futures::retrying(
53+
folly::futures::retryingPolicyCappedJitteredExponentialBackoff(
54+
max_tries, backoff_min, backoff_max, jitter_param),
55+
[fu = std::move(func), should_retry = std::move(should_retry)](
56+
size_t trial) -> folly::SemiFuture<Result> {
57+
T ret = fu(trial);
58+
59+
// If it's a failure, simulate an execption for
60+
// futures::retrying to retry.
61+
if (should_retry(ret)) {
62+
return folly::make_exception_wrapper<Failure>(
63+
std::move(ret));
64+
} else {
65+
return folly::makeExpected<T>(std::move(ret));
66+
}
67+
})
68+
// When we exhaust all the retries, return it as a folly unexpected
69+
// of a future carrying an exception.
70+
.deferError(folly::tag_t<Failure>(),
71+
[](Failure f) -> folly::SemiFuture<Result> {
72+
return folly::makeUnexpected(std::move(f.type));
73+
});
74+
}
75+
76+
/**
77+
* Synchronous version of RetryHandler<T>::run.
78+
*/
79+
static Result syncRun(folly::Function<T(size_t trial_num) const> func,
80+
folly::Function<bool(const T&) const> should_retry,
81+
size_t max_tries,
82+
std::chrono::milliseconds backoff_min,
83+
std::chrono::milliseconds backoff_max,
84+
double jitter_param) {
85+
auto& executor = folly::InlineExecutor::instance();
86+
return run(std::move(func),
87+
std::move(should_retry),
88+
max_tries,
89+
backoff_min,
90+
backoff_max,
91+
jitter_param)
92+
.via(&executor)
93+
.get();
94+
}
95+
96+
private:
97+
struct Failure : std::exception {
98+
explicit Failure(T t) : type(std::move(t)) {}
99+
T type;
100+
};
101+
};
102+
103+
}} // namespace facebook::logdevice
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (c) 2017-present, Facebook, Inc. and its affiliates.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
#include "logdevice/common/RetryHandler.h"
9+
10+
#include <gtest/gtest.h>
11+
12+
#include "logdevice/include/Err.h"
13+
14+
namespace facebook { namespace logdevice {
15+
16+
TEST(RetryHandlerTest, testFirstTimeSuccess) {
17+
int num_calls = 0;
18+
19+
EXPECT_EQ(Status::OK,
20+
RetryHandler<Status>::syncRun(
21+
[&](size_t) {
22+
num_calls++;
23+
return Status::OK;
24+
},
25+
[](const Status& st) { return st != Status::OK; },
26+
/* max_retries= */ 10,
27+
/* backoff_min= */ std::chrono::milliseconds(1),
28+
/* backoff_max= */ std::chrono::milliseconds(10),
29+
/* jitter_param= */ 0)
30+
.value());
31+
EXPECT_EQ(1, num_calls);
32+
}
33+
34+
TEST(RetryHandlerTest, testEventualSuccess) {
35+
int num_calls = 0;
36+
37+
EXPECT_EQ(Status::OK,
38+
RetryHandler<Status>::syncRun(
39+
[&](size_t trial) {
40+
num_calls++;
41+
// Succeed in the 4th trial (zero indexed)
42+
if (trial == 3) {
43+
return Status::OK;
44+
}
45+
return Status::UNKNOWN;
46+
},
47+
[](const Status& st) { return st != Status::OK; },
48+
/* max_retries= */ 10,
49+
/* backoff_min= */ std::chrono::milliseconds(1),
50+
/* backoff_max= */ std::chrono::milliseconds(10),
51+
/* jitter_param= */ 0)
52+
.value());
53+
EXPECT_EQ(4, num_calls);
54+
}
55+
56+
TEST(RetryHandlerTest, testFailure) {
57+
int num_calls = 0;
58+
59+
EXPECT_EQ(Status::UNKNOWN,
60+
RetryHandler<Status>::syncRun(
61+
[&](size_t) {
62+
num_calls++;
63+
// Never succeed
64+
return Status::UNKNOWN;
65+
},
66+
[](const Status& st) { return st != Status::OK; },
67+
/* max_retries= */ 10,
68+
/* backoff_min= */ std::chrono::milliseconds(1),
69+
/* backoff_max= */ std::chrono::milliseconds(10),
70+
/* jitter_param= */ 0)
71+
.error());
72+
EXPECT_EQ(10, num_calls);
73+
}
74+
75+
}} // namespace facebook::logdevice

0 commit comments

Comments
 (0)