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
50
bool clusterSupportsHttp3AndTcpFallback(const Upstream::ClusterInfo& cluster) {
22
50
  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
50
         (cluster.features() & Upstream::ClusterInfo::Features::USE_ALPN);
26
50
}
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,
32
                       Server::Configuration::CommonFactoryContext& context,
33
46661
                       Event::Dispatcher& dispatcher, Upstream::ResourcePriority priority) {
34
46661
  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
46661
  if (request_headers.EnvoyRetryOn() || request_headers.EnvoyRetryGrpcOn() ||
41
46661
      route_policy.retryOn()) {
42
2579
    ret.reset(new RetryStateImpl(route_policy, request_headers, cluster, vcluster,
43
2579
                                 route_stats_context, context, dispatcher, priority, false));
44
45940
  } else if ((cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
45
44082
             Http::Utility::isSafeRequest(request_headers)) {
46
673
    ret.reset(new RetryStateImpl(route_policy, request_headers, cluster, vcluster,
47
673
                                 route_stats_context, context, dispatcher, priority, true));
48
673
  }
49

            
50
  // Consume all retry related headers to avoid them being propagated to the upstream
51
46661
  request_headers.removeEnvoyRetryOn();
52
46661
  request_headers.removeEnvoyRetryGrpcOn();
53
46661
  request_headers.removeEnvoyMaxRetries();
54
46661
  request_headers.removeEnvoyHedgeOnPerTryTimeout();
55
46661
  request_headers.removeEnvoyRetriableHeaderNames();
56
46661
  request_headers.removeEnvoyRetriableStatusCodes();
57
46661
  request_headers.removeEnvoyUpstreamRequestPerTryTimeoutMs();
58

            
59
46661
  return ret;
60
46661
}
61

            
62
RetryStateImpl::RetryStateImpl(const RetryPolicy& route_policy,
63
                               Http::RequestHeaderMap& request_headers,
64
                               const Upstream::ClusterInfo& cluster, const VirtualCluster* vcluster,
65
                               RouteStatsContextOptRef route_stats_context,
66
                               Server::Configuration::CommonFactoryContext& context,
67
                               Event::Dispatcher& dispatcher, Upstream::ResourcePriority priority,
68
                               bool auto_configured_for_http3)
69
3252
    : cluster_(cluster), vcluster_(vcluster), route_stats_context_(route_stats_context),
70
3252
      runtime_(context.runtime()), random_(context.api().randomGenerator()),
71
3252
      dispatcher_(dispatcher), time_source_(context.timeSource()),
72
3252
      retry_host_predicates_(route_policy.retryHostPredicates()),
73
3252
      retry_priority_(route_policy.retryPriority()),
74
3252
      retriable_status_codes_(route_policy.retriableStatusCodes()),
75
3252
      retriable_headers_(route_policy.retriableHeaders()),
76
3252
      reset_headers_(route_policy.resetHeaders()),
77
3252
      reset_max_interval_(route_policy.resetMaxInterval()), retry_on_(route_policy.retryOn()),
78
3252
      retries_remaining_(route_policy.numRetries()), priority_(priority),
79
3252
      auto_configured_for_http3_(auto_configured_for_http3) {
80
3252
  if ((cluster.features() & Upstream::ClusterInfo::Features::HTTP3) &&
81
3252
      Http::Utility::isSafeRequest(request_headers)) {
82
    // Because 0-RTT requests could be rejected because they are sent too early, and such requests
83
    // should always be retried, setup retry policy for 425 response code for all potential 0-RTT
84
    // requests even though the retry policy isn't configured to do so. Since 0-RTT safe requests
85
    // traditionally shouldn't have body, automatically retrying them will not cause extra
86
    // buffering. This will also enable retry if they are reset during connect.
87
678
    retry_on_ |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
88
678
    retriable_status_codes_.push_back(static_cast<uint32_t>(Http::Code::TooEarly));
89
678
  }
90
3252
  std::chrono::milliseconds base_interval(
91
3252
      runtime_.snapshot().getInteger("upstream.base_retry_backoff_ms", 25));
92
3252
  if (route_policy.baseInterval()) {
93
50
    base_interval = *route_policy.baseInterval();
94
50
  }
95

            
96
  // By default, cap the max interval to 10 times the base interval to ensure reasonable back-off
97
  // intervals.
98
3252
  std::chrono::milliseconds max_interval = base_interval * 10;
99
3252
  if (route_policy.maxInterval()) {
100
31
    max_interval = *route_policy.maxInterval();
101
31
  }
102

            
103
3252
  backoff_strategy_ = std::make_unique<JitteredExponentialBackOffStrategy>(
104
3252
      base_interval.count(), max_interval.count(), random_);
105
3252
  host_selection_max_attempts_ = route_policy.hostSelectionMaxAttempts();
106

            
107
  // Merge in the headers.
108
3252
  if (request_headers.EnvoyRetryOn()) {
109
249
    retry_on_ |= parseRetryOn(request_headers.getEnvoyRetryOnValue()).first;
110
249
  }
111
3252
  if (request_headers.EnvoyRetryGrpcOn()) {
112
15
    retry_on_ |= parseRetryGrpcOn(request_headers.getEnvoyRetryGrpcOnValue()).first;
113
15
  }
114

            
115
3252
  const auto& retriable_request_headers = route_policy.retriableRequestHeaders();
116
3252
  if (!retriable_request_headers.empty()) {
117
    // If this route limits retries by request headers, make sure there is a match.
118
6
    bool request_header_match = false;
119
10
    for (const auto& retriable_header : retriable_request_headers) {
120
10
      if (retriable_header->matchesHeaders(request_headers)) {
121
4
        request_header_match = true;
122
4
        break;
123
4
      }
124
10
    }
125

            
126
6
    if (!request_header_match) {
127
2
      retry_on_ = 0;
128
2
    }
129
6
  }
130
3252
  if (retry_on_ != 0 && request_headers.EnvoyMaxRetries()) {
131
8
    uint64_t temp;
132
8
    if (absl::SimpleAtoi(request_headers.getEnvoyMaxRetriesValue(), &temp)) {
133
      // The max retries header takes precedence if set.
134
8
      retries_remaining_ = temp;
135
8
    }
136
8
  }
137

            
138
3252
  if (request_headers.EnvoyRetriableStatusCodes()) {
139
6
    for (const auto& code :
140
10
         StringUtil::splitToken(request_headers.getEnvoyRetriableStatusCodesValue(), ",")) {
141
10
      unsigned int out;
142
10
      if (absl::SimpleAtoi(code, &out)) {
143
7
        retriable_status_codes_.emplace_back(out);
144
7
      }
145
10
    }
146
6
  }
147

            
148
3252
  if (request_headers.EnvoyRetriableHeaderNames()) {
149
    // Retriable headers in the configuration are specified via HeaderMatcher.
150
    // Giving the same flexibility via request header would require the user
151
    // to provide HeaderMatcher serialized into a string. To avoid this extra
152
    // complexity we only support name-only header matchers via request
153
    // header. Anything more sophisticated needs to be provided via config.
154
5
    for (const auto& header_name : StringUtil::splitToken(
155
9
             request_headers.EnvoyRetriableHeaderNames()->value().getStringView(), ",")) {
156
9
      envoy::config::route::v3::HeaderMatcher header_matcher;
157
9
      header_matcher.set_name(std::string(absl::StripAsciiWhitespace(header_name)));
158
9
      retriable_headers_.emplace_back(
159
9
          Http::HeaderUtility::createHeaderData(header_matcher, context));
160
9
    }
161
5
  }
162
3252
}
163

            
164
3252
RetryStateImpl::~RetryStateImpl() { resetRetry(); }
165

            
166
288
void RetryStateImpl::enableBackoffTimer() {
167
288
  if (!retry_timer_) {
168
259
    retry_timer_ = dispatcher_.createTimer([this]() -> void { backoff_callback_(); });
169
244
  }
170

            
171
288
  if (ratelimited_backoff_strategy_ != nullptr) {
172
    // If we have a backoff strategy based on rate limit feedback from the response we use it.
173
2
    retry_timer_->enableTimer(
174
2
        std::chrono::milliseconds(ratelimited_backoff_strategy_->nextBackOffMs()));
175

            
176
    // The strategy is only valid for the response that sent the ratelimit reset header and cannot
177
    // be reused.
178
2
    ratelimited_backoff_strategy_.reset();
179

            
180
2
    cluster_.trafficStats()->upstream_rq_retry_backoff_ratelimited_.inc();
181

            
182
286
  } else {
183
    // Otherwise we use a fully jittered exponential backoff algorithm.
184
286
    retry_timer_->enableTimer(std::chrono::milliseconds(backoff_strategy_->nextBackOffMs()));
185

            
186
286
    cluster_.trafficStats()->upstream_rq_retry_backoff_exponential_.inc();
187
286
  }
188
288
}
189

            
190
490
std::pair<uint32_t, bool> RetryStateImpl::parseRetryOn(absl::string_view config) {
191
490
  uint32_t ret = 0;
192
490
  bool all_fields_valid = true;
193
678
  for (const auto& retry_on : StringUtil::splitToken(config, ",", false, true)) {
194
614
    if (retry_on == Http::Headers::get().EnvoyRetryOnValues._5xx) {
195
334
      ret |= RetryPolicy::RETRY_ON_5XX;
196
507
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.GatewayError) {
197
60
      ret |= RetryPolicy::RETRY_ON_GATEWAY_ERROR;
198
220
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.ConnectFailure) {
199
87
      ret |= RetryPolicy::RETRY_ON_CONNECT_FAILURE;
200
149
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.EnvoyRateLimited) {
201
1
      ret |= RetryPolicy::RETRY_ON_ENVOY_RATE_LIMITED;
202
132
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Retriable4xx) {
203
9
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_4XX;
204
124
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RefusedStream) {
205
26
      ret |= RetryPolicy::RETRY_ON_REFUSED_STREAM;
206
101
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableStatusCodes) {
207
13
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES;
208
86
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.RetriableHeaders) {
209
4
      ret |= RetryPolicy::RETRY_ON_RETRIABLE_HEADERS;
210
80
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Reset) {
211
53
      ret |= RetryPolicy::RETRY_ON_RESET;
212
75
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.ResetBeforeRequest) {
213
3
      ret |= RetryPolicy::RETRY_ON_RESET_BEFORE_REQUEST;
214
27
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnValues.Http3PostConnectFailure) {
215
6
      ret |= RetryPolicy::RETRY_ON_HTTP3_POST_CONNECT_FAILURE;
216
22
    } else {
217
18
      all_fields_valid = false;
218
18
    }
219
614
  }
220

            
221
490
  return {ret, all_fields_valid};
222
490
}
223

            
224
254
std::pair<uint32_t, bool> RetryStateImpl::parseRetryGrpcOn(absl::string_view retry_grpc_on_header) {
225
254
  uint32_t ret = 0;
226
254
  bool all_fields_valid = true;
227
439
  for (const auto& retry_on : StringUtil::splitToken(retry_grpc_on_header, ",", false, true)) {
228
375
    if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Cancelled) {
229
16
      ret |= RetryPolicy::RETRY_ON_GRPC_CANCELLED;
230
365
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.DeadlineExceeded) {
231
8
      ret |= RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED;
232
352
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.ResourceExhausted) {
233
10
      ret |= RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED;
234
343
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Unavailable) {
235
3
      ret |= RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE;
236
338
    } else if (retry_on == Http::Headers::get().EnvoyRetryOnGrpcValues.Internal) {
237
4
      ret |= RetryPolicy::RETRY_ON_GRPC_INTERNAL;
238
334
    } else {
239
334
      all_fields_valid = false;
240
334
    }
241
375
  }
242

            
243
254
  return {ret, all_fields_valid};
244
254
}
245

            
246
absl::optional<std::chrono::milliseconds>
247
10
RetryStateImpl::parseResetInterval(const Http::ResponseHeaderMap& response_headers) const {
248
13
  for (const auto& reset_header : reset_headers_) {
249
13
    const auto& interval = reset_header->parseInterval(time_source_, response_headers);
250
13
    if (interval.has_value() && interval.value() <= reset_max_interval_) {
251
7
      return interval;
252
7
    }
253
13
  }
254

            
255
3
  return absl::nullopt;
256
10
}
257

            
258
6372
void RetryStateImpl::resetRetry() {
259
6372
  if (backoff_callback_ != nullptr) {
260
288
    cluster_.resourceManager(priority_).retries().dec();
261
288
    backoff_callback_ = nullptr;
262
288
  }
263
6372
  if (next_loop_callback_ != nullptr) {
264
9
    cluster_.resourceManager(priority_).retries().dec();
265
9
    next_loop_callback_ = nullptr;
266
9
  }
267
6372
}
268

            
269
3120
RetryStatus RetryStateImpl::shouldRetry(RetryDecision would_retry, DoRetryCallback callback) {
270
  // If a callback is armed from a previous shouldRetry and we don't need to
271
  // retry this particular request, we can infer that we did a retry earlier
272
  // and it was successful.
273
3120
  if ((backoff_callback_ || next_loop_callback_) && would_retry == RetryDecision::NoRetry) {
274
146
    cluster_.trafficStats()->upstream_rq_retry_success_.inc();
275
146
    if (vcluster_) {
276
2
      vcluster_->stats().upstream_rq_retry_success_.inc();
277
2
    }
278
146
    if (route_stats_context_.has_value()) {
279
2
      route_stats_context_->stats().upstream_rq_retry_success_.inc();
280
2
    }
281
146
  }
282

            
283
3120
  resetRetry();
284

            
285
3120
  if (would_retry == RetryDecision::NoRetry) {
286
2772
    return RetryStatus::No;
287
2772
  }
288

            
289
  // The request has exhausted the number of retries allotted to it by the retry policy configured
290
  // (or the x-envoy-max-retries header).
291
348
  if (retries_remaining_ == 0) {
292
42
    cluster_.trafficStats()->upstream_rq_retry_limit_exceeded_.inc();
293
42
    if (vcluster_) {
294
23
      vcluster_->stats().upstream_rq_retry_limit_exceeded_.inc();
295
23
    }
296
42
    if (route_stats_context_.has_value()) {
297
23
      route_stats_context_->stats().upstream_rq_retry_limit_exceeded_.inc();
298
23
    }
299
42
    return RetryStatus::NoRetryLimitExceeded;
300
42
  }
301

            
302
306
  retries_remaining_--;
303

            
304
306
  if (!cluster_.resourceManager(priority_).retries().canCreate()) {
305
8
    cluster_.trafficStats()->upstream_rq_retry_overflow_.inc();
306
8
    if (vcluster_) {
307
4
      vcluster_->stats().upstream_rq_retry_overflow_.inc();
308
4
    }
309
8
    if (route_stats_context_.has_value()) {
310
4
      route_stats_context_->stats().upstream_rq_retry_overflow_.inc();
311
4
    }
312
8
    return RetryStatus::NoOverflow;
313
8
  }
314

            
315
298
  if (!runtime_.snapshot().featureEnabled("upstream.use_retry", 100)) {
316
1
    return RetryStatus::No;
317
1
  }
318

            
319
297
  ASSERT(!backoff_callback_ && !next_loop_callback_);
320
297
  cluster_.resourceManager(priority_).retries().inc();
321
297
  cluster_.trafficStats()->upstream_rq_retry_.inc();
322
297
  if (vcluster_) {
323
68
    vcluster_->stats().upstream_rq_retry_.inc();
324
68
  }
325
297
  if (route_stats_context_.has_value()) {
326
68
    route_stats_context_->stats().upstream_rq_retry_.inc();
327
68
  }
328
297
  if (would_retry == RetryDecision::RetryWithBackoff) {
329
288
    backoff_callback_ = callback;
330
288
    enableBackoffTimer();
331
292
  } else {
332
9
    next_loop_callback_ = dispatcher_.createSchedulableCallback(callback);
333
9
    next_loop_callback_->scheduleCallbackNextIteration();
334
9
  }
335
297
  return RetryStatus::Yes;
336
298
}
337

            
338
RetryStatus RetryStateImpl::shouldRetryHeaders(const Http::ResponseHeaderMap& response_headers,
339
                                               const Http::RequestHeaderMap& original_request,
340
2932
                                               DoRetryHeaderCallback callback) {
341
  // This may be overridden in wouldRetryFromHeaders().
342
2932
  bool disable_early_data = false;
343
2932
  const RetryDecision retry_decision =
344
2932
      wouldRetryFromHeaders(response_headers, original_request, disable_early_data);
345

            
346
  // Yes, we will retry based on the headers - try to parse a rate limited reset interval from the
347
  // response.
348
2932
  if (retry_decision == RetryDecision::RetryWithBackoff && !reset_headers_.empty()) {
349
5
    const auto backoff_interval = parseResetInterval(response_headers);
350
5
    if (backoff_interval.has_value() && (backoff_interval.value().count() > 1L)) {
351
3
      ratelimited_backoff_strategy_ = std::make_unique<JitteredLowerBoundBackOffStrategy>(
352
3
          backoff_interval.value().count(), random_);
353
3
    }
354
5
  }
355

            
356
2932
  return shouldRetry(retry_decision,
357
2932
                     [disable_early_data, callback]() { callback(disable_early_data); });
358
2932
}
359

            
360
RetryStatus RetryStateImpl::shouldRetryReset(Http::StreamResetReason reset_reason,
361
                                             Http3Used http3_used, DoRetryResetCallback callback,
362
179
                                             bool upstream_request_started) {
363

            
364
  // Following wouldRetryFromReset() may override the value.
365
179
  bool disable_http3 = false;
366
179
  const RetryDecision retry_decision =
367
179
      wouldRetryFromReset(reset_reason, http3_used, disable_http3, upstream_request_started);
368
179
  return shouldRetry(retry_decision, [disable_http3, callback]() { callback(disable_http3); });
369
179
}
370

            
371
9
RetryStatus RetryStateImpl::shouldHedgeRetryPerTryTimeout(DoRetryCallback callback) {
372
  // A hedged retry on per try timeout is always retried if there are retries
373
  // left. NOTE: this is a bit different than non-hedged per try timeouts which
374
  // are only retried if the applicable retry policy specifies either
375
  // RETRY_ON_5XX or RETRY_ON_GATEWAY_ERROR. This is because these types of
376
  // retries are associated with a stream reset which is analogous to a gateway
377
  // error. When hedging on per try timeout is enabled, however, there is no
378
  // stream reset.
379
9
  return shouldRetry(RetryState::RetryDecision::RetryWithBackoff, callback);
380
9
}
381

            
382
RetryState::RetryDecision
383
RetryStateImpl::wouldRetryFromHeaders(const Http::ResponseHeaderMap& response_headers,
384
                                      const Http::RequestHeaderMap& original_request,
385
2937
                                      bool& disable_early_data) {
386
  // A response that contains the x-envoy-ratelimited header comes from an upstream envoy.
387
  // We retry these only when the envoy-ratelimited policy is in effect.
388
2937
  if (response_headers.EnvoyRateLimited() != nullptr) {
389
3
    return (retry_on_ & RetryPolicy::RETRY_ON_ENVOY_RATE_LIMITED) ? RetryDecision::RetryWithBackoff
390
3
                                                                  : RetryDecision::NoRetry;
391
3
  }
392

            
393
2934
  uint64_t response_status = Http::Utility::getResponseStatus(response_headers);
394

            
395
2934
  if (retry_on_ & RetryPolicy::RETRY_ON_5XX) {
396
327
    if (Http::CodeUtility::is5xx(response_status)) {
397
161
      return RetryDecision::RetryWithBackoff;
398
161
    }
399
327
  }
400

            
401
2773
  if (retry_on_ & RetryPolicy::RETRY_ON_GATEWAY_ERROR) {
402
26
    if (Http::CodeUtility::isGatewayError(response_status)) {
403
7
      return RetryDecision::RetryWithBackoff;
404
7
    }
405
26
  }
406

            
407
2766
  if ((retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_4XX)) {
408
11
    Http::Code code = static_cast<Http::Code>(response_status);
409
11
    if (code == Http::Code::Conflict) {
410
7
      return RetryDecision::RetryWithBackoff;
411
7
    }
412
11
  }
413

            
414
2759
  if ((retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_STATUS_CODES)) {
415
320
    for (auto code : retriable_status_codes_) {
416
320
      if (response_status == code) {
417
11
        if (static_cast<Http::Code>(code) != Http::Code::TooEarly) {
418
5
          return RetryDecision::RetryWithBackoff;
419
5
        }
420
6
        if (original_request.get(Http::Headers::get().EarlyData).empty()) {
421
          // Retry if the downstream request wasn't received as early data. Otherwise, regardless if
422
          // the request was sent as early data in upstream or not, don't retry. Instead, forward
423
          // the response to downstream.
424
4
          disable_early_data = true;
425
4
          return RetryDecision::RetryImmediately;
426
4
        }
427
6
      }
428
320
    }
429
320
  }
430

            
431
2750
  if (retry_on_ & RetryPolicy::RETRY_ON_RETRIABLE_HEADERS) {
432
48
    for (const auto& retriable_header : retriable_headers_) {
433
48
      if (retriable_header->matchesHeaders(response_headers)) {
434
11
        return RetryDecision::RetryWithBackoff;
435
11
      }
436
48
    }
437
18
  }
438

            
439
2739
  if (retry_on_ &
440
2739
      (RetryPolicy::RETRY_ON_GRPC_CANCELLED | RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED |
441
2739
       RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED | RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE |
442
2739
       RetryPolicy::RETRY_ON_GRPC_INTERNAL)) {
443
31
    absl::optional<Grpc::Status::GrpcStatus> status = Grpc::Common::getGrpcStatus(response_headers);
444
31
    if (status) {
445
21
      if ((status.value() == Grpc::Status::Canceled &&
446
21
           (retry_on_ & RetryPolicy::RETRY_ON_GRPC_CANCELLED)) ||
447
21
          (status.value() == Grpc::Status::DeadlineExceeded &&
448
13
           (retry_on_ & RetryPolicy::RETRY_ON_GRPC_DEADLINE_EXCEEDED)) ||
449
21
          (status.value() == Grpc::Status::ResourceExhausted &&
450
10
           (retry_on_ & RetryPolicy::RETRY_ON_GRPC_RESOURCE_EXHAUSTED)) ||
451
21
          (status.value() == Grpc::Status::Unavailable &&
452
5
           (retry_on_ & RetryPolicy::RETRY_ON_GRPC_UNAVAILABLE)) ||
453
21
          (status.value() == Grpc::Status::Internal &&
454
21
           (retry_on_ & RetryPolicy::RETRY_ON_GRPC_INTERNAL))) {
455
21
        return RetryDecision::RetryWithBackoff;
456
21
      }
457
21
    }
458
31
  }
459

            
460
2718
  return RetryDecision::NoRetry;
461
2739
}
462

            
463
RetryState::RetryDecision
464
RetryStateImpl::wouldRetryFromReset(const Http::StreamResetReason reset_reason,
465
                                    Http3Used http3_used, bool& disable_http3,
466
179
                                    bool upstream_request_started) {
467
179
  ASSERT(!disable_http3);
468
  // First check "never retry" conditions so we can short circuit (we never
469
  // retry if the reset reason is overflow).
470
179
  if (reset_reason == Http::StreamResetReason::Overflow) {
471
2
    return RetryDecision::NoRetry;
472
2
  }
473

            
474
177
  if (reset_reason == Http::StreamResetReason::LocalConnectionFailure ||
475
177
      reset_reason == Http::StreamResetReason::RemoteConnectionFailure ||
476
177
      reset_reason == Http::StreamResetReason::ConnectionTimeout) {
477
87
    if (http3_used != Http3Used::Unknown && clusterSupportsHttp3AndTcpFallback(cluster_)) {
478
      // Already got request encoder, so this must be a 0-RTT handshake failure. Retry
479
      // immediately.
480
      // TODO(danzh) consider making the retry configurable.
481
3
      ASSERT(http3_used == Http3Used::Yes,
482
3
             "0-RTT was attempted on non-Quic connection and failed.");
483
3
      return RetryDecision::RetryImmediately;
484
3
    }
485
84
    if (retry_on_ & RetryPolicy::RETRY_ON_CONNECT_FAILURE) {
486
      // This is a pool failure.
487
65
      return RetryDecision::RetryWithBackoff;
488
65
    }
489
143
  } else if (http3_used == Http3Used::Yes && clusterSupportsHttp3AndTcpFallback(cluster_) &&
490
90
             (retry_on_ & RetryPolicy::RETRY_ON_HTTP3_POST_CONNECT_FAILURE)) {
491
    // Retry any post-handshake failure immediately with http3 disabled if the
492
    // failed request was sent over Http/3.
493
3
    disable_http3 = true;
494
3
    return RetryDecision::RetryImmediately;
495
3
  }
496

            
497
  // Technically, this doesn't *have* to go before the RETRY_ON_RESET check,
498
  // but it's safer for the user if they have them both set
499
  // for some reason.
500
106
  if (retry_on_ & RetryPolicy::RETRY_ON_RESET_BEFORE_REQUEST && !upstream_request_started) {
501
    // Only return a positive retry decision if we haven't sent any bytes upstream.
502
1
    return RetryDecision::RetryWithBackoff;
503
1
  }
504

            
505
105
  if (retry_on_ & RetryPolicy::RETRY_ON_RESET) {
506
2
    return RetryDecision::RetryWithBackoff;
507
2
  }
508

            
509
103
  if (retry_on_ & (RetryPolicy::RETRY_ON_5XX | RetryPolicy::RETRY_ON_GATEWAY_ERROR)) {
510
    // Currently we count an upstream reset as a "5xx" (since it will result in
511
    // one). With RETRY_ON_RESET we may eventually remove these policies.
512
44
    return RetryDecision::RetryWithBackoff;
513
44
  }
514

            
515
59
  if ((retry_on_ & RetryPolicy::RETRY_ON_REFUSED_STREAM) &&
516
59
      reset_reason == Http::StreamResetReason::RemoteRefusedStreamReset) {
517
3
    return RetryDecision::RetryWithBackoff;
518
3
  }
519

            
520
56
  return RetryDecision::NoRetry;
521
59
}
522

            
523
} // namespace Router
524
} // namespace Envoy