Line data Source code
1 : #include "source/common/router/retry_state_impl.h"
2 :
3 : #include <chrono>
4 : #include <cstdint>
5 : #include <string>
6 : #include <vector>
7 :
8 : #include "envoy/config/route/v3/route_components.pb.h"
9 :
10 : #include "source/common/common/assert.h"
11 : #include "source/common/common/utility.h"
12 : #include "source/common/grpc/common.h"
13 : #include "source/common/http/codes.h"
14 : #include "source/common/http/headers.h"
15 : #include "source/common/http/utility.h"
16 : #include "source/common/runtime/runtime_features.h"
17 :
18 : namespace Envoy {
19 : namespace Router {
20 :
21 0 : bool clusterSupportsHttp3AndTcpFallback(const Upstream::ClusterInfo& cluster) {
22 0 : return (cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
23 : // USE_ALPN is only set when a TCP pool is also configured. Such cluster supports TCP
24 : // fallback.
25 0 : (cluster.features() & Upstream::ClusterInfo::Features::USE_ALPN);
26 0 : }
27 :
28 : std::unique_ptr<RetryStateImpl>
29 : RetryStateImpl::create(const RetryPolicy& route_policy, Http::RequestHeaderMap& request_headers,
30 : const Upstream::ClusterInfo& cluster, const VirtualCluster* vcluster,
31 : RouteStatsContextOptRef route_stats_context, Runtime::Loader& runtime,
32 : Random::RandomGenerator& random, Event::Dispatcher& dispatcher,
33 251 : TimeSource& time_source, Upstream::ResourcePriority priority) {
34 251 : std::unique_ptr<RetryStateImpl> ret;
35 :
36 : // We short circuit here and do not bother with an allocation if there is no chance we will retry.
37 : // But for HTTP/3 0-RTT safe requests, which can be rejected because they are sent too early(425
38 : // response code), we want to give them a chance to retry as normal requests even though the retry
39 : // policy doesn't specify it. So always allocate retry state object.
40 251 : if (request_headers.EnvoyRetryOn() || request_headers.EnvoyRetryGrpcOn() ||
41 251 : route_policy.retryOn()) {
42 0 : ret.reset(new RetryStateImpl(route_policy, request_headers, cluster, vcluster,
43 0 : route_stats_context, runtime, random, dispatcher, time_source,
44 0 : priority, false));
45 251 : } else if ((cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
46 251 : Http::Utility::isSafeRequest(request_headers)) {
47 0 : ret.reset(new RetryStateImpl(route_policy, request_headers, cluster, vcluster,
48 0 : route_stats_context, runtime, random, dispatcher, time_source,
49 0 : priority, true));
50 0 : }
51 :
52 : // Consume all retry related headers to avoid them being propagated to the upstream
53 251 : request_headers.removeEnvoyRetryOn();
54 251 : request_headers.removeEnvoyRetryGrpcOn();
55 251 : request_headers.removeEnvoyMaxRetries();
56 251 : request_headers.removeEnvoyHedgeOnPerTryTimeout();
57 251 : request_headers.removeEnvoyRetriableHeaderNames();
58 251 : request_headers.removeEnvoyRetriableStatusCodes();
59 251 : request_headers.removeEnvoyUpstreamRequestPerTryTimeoutMs();
60 :
61 251 : return ret;
62 251 : }
63 :
64 : RetryStateImpl::RetryStateImpl(const RetryPolicy& route_policy,
65 : Http::RequestHeaderMap& request_headers,
66 : const Upstream::ClusterInfo& cluster, const VirtualCluster* vcluster,
67 : RouteStatsContextOptRef route_stats_context,
68 : Runtime::Loader& runtime, Random::RandomGenerator& random,
69 : Event::Dispatcher& dispatcher, TimeSource& time_source,
70 : Upstream::ResourcePriority priority, bool auto_configured_for_http3)
71 : : cluster_(cluster), vcluster_(vcluster), route_stats_context_(route_stats_context),
72 : runtime_(runtime), random_(random), dispatcher_(dispatcher), time_source_(time_source),
73 : retry_host_predicates_(route_policy.retryHostPredicates()),
74 : retry_priority_(route_policy.retryPriority()),
75 : retriable_status_codes_(route_policy.retriableStatusCodes()),
76 : retriable_headers_(route_policy.retriableHeaders()),
77 : reset_headers_(route_policy.resetHeaders()),
78 : reset_max_interval_(route_policy.resetMaxInterval()), retry_on_(route_policy.retryOn()),
79 : retries_remaining_(route_policy.numRetries()), priority_(priority),
80 0 : auto_configured_for_http3_(auto_configured_for_http3) {
81 0 : if ((cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
82 0 : Http::Utility::isSafeRequest(request_headers)) {
83 : // Because 0-RTT requests could be rejected because they are sent too early, and such requests
84 : // should always be retried, setup retry policy for 425 response code for all potential 0-RTT
85 : // requests even though the retry policy isn't configured to do so. Since 0-RTT safe requests
86 : // traditionally shouldn't have body, automatically retrying them will not cause extra
87 : // buffering. This will also enable retry if they are reset during connect.
88 0 : retry_on_ |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
89 0 : retriable_status_codes_.push_back(static_cast<uint32_t>(Http::Code::TooEarly));
90 0 : }
91 0 : std::chrono::milliseconds base_interval(
92 0 : runtime_.snapshot().getInteger("upstream.base_retry_backoff_ms", 25));
93 0 : if (route_policy.baseInterval()) {
94 0 : base_interval = *route_policy.baseInterval();
95 0 : }
96 :
97 : // By default, cap the max interval to 10 times the base interval to ensure reasonable back-off
98 : // intervals.
99 0 : std::chrono::milliseconds max_interval = base_interval * 10;
100 0 : if (route_policy.maxInterval()) {
101 0 : max_interval = *route_policy.maxInterval();
102 0 : }
103 :
104 0 : backoff_strategy_ = std::make_unique<JitteredExponentialBackOffStrategy>(
105 0 : base_interval.count(), max_interval.count(), random_);
106 0 : host_selection_max_attempts_ = route_policy.hostSelectionMaxAttempts();
107 :
108 : // Merge in the headers.
109 0 : if (request_headers.EnvoyRetryOn()) {
110 0 : retry_on_ |= parseRetryOn(request_headers.getEnvoyRetryOnValue()).first;
111 0 : }
112 0 : if (request_headers.EnvoyRetryGrpcOn()) {
113 0 : retry_on_ |= parseRetryGrpcOn(request_headers.getEnvoyRetryGrpcOnValue()).first;
114 0 : }
115 :
116 0 : const auto& retriable_request_headers = route_policy.retriableRequestHeaders();
117 0 : if (!retriable_request_headers.empty()) {
118 : // If this route limits retries by request headers, make sure there is a match.
119 0 : bool request_header_match = false;
120 0 : for (const auto& retriable_header : retriable_request_headers) {
121 0 : if (retriable_header->matchesHeaders(request_headers)) {
122 0 : request_header_match = true;
123 0 : break;
124 0 : }
125 0 : }
126 :
127 0 : if (!request_header_match) {
128 0 : retry_on_ = 0;
129 0 : }
130 0 : }
131 0 : if (retry_on_ != 0 && request_headers.EnvoyMaxRetries()) {
132 0 : uint64_t temp;
133 0 : if (absl::SimpleAtoi(request_headers.getEnvoyMaxRetriesValue(), &temp)) {
134 : // The max retries header takes precedence if set.
135 0 : retries_remaining_ = temp;
136 0 : }
137 0 : }
138 :
139 0 : if (request_headers.EnvoyRetriableStatusCodes()) {
140 0 : for (const auto& code :
141 0 : StringUtil::splitToken(request_headers.getEnvoyRetriableStatusCodesValue(), ",")) {
142 0 : unsigned int out;
143 0 : if (absl::SimpleAtoi(code, &out)) {
144 0 : retriable_status_codes_.emplace_back(out);
145 0 : }
146 0 : }
147 0 : }
148 :
149 0 : if (request_headers.EnvoyRetriableHeaderNames()) {
150 : // Retriable headers in the configuration are specified via HeaderMatcher.
151 : // Giving the same flexibility via request header would require the user
152 : // to provide HeaderMatcher serialized into a string. To avoid this extra
153 : // complexity we only support name-only header matchers via request
154 : // header. Anything more sophisticated needs to be provided via config.
155 0 : for (const auto& header_name : StringUtil::splitToken(
156 0 : request_headers.EnvoyRetriableHeaderNames()->value().getStringView(), ",")) {
157 0 : envoy::config::route::v3::HeaderMatcher header_matcher;
158 0 : header_matcher.set_name(std::string(absl::StripAsciiWhitespace(header_name)));
159 0 : retriable_headers_.emplace_back(
160 0 : std::make_shared<Http::HeaderUtility::HeaderData>(header_matcher));
161 0 : }
162 0 : }
163 0 : }
164 :
165 0 : RetryStateImpl::~RetryStateImpl() { resetRetry(); }
166 :
167 0 : void RetryStateImpl::enableBackoffTimer() {
168 0 : if (!retry_timer_) {
169 0 : retry_timer_ = dispatcher_.createTimer([this]() -> void { backoff_callback_(); });
170 0 : }
171 :
172 0 : if (ratelimited_backoff_strategy_ != nullptr) {
173 : // If we have a backoff strategy based on rate limit feedback from the response we use it.
174 0 : retry_timer_->enableTimer(
175 0 : std::chrono::milliseconds(ratelimited_backoff_strategy_->nextBackOffMs()));
176 :
177 : // The strategy is only valid for the response that sent the ratelimit reset header and cannot
178 : // be reused.
179 0 : ratelimited_backoff_strategy_.reset();
180 :
181 0 : cluster_.trafficStats()->upstream_rq_retry_backoff_ratelimited_.inc();
182 :
183 0 : } else {
184 : // Otherwise we use a fully jittered exponential backoff algorithm.
185 0 : retry_timer_->enableTimer(std::chrono::milliseconds(backoff_strategy_->nextBackOffMs()));
186 :
187 0 : cluster_.trafficStats()->upstream_rq_retry_backoff_exponential_.inc();
188 0 : }
189 0 : }
190 :
191 88 : std::pair<uint32_t, bool> RetryStateImpl::parseRetryOn(absl::string_view config) {
192 88 : uint32_t ret = 0;
193 88 : bool all_fields_valid = true;
194 88 : for (const auto& retry_on : StringUtil::splitToken(config, ",", false, true)) {
195 66 : if (retry_on == Http::Headers::get().EnvoyRetryOnValues._5xx) {
196 0 : ret |= RetryPolicy::RETRY_ON_5XX;
197 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.GatewayError) {
198 0 : ret |= RetryPolicy::RETRY_ON_GATEWAY_ERROR;
199 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.ConnectFailure) {
200 66 : ret |= RetryPolicy::RETRY_ON_CONNECT_FAILURE;
201 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.EnvoyRateLimited) {
202 0 : ret |= RetryPolicy::RETRY_ON_ENVOY_RATE_LIMITED;
203 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Retriable4xx) {
204 0 : ret |= RetryPolicy::RETRY_ON_RETRIABLE_4XX;
205 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RefusedStream) {
206 0 : ret |= RetryPolicy::RETRY_ON_REFUSED_STREAM;
207 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableStatusCodes) {
208 0 : ret |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
209 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableHeaders) {
210 0 : ret |= RetryPolicy::RETRY_ON_RETRIABLE_HEADERS;
211 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Reset) {
212 0 : ret |= RetryPolicy::RETRY_ON_RESET;
213 0 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Http3PostConnectFailure) {
214 0 : ret |= RetryPolicy::RETRY_ON_HTTP3_POST_CONNECT_FAILURE;
215 0 : } else {
216 0 : all_fields_valid = false;
217 0 : }
218 66 : }
219 :
220 88 : return {ret, all_fields_valid};
221 88 : }
222 :
223 88 : std::pair<uint32_t, bool> RetryStateImpl::parseRetryGrpcOn(absl::string_view retry_grpc_on_header) {
224 88 : uint32_t ret = 0;
225 88 : bool all_fields_valid = true;
226 88 : for (const auto& retry_on : StringUtil::splitToken(retry_grpc_on_header, ",", false, true)) {
227 66 : if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Cancelled) {
228 0 : ret |= RetryPolicy::RETRY_ON_GRPC_CANCELLED;
229 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.DeadlineExceeded) {
230 0 : ret |= RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED;
231 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.ResourceExhausted) {
232 0 : ret |= RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED;
233 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Unavailable) {
234 0 : ret |= RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE;
235 66 : } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Internal) {
236 0 : ret |= RetryPolicy::RETRY_ON_GRPC_INTERNAL;
237 66 : } else {
238 66 : all_fields_valid = false;
239 66 : }
240 66 : }
241 :
242 88 : return {ret, all_fields_valid};
243 88 : }
244 :
245 : absl::optional<std::chrono::milliseconds>
246 0 : RetryStateImpl::parseResetInterval(const Http::ResponseHeaderMap& response_headers) const {
247 0 : for (const auto& reset_header : reset_headers_) {
248 0 : const auto& interval = reset_header->parseInterval(time_source_, response_headers);
249 0 : if (interval.has_value() && interval.value() <= reset_max_interval_) {
250 0 : return interval;
251 0 : }
252 0 : }
253 :
254 0 : return absl::nullopt;
255 0 : }
256 :
257 0 : void RetryStateImpl::resetRetry() {
258 0 : if (backoff_callback_ != nullptr) {
259 0 : cluster_.resourceManager(priority_).retries().dec();
260 0 : backoff_callback_ = nullptr;
261 0 : }
262 0 : if (next_loop_callback_ != nullptr) {
263 0 : cluster_.resourceManager(priority_).retries().dec();
264 0 : next_loop_callback_ = nullptr;
265 0 : }
266 0 : }
267 :
268 0 : RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallback callback) {
269 : // If a callback is armed from a previous shouldRetry and we don't need to
270 : // retry this particular request, we can infer that we did a retry earlier
271 : // and it was successful.
272 0 : if ((backoff_callback_ || next_loop_callback_) && would_retry == RetryDecision::NoRetry) {
273 0 : cluster_.trafficStats()->upstream_rq_retry_success_.inc();
274 0 : if (vcluster_) {
275 0 : vcluster_->stats().upstream_rq_retry_success_.inc();
276 0 : }
277 0 : if (route_stats_context_.has_value()) {
278 0 : route_stats_context_->stats().upstream_rq_retry_success_.inc();
279 0 : }
280 0 : }
281 :
282 0 : resetRetry();
283 :
284 0 : if (would_retry == RetryDecision::NoRetry) {
285 0 : return RetryStatus::No;
286 0 : }
287 :
288 : // The request has exhausted the number of retries allotted to it by the retry policy configured
289 : // (or the x-envoy-max-retries header).
290 0 : if (retries_remaining_ == 0) {
291 0 : cluster_.trafficStats()->upstream_rq_retry_limit_exceeded_.inc();
292 0 : if (vcluster_) {
293 0 : vcluster_->stats().upstream_rq_retry_limit_exceeded_.inc();
294 0 : }
295 0 : if (route_stats_context_.has_value()) {
296 0 : route_stats_context_->stats().upstream_rq_retry_limit_exceeded_.inc();
297 0 : }
298 0 : return RetryStatus::NoRetryLimitExceeded;
299 0 : }
300 :
301 0 : retries_remaining_--;
302 :
303 0 : if (!cluster_.resourceManager(priority_).retries().canCreate()) {
304 0 : cluster_.trafficStats()->upstream_rq_retry_overflow_.inc();
305 0 : if (vcluster_) {
306 0 : vcluster_->stats().upstream_rq_retry_overflow_.inc();
307 0 : }
308 0 : if (route_stats_context_.has_value()) {
309 0 : route_stats_context_->stats().upstream_rq_retry_overflow_.inc();
310 0 : }
311 0 : return RetryStatus::NoOverflow;
312 0 : }
313 :
314 0 : if (!runtime_.snapshot().featureEnabled("upstream.use_retry", 100)) {
315 0 : return RetryStatus::No;
316 0 : }
317 :
318 0 : ASSERT(!backoff_callback_ && !next_loop_callback_);
319 0 : cluster_.resourceManager(priority_).retries().inc();
320 0 : cluster_.trafficStats()->upstream_rq_retry_.inc();
321 0 : if (vcluster_) {
322 0 : vcluster_->stats().upstream_rq_retry_.inc();
323 0 : }
324 0 : if (route_stats_context_.has_value()) {
325 0 : route_stats_context_->stats().upstream_rq_retry_.inc();
326 0 : }
327 0 : if (would_retry == RetryDecision::RetryWithBackoff) {
328 0 : backoff_callback_ = callback;
329 0 : enableBackoffTimer();
330 0 : } else {
331 0 : next_loop_callback_ = dispatcher_.createSchedulableCallback(callback);
332 0 : next_loop_callback_->scheduleCallbackNextIteration();
333 0 : }
334 0 : return RetryStatus::Yes;
335 0 : }
336 :
337 : RetryStatus RetryStateImpl::shouldRetryHeaders(const Http::ResponseHeaderMap& response_headers,
338 : const Http::RequestHeaderMap& original_request,
339 0 : DoRetryHeaderCallback callback) {
340 : // This may be overridden in wouldRetryFromHeaders().
341 0 : bool disable_early_data = false;
342 0 : const RetryDecision retry_decision =
343 0 : wouldRetryFromHeaders(response_headers, original_request, disable_early_data);
344 :
345 : // Yes, we will retry based on the headers - try to parse a rate limited reset interval from the
346 : // response.
347 0 : if (retry_decision == RetryDecision::RetryWithBackoff && !reset_headers_.empty()) {
348 0 : const auto backoff_interval = parseResetInterval(response_headers);
349 0 : if (backoff_interval.has_value() && (backoff_interval.value().count() > 1L)) {
350 0 : ratelimited_backoff_strategy_ = std::make_unique<JitteredLowerBoundBackOffStrategy>(
351 0 : backoff_interval.value().count(), random_);
352 0 : }
353 0 : }
354 :
355 0 : return shouldRetry(retry_decision,
356 0 : [disable_early_data, callback]() { callback(disable_early_data); });
357 0 : }
358 :
359 : RetryStatus RetryStateImpl::shouldRetryReset(Http::StreamResetReason reset_reason,
360 0 : Http3Used http3_used, DoRetryResetCallback callback) {
361 :
362 : // Following wouldRetryFromReset() may override the value.
363 0 : bool disable_http3 = false;
364 0 : const RetryDecision retry_decision = wouldRetryFromReset(reset_reason, http3_used, disable_http3);
365 0 : return shouldRetry(retry_decision, [disable_http3, callback]() { callback(disable_http3); });
366 0 : }
367 :
368 0 : RetryStatus RetryStateImpl::shouldHedgeRetryPerTryTimeout(DoRetryCallback callback) {
369 : // A hedged retry on per try timeout is always retried if there are retries
370 : // left. NOTE: this is a bit different than non-hedged per try timeouts which
371 : // are only retried if the applicable retry policy specifies either
372 : // RETRY_ON_5XX or RETRY_ON_GATEWAY_ERROR. This is because these types of
373 : // retries are associated with a stream reset which is analogous to a gateway
374 : // error. When hedging on per try timeout is enabled, however, there is no
375 : // stream reset.
376 0 : return shouldRetry(RetryState::RetryDecision::RetryWithBackoff, callback);
377 0 : }
378 :
379 : RetryState::RetryDecision
380 : RetryStateImpl::wouldRetryFromHeaders(const Http::ResponseHeaderMap& response_headers,
381 : const Http::RequestHeaderMap& original_request,
382 0 : bool& disable_early_data) {
383 : // A response that contains the x-envoy-ratelimited header comes from an upstream envoy.
384 : // We retry these only when the envoy-ratelimited policy is in effect.
385 0 : if (response_headers.EnvoyRateLimited() != nullptr) {
386 0 : return (retry_on_ & RetryPolicy::RETRY_ON_ENVOY_RATE_LIMITED) ? RetryDecision::RetryWithBackoff
387 0 : : RetryDecision::NoRetry;
388 0 : }
389 :
390 0 : uint64_t response_status = Http::Utility::getResponseStatus(response_headers);
391 :
392 0 : if (retry_on_ & RetryPolicy::RETRY_ON_5XX) {
393 0 : if (Http::CodeUtility::is5xx(response_status)) {
394 0 : return RetryDecision::RetryWithBackoff;
395 0 : }
396 0 : }
397 :
398 0 : if (retry_on_ & RetryPolicy::RETRY_ON_GATEWAY_ERROR) {
399 0 : if (Http::CodeUtility::isGatewayError(response_status)) {
400 0 : return RetryDecision::RetryWithBackoff;
401 0 : }
402 0 : }
403 :
404 0 : if ((retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_4XX)) {
405 0 : Http::Code code = static_cast<Http::Code>(response_status);
406 0 : if (code == Http::Code::Conflict) {
407 0 : return RetryDecision::RetryWithBackoff;
408 0 : }
409 0 : }
410 :
411 0 : if ((retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES)) {
412 0 : for (auto code : retriable_status_codes_) {
413 0 : if (response_status == code) {
414 0 : if (static_cast<Http::Code>(code) != Http::Code::TooEarly) {
415 0 : return RetryDecision::RetryWithBackoff;
416 0 : }
417 0 : if (original_request.get(Http::Headers::get().EarlyData).empty()) {
418 : // Retry if the downstream request wasn't received as early data. Otherwise, regardless if
419 : // the request was sent as early data in upstream or not, don't retry. Instead, forward
420 : // the response to downstream.
421 0 : disable_early_data = true;
422 0 : return RetryDecision::RetryImmediately;
423 0 : }
424 0 : }
425 0 : }
426 0 : }
427 :
428 0 : if (retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_HEADERS) {
429 0 : for (const auto& retriable_header : retriable_headers_) {
430 0 : if (retriable_header->matchesHeaders(response_headers)) {
431 0 : return RetryDecision::RetryWithBackoff;
432 0 : }
433 0 : }
434 0 : }
435 :
436 0 : if (retry_on_ &
437 0 : (RetryPolicy::RETRY_ON_GRPC_CANCELLED | RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED |
438 0 : RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED | RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE |
439 0 : RetryPolicy::RETRY_ON_GRPC_INTERNAL)) {
440 0 : absl::optional<Grpc::Status::GrpcStatus> status = Grpc::Common::getGrpcStatus(response_headers);
441 0 : if (status) {
442 0 : if ((status.value() == Grpc::Status::Canceled &&
443 0 : (retry_on_ & RetryPolicy::RETRY_ON_GRPC_CANCELLED)) ||
444 0 : (status.value() == Grpc::Status::DeadlineExceeded &&
445 0 : (retry_on_ & RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED)) ||
446 0 : (status.value() == Grpc::Status::ResourceExhausted &&
447 0 : (retry_on_ & RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED)) ||
448 0 : (status.value() == Grpc::Status::Unavailable &&
449 0 : (retry_on_ & RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE)) ||
450 0 : (status.value() == Grpc::Status::Internal &&
451 0 : (retry_on_ & RetryPolicy::RETRY_ON_GRPC_INTERNAL))) {
452 0 : return RetryDecision::RetryWithBackoff;
453 0 : }
454 0 : }
455 0 : }
456 :
457 0 : return RetryDecision::NoRetry;
458 0 : }
459 :
460 : RetryState::RetryDecision
461 : RetryStateImpl::wouldRetryFromReset(const Http::StreamResetReason reset_reason,
462 0 : Http3Used http3_used, bool& disable_http3) {
463 0 : ASSERT(!disable_http3);
464 : // First check "never retry" conditions so we can short circuit (we never
465 : // retry if the reset reason is overflow).
466 0 : if (reset_reason == Http::StreamResetReason::Overflow) {
467 0 : return RetryDecision::NoRetry;
468 0 : }
469 :
470 0 : if (reset_reason == Http::StreamResetReason::LocalConnectionFailure ||
471 0 : reset_reason == Http::StreamResetReason::RemoteConnectionFailure ||
472 0 : reset_reason == Http::StreamResetReason::ConnectionTimeout) {
473 0 : if (http3_used != Http3Used::Unknown && clusterSupportsHttp3AndTcpFallback(cluster_)) {
474 : // Already got request encoder, so this must be a 0-RTT handshake failure. Retry
475 : // immediately.
476 : // TODO(danzh) consider making the retry configurable.
477 0 : ASSERT(http3_used == Http3Used::Yes,
478 0 : "0-RTT was attempted on non-Quic connection and failed.");
479 0 : return RetryDecision::RetryImmediately;
480 0 : }
481 0 : if ((retry_on_ & RetryPolicy::RETRY_ON_CONNECT_FAILURE)) {
482 : // This is a pool failure.
483 0 : return RetryDecision::RetryWithBackoff;
484 0 : }
485 0 : } else if (http3_used == Http3Used::Yes && clusterSupportsHttp3AndTcpFallback(cluster_) &&
486 0 : (retry_on_ & RetryPolicy::RETRY_ON_HTTP3_POST_CONNECT_FAILURE)) {
487 : // Retry any post-handshake failure immediately with http3 disabled if the
488 : // failed request was sent over Http/3.
489 0 : disable_http3 = true;
490 0 : return RetryDecision::RetryImmediately;
491 0 : }
492 :
493 0 : if (retry_on_ & RetryPolicy::RETRY_ON_RESET) {
494 0 : return RetryDecision::RetryWithBackoff;
495 0 : }
496 :
497 0 : if (retry_on_ & (RetryPolicy::RETRY_ON_5XX | RetryPolicy::RETRY_ON_GATEWAY_ERROR)) {
498 : // Currently we count an upstream reset as a "5xx" (since it will result in
499 : // one). With RETRY_ON_RESET we may eventually remove these policies.
500 0 : return RetryDecision::RetryWithBackoff;
501 0 : }
502 :
503 0 : if ((retry_on_ & RetryPolicy::RETRY_ON_REFUSED_STREAM) &&
504 0 : reset_reason == Http::StreamResetReason::RemoteRefusedStreamReset) {
505 0 : return RetryDecision::RetryWithBackoff;
506 0 : }
507 :
508 0 : return RetryDecision::NoRetry;
509 0 : }
510 :
511 : } // namespace Router
512 : } // namespace Envoy
|