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