Coverage Report

Created: 2023-11-12 09:30

/proc/self/cwd/source/common/router/retry_state_impl.cc
Line
Count
Source (jump to first uncovered line)
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
2.39k
                       TimeSource& time_source, Upstream::ResourcePriority priority) {
34
2.39k
  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
2.39k
  if (request_headers.EnvoyRetryOn() || request_headers.EnvoyRetryGrpcOn() ||
41
2.39k
      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
2.39k
  } else if ((cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
46
2.39k
             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
2.39k
  request_headers.removeEnvoyRetryOn();
54
2.39k
  request_headers.removeEnvoyRetryGrpcOn();
55
2.39k
  request_headers.removeEnvoyMaxRetries();
56
2.39k
  request_headers.removeEnvoyHedgeOnPerTryTimeout();
57
2.39k
  request_headers.removeEnvoyRetriableHeaderNames();
58
2.39k
  request_headers.removeEnvoyRetriableStatusCodes();
59
2.39k
  request_headers.removeEnvoyUpstreamRequestPerTryTimeoutMs();
60
61
2.39k
  return ret;
62
2.39k
}
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
30.3k
std::pair<uint32_t, bool> RetryStateImpl::parseRetryOn(absl::string_view config) {
192
30.3k
  uint32_t ret = 0;
193
30.3k
  bool all_fields_valid = true;
194
43.2k
  for (const auto& retry_on : StringUtil::splitToken(config, ",", false, true)) {
195
43.2k
    if (retry_on == Http::Headers::get().EnvoyRetryOnValues._5xx) {
196
73
      ret |= RetryPolicy::RETRY_ON_5XX;
197
43.1k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.GatewayError) {
198
53
      ret |= RetryPolicy::RETRY_ON_GATEWAY_ERROR;
199
43.1k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.ConnectFailure) {
200
131
      ret |= RetryPolicy::RETRY_ON_CONNECT_FAILURE;
201
42.9k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.EnvoyRateLimited) {
202
22
      ret |= RetryPolicy::RETRY_ON_ENVOY_RATE_LIMITED;
203
42.9k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Retriable4xx) {
204
10
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_4XX;
205
42.9k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RefusedStream) {
206
40
      ret |= RetryPolicy::RETRY_ON_REFUSED_STREAM;
207
42.9k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableStatusCodes) {
208
18
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
209
42.9k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableHeaders) {
210
137
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_HEADERS;
211
42.7k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Reset) {
212
187
      ret |= RetryPolicy::RETRY_ON_RESET;
213
42.5k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Http3PostConnectFailure) {
214
80
      ret |= RetryPolicy::RETRY_ON_HTTP3_POST_CONNECT_FAILURE;
215
42.5k
    } else {
216
42.5k
      all_fields_valid = false;
217
42.5k
    }
218
43.2k
  }
219
220
30.3k
  return {ret, all_fields_valid};
221
30.3k
}
222
223
30.3k
std::pair<uint32_t, bool> RetryStateImpl::parseRetryGrpcOn(absl::string_view retry_grpc_on_header) {
224
30.3k
  uint32_t ret = 0;
225
30.3k
  bool all_fields_valid = true;
226
43.2k
  for (const auto& retry_on : StringUtil::splitToken(retry_grpc_on_header, ",", false, true)) {
227
43.2k
    if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Cancelled) {
228
43
      ret |= RetryPolicy::RETRY_ON_GRPC_CANCELLED;
229
43.2k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.DeadlineExceeded) {
230
62
      ret |= RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED;
231
43.1k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.ResourceExhausted) {
232
52
      ret |= RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED;
233
43.0k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Unavailable) {
234
18
      ret |= RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE;
235
43.0k
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Internal) {
236
19
      ret |= RetryPolicy::RETRY_ON_GRPC_INTERNAL;
237
43.0k
    } else {
238
43.0k
      all_fields_valid = false;
239
43.0k
    }
240
43.2k
  }
241
242
30.3k
  return {ret, all_fields_valid};
243
30.3k
}
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