iceberg-cpp
Loading...
Searching...
No Matches
retry_util.h
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#pragma once
21
22#include <chrono>
23#include <cstdint>
24#include <functional>
25#include <optional>
26#include <type_traits>
27#include <utility>
28
29#include "iceberg/iceberg_export.h"
30#include "iceberg/result.h"
31#include "iceberg/util/macros.h"
32
33namespace iceberg {
34
35namespace detail {
36
37template <typename F>
39
40} // namespace detail
41
43struct ICEBERG_EXPORT RetryConfig {
45 int32_t num_retries = 4;
47 int32_t min_wait_ms = 100;
49 int32_t max_wait_ms = 60 * 1000; // 1 minute
51 int32_t total_timeout_ms = 30 * 60 * 1000; // 30 minutes
53 double scale_factor = 2.0;
54};
55
56namespace detail {
57
58class ICEBERG_EXPORT RetryRunnerBase {
59 protected:
60 explicit RetryRunnerBase(RetryConfig config) : config_(std::move(config)) {}
61
62 using Clock = std::chrono::steady_clock;
63 using Duration = std::chrono::milliseconds;
64 using TimePoint = Clock::time_point;
65
67 Status ValidateConfig() const;
68 std::optional<TimePoint> ComputeDeadline() const;
69 bool HasTimedOut(const std::optional<TimePoint>& deadline) const;
70 std::optional<Duration> RetryDelayWithinBudget(
71 int32_t attempt, const std::optional<TimePoint>& deadline) const;
72 bool WaitForNextAttempt(int32_t attempt,
73 const std::optional<TimePoint>& deadline) const;
75 int32_t CalculateDelay(int32_t attempt) const;
76
77 RetryConfig config_;
78};
79
80} // namespace detail
81
82namespace retry {
83
84enum class RetryPolicyMode {
85 kNoRetry,
86 kOnlyRetryOn,
87 kStopRetryOn,
88};
89
90template <RetryPolicyMode Mode, ErrorKind... Kinds>
92 static_assert(Mode != RetryPolicyMode::kNoRetry || sizeof...(Kinds) == 0,
93 "NoRetry must not include error kinds");
94 static_assert(Mode == RetryPolicyMode::kNoRetry || sizeof...(Kinds) > 0,
95 "RetryPolicy must include at least one error kind");
96
97 static constexpr RetryPolicyMode kMode = Mode;
98 static constexpr bool kEnabled = Mode != RetryPolicyMode::kNoRetry;
99
100 static constexpr bool ShouldRetry(ErrorKind kind) {
101 if constexpr (Mode == RetryPolicyMode::kNoRetry) {
102 return false;
103 } else if constexpr (Mode == RetryPolicyMode::kOnlyRetryOn) {
104 return ((kind == Kinds) || ...);
105 } else {
106 return !((kind == Kinds) || ...);
107 }
108 }
109};
110
111using NoRetry = RetryPolicy<RetryPolicyMode::kNoRetry>;
112
113template <ErrorKind... Kinds>
114using OnlyRetryOn = RetryPolicy<RetryPolicyMode::kOnlyRetryOn, Kinds...>;
115
116template <ErrorKind... Kinds>
117using StopRetryOn = RetryPolicy<RetryPolicyMode::kStopRetryOn, Kinds...>;
118
119template <typename T>
120inline constexpr bool kIsRetryPolicy = false;
121
122template <RetryPolicyMode Mode, ErrorKind... Kinds>
123inline constexpr bool kIsRetryPolicy<RetryPolicy<Mode, Kinds...>> = true;
124
125template <typename T>
126concept Policy = kIsRetryPolicy<std::remove_cvref_t<T>>;
127
128} // namespace retry
129
134template <retry::Policy RetryPolicy>
136 public:
138 explicit RetryRunner(RetryConfig config = {})
139 : detail::RetryRunnerBase(std::move(config)) {}
140
146 template <detail::RetryTask F>
147 auto Run(F&& task, int32_t* attempt_counter = nullptr)
148 -> std::remove_cvref_t<std::invoke_result_t<F&>> {
149 ICEBERG_RETURN_UNEXPECTED(ValidatePolicyConfig());
150
151 const auto deadline = this->ComputeDeadline();
152 int32_t attempt = 0;
153 const int32_t max_attempts = this->config_.num_retries + 1;
154
155 while (true) {
156 ++attempt;
157 if (attempt_counter != nullptr) {
158 *attempt_counter = attempt;
159 }
160
161 auto result = std::invoke(task);
162 if (result.has_value()) {
163 return result;
164 }
165
166 if (!CanRetry(result.error().kind, attempt, max_attempts, deadline)) {
167 return result;
168 }
169
170 if (!this->WaitForNextAttempt(attempt, deadline)) {
171 return result;
172 }
173 }
174 }
175
176 private:
177 using TimePoint = detail::RetryRunnerBase::TimePoint;
178
179 Status ValidatePolicyConfig() const {
180 auto validation = this->ValidateConfig();
181 if (!validation.has_value()) {
182 return validation;
183 }
184 if (this->config_.num_retries > 0 && !RetryPolicy::kEnabled) {
185 return InvalidArgument("Retry policy must be enabled when num_retries > 0");
186 }
187 return {};
188 }
189
190 bool CanRetry(ErrorKind kind, int32_t attempt, int32_t max_attempts,
191 const std::optional<TimePoint>& deadline) const {
192 return attempt < max_attempts && !this->HasTimedOut(deadline) &&
193 RetryPolicy::ShouldRetry(kind);
194 }
195};
196
198ICEBERG_EXPORT inline auto MakeCommitRetryRunner(int32_t num_retries, int32_t min_wait_ms,
199 int32_t max_wait_ms,
200 int32_t total_timeout_ms) {
201 return RetryRunner<retry::OnlyRetryOn<ErrorKind::kCommitFailed,
202 ErrorKind::kRetryableValidationFailed>>(
203 RetryConfig{.num_retries = num_retries,
204 .min_wait_ms = min_wait_ms,
205 .max_wait_ms = max_wait_ms,
206 .total_timeout_ms = total_timeout_ms});
207}
208
209} // namespace iceberg
Utility class for running tasks with retry logic.
Definition retry_util.h:135
auto Run(F &&task, int32_t *attempt_counter=nullptr) -> std::remove_cvref_t< std::invoke_result_t< F & > >
Run a task that returns a Result<T>
Definition retry_util.h:147
RetryRunner(RetryConfig config={})
Construct a RetryRunner with the given configuration.
Definition retry_util.h:138
Definition retry_util.h:58
Status ValidateConfig() const
Validate retry counts and timing bounds.
Definition retry_util.cc:83
Definition result.h:140
Definition retry_util.h:38
Definition retry_util.h:126
STL namespace.
Configuration for retry behavior.
Definition retry_util.h:43
Definition retry_util.h:91