1 // Copyright 2018 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RETRY_POLICY_H
16 #define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RETRY_POLICY_H
17 
18 #include "google/cloud/status.h"
19 #include "google/cloud/version.h"
20 #include <chrono>
21 #include <memory>
22 
23 namespace google {
24 namespace cloud {
25 inline namespace GOOGLE_CLOUD_CPP_NS {
26 namespace internal {
27 
28 enum class Idempotency { kIdempotent, kNonIdempotent };
29 
30 /**
31  * Define the interface for retry policies.
32  */
33 class RetryPolicy {
34  public:
35   virtual ~RetryPolicy() = default;
36 
37   //@{
38   /**
39    * @name Control retry loop duration.
40    *
41    * This functions are typically used in a retry loop, where they control
42    * whether to continue, whether a failure should be retried, and finally
43    * how to format the error message.
44    *
45    * @code
46    * std::unique_ptr<RetryPolicy> policy = ....;
47    * Status status;
48    * while (!policy->IsExhausted()) {
49    *   auto response = try_rpc();  // typically `response` is StatusOr<T>
50    *   if (response.ok()) return response;
51    *   status = std::move(response).status();
52    *   if (!policy->OnFailure(response->status())) {
53    *     if (policy->IsPermanentFailure(response->status()) {
54    *       return StatusModifiedToSayPermanentFailureCausedTheProblem(status);
55    *     }
56    *     return StatusModifiedToSayPolicyExhaustionCausedTheProblem(status);
57    *   }
58    *   // sleep, which may exhaust the policy, even if it was not exhausted in
59    *   // the last call.
60    * }
61    * return StatusModifiedToSayPolicyExhaustionCausedTheProblem(status);
62    * @endcode
63    */
64   virtual bool OnFailure(Status const&) = 0;
65   virtual bool IsExhausted() const = 0;
66   virtual bool IsPermanentFailure(Status const&) const = 0;
67   //@}
68 };
69 
70 /**
71  * Trait based RetryPolicy.
72  *
73  * @tparam StatusType the type used to represent success/failures.
74  * @tparam RetryablePolicy the policy to decide if a status represents a
75  *     permanent failure.
76  */
77 template <typename RetryableTraitsP>
78 class TraitBasedRetryPolicy : public RetryPolicy {
79  public:
80   ///@{
81   /**
82    * @name type traits
83    */
84   /// The traits describing which errors are permanent failures
85   using RetryableTraits = RetryableTraitsP;
86 
87   /// The status type used by the retry policy
88   using StatusType = google::cloud::Status;
89   ///@}
90 
91   ~TraitBasedRetryPolicy() override = default;
92 
93   virtual std::unique_ptr<TraitBasedRetryPolicy> clone() const = 0;
94 
IsPermanentFailure(Status const & status)95   bool IsPermanentFailure(Status const& status) const override {
96     return RetryableTraits::IsPermanentFailure(status);
97   }
98 
OnFailure(Status const & status)99   bool OnFailure(Status const& status) override {
100     if (RetryableTraits::IsPermanentFailure(status)) {
101       return false;
102     }
103     OnFailureImpl();
104     return !IsExhausted();
105   }
106 
107  protected:
108   virtual void OnFailureImpl() = 0;
109 };
110 
111 /**
112  * Implement a simple "count errors and then stop" retry policy.
113  *
114  * @tparam StatusType the type used to represent success/failures.
115  * @tparam RetryablePolicy the policy to decide if a status represents a
116  *     permanent failure.
117  */
118 template <typename RetryablePolicy>
119 class LimitedErrorCountRetryPolicy
120     : public TraitBasedRetryPolicy<RetryablePolicy> {
121  public:
122   using BaseType = TraitBasedRetryPolicy<RetryablePolicy>;
123 
LimitedErrorCountRetryPolicy(int maximum_failures)124   explicit LimitedErrorCountRetryPolicy(int maximum_failures)
125       : failure_count_(0), maximum_failures_(maximum_failures) {}
126 
LimitedErrorCountRetryPolicy(LimitedErrorCountRetryPolicy && rhs)127   LimitedErrorCountRetryPolicy(LimitedErrorCountRetryPolicy&& rhs) noexcept
128       : LimitedErrorCountRetryPolicy(rhs.maximum_failures_) {}
LimitedErrorCountRetryPolicy(LimitedErrorCountRetryPolicy const & rhs)129   LimitedErrorCountRetryPolicy(LimitedErrorCountRetryPolicy const& rhs) noexcept
130       : LimitedErrorCountRetryPolicy(rhs.maximum_failures_) {}
131 
clone()132   std::unique_ptr<BaseType> clone() const override {
133     return std::unique_ptr<BaseType>(
134         new LimitedErrorCountRetryPolicy(maximum_failures_));
135   }
IsExhausted()136   bool IsExhausted() const override {
137     return failure_count_ > maximum_failures_;
138   }
139 
140  protected:
OnFailureImpl()141   void OnFailureImpl() override { ++failure_count_; }
142 
143  private:
144   int failure_count_;
145   int maximum_failures_;
146 };
147 
148 /**
149  * Implement a simple "keep trying for this time" retry policy.
150  *
151  * @tparam StatusType the type used to represent success/failures.
152  * @tparam RetryablePolicy the policy to decide if a status represents a
153  *     permanent failure.
154  */
155 template <typename RetryablePolicy>
156 class LimitedTimeRetryPolicy : public TraitBasedRetryPolicy<RetryablePolicy> {
157  public:
158   using BaseType = TraitBasedRetryPolicy<RetryablePolicy>;
159 
160   /**
161    * Constructor given a `std::chrono::duration<>` object.
162    *
163    * @tparam DurationRep a placeholder to match the `Rep` tparam for @p
164    *     duration's type. The semantics of this template parameter are
165    *     documented in `std::chrono::duration<>` (in brief, the underlying
166    *     arithmetic type used to store the number of ticks), for our purposes it
167    *     is simply a formal parameter.
168    * @tparam DurationPeriod a placeholder to match the `Period` tparam for @p
169    *     duration's type. The semantics of this template parameter are
170    *     documented in `std::chrono::duration<>` (in brief, the length of the
171    *     tick in seconds, expressed as a `std::ratio<>`), for our purposes it is
172    *     simply a formal parameter.
173    * @param maximum_duration the maximum time allowed before the policy expires,
174    *     while the application can express this time in any units they desire,
175    *     the class truncates to milliseconds.
176    */
177   template <typename DurationRep, typename DurationPeriod>
LimitedTimeRetryPolicy(std::chrono::duration<DurationRep,DurationPeriod> maximum_duration)178   explicit LimitedTimeRetryPolicy(
179       std::chrono::duration<DurationRep, DurationPeriod> maximum_duration)
180       : maximum_duration_(std::chrono::duration_cast<std::chrono::milliseconds>(
181             maximum_duration)),
182         deadline_(std::chrono::system_clock::now() + maximum_duration_) {}
183 
LimitedTimeRetryPolicy(LimitedTimeRetryPolicy && rhs)184   LimitedTimeRetryPolicy(LimitedTimeRetryPolicy&& rhs) noexcept
185       : LimitedTimeRetryPolicy(rhs.maximum_duration_) {}
LimitedTimeRetryPolicy(LimitedTimeRetryPolicy const & rhs)186   LimitedTimeRetryPolicy(LimitedTimeRetryPolicy const& rhs)
187       : LimitedTimeRetryPolicy(rhs.maximum_duration_) {}
188 
clone()189   std::unique_ptr<BaseType> clone() const override {
190     return std::unique_ptr<BaseType>(
191         new LimitedTimeRetryPolicy(maximum_duration_));
192   }
IsExhausted()193   bool IsExhausted() const override {
194     return std::chrono::system_clock::now() >= deadline_;
195   }
196 
deadline()197   std::chrono::system_clock::time_point deadline() const { return deadline_; }
198 
199  protected:
OnFailureImpl()200   void OnFailureImpl() override {}
201 
202  private:
203   std::chrono::milliseconds maximum_duration_;
204   std::chrono::system_clock::time_point deadline_;
205 };
206 
207 }  // namespace internal
208 }  // namespace GOOGLE_CLOUD_CPP_NS
209 }  // namespace cloud
210 }  // namespace google
211 
212 #endif  // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_RETRY_POLICY_H
213